initial commit

This commit is contained in:
ferdiansyah783
2025-08-05 12:35:40 +07:00
commit fffa2ead5c
1069 changed files with 118056 additions and 0 deletions
@@ -0,0 +1,67 @@
'use client'
// React Imports
import { useState } from 'react'
// MUI Imports
import Card from '@mui/material/Card'
import CardHeader from '@mui/material/CardHeader'
import CardContent from '@mui/material/CardContent'
import FormControlLabel from '@mui/material/FormControlLabel'
import Checkbox from '@mui/material/Checkbox'
import Button from '@mui/material/Button'
import FormControl from '@mui/material/FormControl'
import FormHelperText from '@mui/material/FormHelperText'
// Third-party Imports
import { useForm, Controller } from 'react-hook-form'
// Component Imports
import ConfirmationDialog from '@components/dialogs/confirmation-dialog'
const AccountDelete = () => {
// States
const [open, setOpen] = useState(false)
// Hooks
const {
control,
watch,
handleSubmit,
formState: { errors }
} = useForm({ defaultValues: { checkbox: false } })
// Vars
const checkboxValue = watch('checkbox')
const onSubmit = () => {
setOpen(true)
}
return (
<Card>
<CardHeader title='Delete Account' />
<CardContent>
<form onSubmit={handleSubmit(onSubmit)}>
<FormControl error={Boolean(errors.checkbox)} className='is-full mbe-6'>
<Controller
name='checkbox'
control={control}
rules={{ required: true }}
render={({ field }) => (
<FormControlLabel control={<Checkbox {...field} />} label='I confirm my account deactivation' />
)}
/>
{errors.checkbox && <FormHelperText error>Please confirm you want to delete account</FormHelperText>}
</FormControl>
<Button variant='contained' color='error' type='submit' disabled={!checkboxValue}>
Deactivate Account
</Button>
<ConfirmationDialog open={open} setOpen={setOpen} type='delete-account' />
</form>
</CardContent>
</Card>
)
}
export default AccountDelete
@@ -0,0 +1,300 @@
'use client'
// React Imports
import { useState } from 'react'
import type { ChangeEvent } from 'react'
// MUI Imports
import Grid from '@mui/material/Grid2'
import Card from '@mui/material/Card'
import CardContent from '@mui/material/CardContent'
import Button from '@mui/material/Button'
import Typography from '@mui/material/Typography'
import MenuItem from '@mui/material/MenuItem'
import Chip from '@mui/material/Chip'
import type { SelectChangeEvent } from '@mui/material/Select'
// Component Imports
import CustomTextField from '@core/components/mui/TextField'
type Data = {
firstName: string
lastName: string
email: string
organization: string
phoneNumber: number | string
address: string
state: string
zipCode: string
country: string
language: string
timezone: string
currency: string
}
// Vars
const initialData: Data = {
firstName: 'John',
lastName: 'Doe',
email: 'john.doe@example.com',
organization: 'Pixinvent',
phoneNumber: '+1 (917) 543-9876',
address: '123 Main St, New York, NY 10001',
state: 'New York',
zipCode: '634880',
country: 'usa',
language: 'english',
timezone: 'gmt-12',
currency: 'usd'
}
const languageData = ['English', 'Arabic', 'French', 'German', 'Portuguese']
const AccountDetails = () => {
// States
const [formData, setFormData] = useState<Data>(initialData)
const [fileInput, setFileInput] = useState<string>('')
const [imgSrc, setImgSrc] = useState<string>('/images/avatars/1.png')
const [language, setLanguage] = useState<string[]>(['English'])
const handleDelete = (value: string) => {
setLanguage(current => current.filter(item => item !== value))
}
const handleChange = (event: SelectChangeEvent<string[]>) => {
setLanguage(event.target.value as string[])
}
const handleFormChange = (field: keyof Data, value: Data[keyof Data]) => {
setFormData({ ...formData, [field]: value })
}
const handleFileInputChange = (file: ChangeEvent) => {
const reader = new FileReader()
const { files } = file.target as HTMLInputElement
if (files && files.length !== 0) {
reader.onload = () => setImgSrc(reader.result as string)
reader.readAsDataURL(files[0])
if (reader.result !== null) {
setFileInput(reader.result as string)
}
}
}
const handleFileInputReset = () => {
setFileInput('')
setImgSrc('/images/avatars/1.png')
}
return (
<Card>
<CardContent className='mbe-4'>
<div className='flex max-sm:flex-col items-center gap-6'>
<img height={100} width={100} className='rounded' src={imgSrc} alt='Profile' />
<div className='flex flex-grow flex-col gap-4'>
<div className='flex flex-col sm:flex-row gap-4'>
<Button component='label' variant='contained' htmlFor='account-settings-upload-image'>
Upload New Photo
<input
hidden
type='file'
value={fileInput}
accept='image/png, image/jpeg'
onChange={handleFileInputChange}
id='account-settings-upload-image'
/>
</Button>
<Button variant='tonal' color='secondary' onClick={handleFileInputReset}>
Reset
</Button>
</div>
<Typography>Allowed JPG, GIF or PNG. Max size of 800K</Typography>
</div>
</div>
</CardContent>
<CardContent>
<form onSubmit={e => e.preventDefault()}>
<Grid container spacing={6}>
<Grid size={{ xs: 12, sm: 6 }}>
<CustomTextField
fullWidth
label='First Name'
value={formData.firstName}
placeholder='John'
onChange={e => handleFormChange('firstName', e.target.value)}
/>
</Grid>
<Grid size={{ xs: 12, sm: 6 }}>
<CustomTextField
fullWidth
label='Last Name'
value={formData.lastName}
placeholder='Doe'
onChange={e => handleFormChange('lastName', e.target.value)}
/>
</Grid>
<Grid size={{ xs: 12, sm: 6 }}>
<CustomTextField
fullWidth
label='Email'
value={formData.email}
placeholder='john.doe@gmail.com'
onChange={e => handleFormChange('email', e.target.value)}
/>
</Grid>
<Grid size={{ xs: 12, sm: 6 }}>
<CustomTextField
fullWidth
label='Organization'
value={formData.organization}
placeholder='Pixinvent'
onChange={e => handleFormChange('organization', e.target.value)}
/>
</Grid>
<Grid size={{ xs: 12, sm: 6 }}>
<CustomTextField
fullWidth
label='Phone Number'
value={formData.phoneNumber}
placeholder='+1 (234) 567-8901'
onChange={e => handleFormChange('phoneNumber', e.target.value)}
/>
</Grid>
<Grid size={{ xs: 12, sm: 6 }}>
<CustomTextField
fullWidth
label='Address'
value={formData.address}
placeholder='Address'
onChange={e => handleFormChange('address', e.target.value)}
/>
</Grid>
<Grid size={{ xs: 12, sm: 6 }}>
<CustomTextField
fullWidth
label='State'
value={formData.state}
placeholder='New York'
onChange={e => handleFormChange('state', e.target.value)}
/>
</Grid>
<Grid size={{ xs: 12, sm: 6 }}>
<CustomTextField
fullWidth
type='number'
label='Zip Code'
value={formData.zipCode}
placeholder='123456'
onChange={e => handleFormChange('zipCode', e.target.value)}
/>
</Grid>
<Grid size={{ xs: 12, sm: 6 }}>
<CustomTextField
select
fullWidth
label='Country'
value={formData.country}
onChange={e => handleFormChange('country', e.target.value)}
>
<MenuItem value='usa'>USA</MenuItem>
<MenuItem value='uk'>UK</MenuItem>
<MenuItem value='australia'>Australia</MenuItem>
<MenuItem value='germany'>Germany</MenuItem>
</CustomTextField>
</Grid>
<Grid size={{ xs: 12, sm: 6 }}>
<CustomTextField
select
fullWidth
label='Language'
value={language}
slotProps={{
select: {
multiple: true, // @ts-ignore
onChange: handleChange,
renderValue: selected => (
<div className='flex flex-wrap gap-2'>
{(selected as string[]).map(value => (
<Chip
key={value}
clickable
onMouseDown={event => event.stopPropagation()}
size='small'
label={value}
onDelete={() => handleDelete(value)}
/>
))}
</div>
)
}
}}
>
{languageData.map(name => (
<MenuItem key={name} value={name}>
{name}
</MenuItem>
))}
</CustomTextField>
</Grid>
<Grid size={{ xs: 12, sm: 6 }}>
<CustomTextField
select
fullWidth
label='TimeZone'
value={formData.timezone}
onChange={e => handleFormChange('timezone', e.target.value)}
slotProps={{
select: { MenuProps: { PaperProps: { style: { maxHeight: 250 } } } }
}}
>
<MenuItem value='gmt-12'>(GMT-12:00) International Date Line West</MenuItem>
<MenuItem value='gmt-11'>(GMT-11:00) Midway Island, Samoa</MenuItem>
<MenuItem value='gmt-10'>(GMT-10:00) Hawaii</MenuItem>
<MenuItem value='gmt-09'>(GMT-09:00) Alaska</MenuItem>
<MenuItem value='gmt-08'>(GMT-08:00) Pacific Time (US & Canada)</MenuItem>
<MenuItem value='gmt-08-baja'>(GMT-08:00) Tijuana, Baja California</MenuItem>
<MenuItem value='gmt-07'>(GMT-07:00) Chihuahua, La Paz, Mazatlan</MenuItem>
<MenuItem value='gmt-07-mt'>(GMT-07:00) Mountain Time (US & Canada)</MenuItem>
<MenuItem value='gmt-06'>(GMT-06:00) Central America</MenuItem>
<MenuItem value='gmt-06-ct'>(GMT-06:00) Central Time (US & Canada)</MenuItem>
<MenuItem value='gmt-06-mc'>(GMT-06:00) Guadalajara, Mexico City, Monterrey</MenuItem>
<MenuItem value='gmt-06-sk'>(GMT-06:00) Saskatchewan</MenuItem>
<MenuItem value='gmt-05'>(GMT-05:00) Bogota, Lima, Quito, Rio Branco</MenuItem>
<MenuItem value='gmt-05-et'>(GMT-05:00) Eastern Time (US & Canada)</MenuItem>
<MenuItem value='gmt-05-ind'>(GMT-05:00) Indiana (East)</MenuItem>
<MenuItem value='gmt-04'>(GMT-04:00) Atlantic Time (Canada)</MenuItem>
<MenuItem value='gmt-04-clp'>(GMT-04:00) Caracas, La Paz</MenuItem>
</CustomTextField>
</Grid>
<Grid size={{ xs: 12, sm: 6 }}>
<CustomTextField
select
fullWidth
label='Currency'
value={formData.currency}
onChange={e => handleFormChange('currency', e.target.value)}
>
<MenuItem value='usd'>USD</MenuItem>
<MenuItem value='euro'>EUR</MenuItem>
<MenuItem value='pound'>Pound</MenuItem>
<MenuItem value='bitcoin'>Bitcoin</MenuItem>
</CustomTextField>
</Grid>
<Grid size={{ xs: 12 }} className='flex gap-4 flex-wrap'>
<Button variant='contained' type='submit'>
Save Changes
</Button>
<Button variant='tonal' type='reset' color='secondary' onClick={() => setFormData(initialData)}>
Reset
</Button>
</Grid>
</Grid>
</form>
</CardContent>
</Card>
)
}
export default AccountDetails
@@ -0,0 +1,21 @@
// MUI Imports
import Grid from '@mui/material/Grid2'
// Component Imports
import AccountDetails from './AccountDetails'
import AccountDelete from './AccountDelete'
const Account = () => {
return (
<Grid container spacing={6}>
<Grid size={{ xs: 12 }}>
<AccountDetails />
</Grid>
<Grid size={{ xs: 12 }}>
<AccountDelete />
</Grid>
</Grid>
)
}
export default Account
@@ -0,0 +1,85 @@
'use client'
// React Imports
import { useState } from 'react'
// MUI Imports
import Grid from '@mui/material/Grid2'
import Card from '@mui/material/Card'
import CardHeader from '@mui/material/CardHeader'
import CardContent from '@mui/material/CardContent'
import Button from '@mui/material/Button'
import MenuItem from '@mui/material/MenuItem'
import InputAdornment from '@mui/material/InputAdornment'
// Component Imports
import CustomTextField from '@core/components/mui/TextField'
const Address = () => {
// States
const [state, setState] = useState('')
return (
<Card>
<CardHeader title='Billing Address' />
<CardContent>
<form>
<Grid container spacing={6}>
<Grid size={{ xs: 12, sm: 6 }}>
<CustomTextField fullWidth label='Company Name' variant='outlined' placeholder='Pixinvent' />
</Grid>
<Grid size={{ xs: 12, sm: 6 }}>
<CustomTextField fullWidth label='Billing Email' variant='outlined' placeholder='john.doe@example.com' />
</Grid>
<Grid size={{ xs: 12, sm: 6 }}>
<CustomTextField fullWidth label='TAX ID' variant='outlined' placeholder='Enter TAX ID' />
</Grid>
<Grid size={{ xs: 12, sm: 6 }}>
<CustomTextField fullWidth label='VAT Number' variant='outlined' placeholder='Enter VAT Number' />
</Grid>
<Grid size={{ xs: 12, sm: 6 }}>
<CustomTextField
fullWidth
type='number'
label='Mobile Number'
placeholder='202 555 0111'
slotProps={{
input: {
startAdornment: <InputAdornment position='start'>US (+1)</InputAdornment>
}
}}
/>
</Grid>
<Grid size={{ xs: 12, sm: 6 }}>
<CustomTextField select fullWidth label='Country' value={state} onChange={e => setState(e.target.value)}>
<MenuItem value=''>Select Country</MenuItem>
<MenuItem value='australia'>Australia</MenuItem>
<MenuItem value='canada'>Canada</MenuItem>
<MenuItem value='france'>France</MenuItem>
<MenuItem value='united-kingdom'>United Kingdom</MenuItem>
<MenuItem value='united-states'>United States</MenuItem>
</CustomTextField>
</Grid>
<Grid size={{ xs: 12 }}>
<CustomTextField fullWidth label='Billing Address' variant='outlined' placeholder='Billing Address' />
</Grid>
<Grid size={{ xs: 12, sm: 6 }}>
<CustomTextField fullWidth label='State' variant='outlined' placeholder='California' />
</Grid>
<Grid size={{ xs: 12, sm: 6 }}>
<CustomTextField fullWidth type='number' label='Zip Code' variant='outlined' placeholder='231465' />
</Grid>
<Grid size={{ xs: 12 }} className='flex gap-4 flex-wrap'>
<Button variant='contained'>Save Changes</Button>
<Button variant='tonal' type='reset' color='secondary' onClick={() => setState('')}>
Discard
</Button>
</Grid>
</Grid>
</form>
</CardContent>
</Card>
)
}
export default Address
@@ -0,0 +1,96 @@
// MUI Imports
import Card from '@mui/material/Card'
import CardHeader from '@mui/material/CardHeader'
import CardContent from '@mui/material/CardContent'
import Grid from '@mui/material/Grid2'
import Typography from '@mui/material/Typography'
import Button from '@mui/material/Button'
import Chip from '@mui/material/Chip'
import Alert from '@mui/material/Alert'
import AlertTitle from '@mui/material/AlertTitle'
import LinearProgress from '@mui/material/LinearProgress'
import type { ButtonProps } from '@mui/material/Button'
// Type Imports
import type { PricingPlanType } from '@/types/pages/pricingTypes'
import type { ThemeColor } from '@core/types'
// Component Imports
import ConfirmationDialog from '@components/dialogs/confirmation-dialog'
import UpgradePlan from '@components/dialogs/upgrade-plan'
import OpenDialogOnElementClick from '@components/dialogs/OpenDialogOnElementClick'
const CurrentPlan = ({ data }: { data?: PricingPlanType[] }) => {
const buttonProps = (children: string, color: ThemeColor, variant: ButtonProps['variant']): ButtonProps => ({
children,
variant,
color
})
return (
<Card>
<CardHeader title='Current Plan' />
<CardContent>
<Grid container spacing={6}>
<Grid size={{ xs: 12, md: 6 }} className='flex flex-col gap-6'>
<div className='flex flex-col gap-1'>
<Typography color='text.primary' className='font-medium'>
Your Current Plan is Basic
</Typography>
<Typography>A simple start for everyone</Typography>
</div>
<div className='flex flex-col gap-1'>
<Typography color='text.primary' className='font-medium'>
Active until Dec 09, 2021
</Typography>
<Typography>We will send you a notification upon Subscription expiration</Typography>
</div>
<div className='flex flex-col gap-1'>
<div className='flex items-center gap-1.5'>
<Typography color='text.primary' className='font-medium'>
$199 Per Month
</Typography>
<Chip color='primary' variant='tonal' label='Popular' size='small' />
</div>
<Typography>Standard plan for small to medium businesses</Typography>
</div>
</Grid>
<Grid size={{ xs: 12, md: 6 }} className='flex flex-col gap-6'>
<Alert severity='warning'>
<AlertTitle>We need your attention!</AlertTitle>
Your plan requires update
</Alert>
<div className='flex flex-col gap-1'>
<div className='flex items-center justify-between'>
<Typography color='text.primary' className='font-medium'>
Days
</Typography>
<Typography color='text.primary' className='font-medium'>
12 of 30 Days
</Typography>
</div>
<LinearProgress variant='determinate' value={20} />
<Typography variant='body2'>18 days remaining until your plan requires update</Typography>
</div>
</Grid>
<Grid size={{ xs: 12 }} className='flex gap-4 flex-wrap'>
<OpenDialogOnElementClick
element={Button}
elementProps={buttonProps('Upgrade Plan', 'primary', 'contained')}
dialog={UpgradePlan}
dialogProps={{ data: data }}
/>
<OpenDialogOnElementClick
element={Button}
elementProps={buttonProps('Cancel Subscription', 'error', 'tonal')}
dialog={ConfirmationDialog}
dialogProps={{ type: 'unsubscribe' }}
/>
</Grid>
</Grid>
</CardContent>
</Card>
)
}
export default CurrentPlan
@@ -0,0 +1,463 @@
'use client'
// React Imports
import { useState, useEffect, useMemo } from 'react'
// Next Imports
import Link from 'next/link'
import { useParams } from 'next/navigation'
// MUI Imports
import Card from '@mui/material/Card'
import CardContent from '@mui/material/CardContent'
import Button from '@mui/material/Button'
import Typography from '@mui/material/Typography'
import Checkbox from '@mui/material/Checkbox'
import Chip from '@mui/material/Chip'
import IconButton from '@mui/material/IconButton'
import MenuItem from '@mui/material/MenuItem'
import Tooltip from '@mui/material/Tooltip'
import TablePagination from '@mui/material/TablePagination'
import type { TextFieldProps } from '@mui/material/TextField'
// Third-party Imports
import classnames from 'classnames'
import { rankItem } from '@tanstack/match-sorter-utils'
import {
createColumnHelper,
flexRender,
getCoreRowModel,
useReactTable,
getFilteredRowModel,
getFacetedRowModel,
getFacetedUniqueValues,
getFacetedMinMaxValues,
getPaginationRowModel,
getSortedRowModel
} from '@tanstack/react-table'
import type { ColumnDef, FilterFn } from '@tanstack/react-table'
import type { RankingInfo } from '@tanstack/match-sorter-utils'
// Type Imports
import type { ThemeColor } from '@core/types'
import type { InvoiceType } from '@/types/apps/invoiceTypes'
import type { Locale } from '@configs/i18n'
// Component Imports
import OptionMenu from '@core/components/option-menu'
import CustomAvatar from '@core/components/mui/Avatar'
import TablePaginationComponent from '@components/TablePaginationComponent'
import CustomTextField from '@core/components/mui/TextField'
// Util Imports
import { getInitials } from '@/utils/getInitials'
import { getLocalizedUrl } from '@/utils/i18n'
// Style Imports
import tableStyles from '@core/styles/table.module.css'
declare module '@tanstack/table-core' {
interface FilterFns {
fuzzy: FilterFn<unknown>
}
interface FilterMeta {
itemRank: RankingInfo
}
}
type InvoiceTypeWithAction = InvoiceType & {
action?: string
}
type InvoiceStatusObj = {
[key: string]: {
icon: string
color: ThemeColor
}
}
const fuzzyFilter: FilterFn<any> = (row, columnId, value, addMeta) => {
// Rank the item
const itemRank = rankItem(row.getValue(columnId), value)
// Store the itemRank info
addMeta({
itemRank
})
// Return if the item should be filtered in/out
return itemRank.passed
}
const DebouncedInput = ({
value: initialValue,
onChange,
debounce = 500,
...props
}: {
value: string | number
onChange: (value: string | number) => void
debounce?: number
} & Omit<TextFieldProps, 'onChange'>) => {
// States
const [value, setValue] = useState(initialValue)
useEffect(() => {
setValue(initialValue)
}, [initialValue])
useEffect(() => {
const timeout = setTimeout(() => {
onChange(value)
}, debounce)
return () => clearTimeout(timeout)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [value])
return <CustomTextField {...props} value={value} onChange={e => setValue(e.target.value)} />
}
// Vars
const invoiceStatusObj: InvoiceStatusObj = {
Sent: { color: 'secondary', icon: 'tabler-send-2' },
Paid: { color: 'success', icon: 'tabler-check' },
Draft: { color: 'primary', icon: 'tabler-mail' },
'Partial Payment': { color: 'warning', icon: 'tabler-chart-pie-2' },
'Past Due': { color: 'error', icon: 'tabler-alert-circle' },
Downloaded: { color: 'info', icon: 'tabler-arrow-down' }
}
// Column Definitions
const columnHelper = createColumnHelper<InvoiceTypeWithAction>()
const InvoiceListTable = ({ invoiceData }: { invoiceData?: InvoiceType[] }) => {
// States
const [status, setStatus] = useState<InvoiceType['invoiceStatus']>('')
const [rowSelection, setRowSelection] = useState({})
const [data, setData] = useState(...[invoiceData])
const [filteredData, setFilteredData] = useState(data)
const [globalFilter, setGlobalFilter] = useState('')
// Hooks
const { lang: locale } = useParams()
const columns = useMemo<ColumnDef<InvoiceTypeWithAction, any>[]>(
() => [
{
id: 'select',
header: ({ table }) => (
<Checkbox
{...{
checked: table.getIsAllRowsSelected(),
indeterminate: table.getIsSomeRowsSelected(),
onChange: table.getToggleAllRowsSelectedHandler()
}}
/>
),
cell: ({ row }) => (
<Checkbox
{...{
checked: row.getIsSelected(),
disabled: !row.getCanSelect(),
indeterminate: row.getIsSomeSelected(),
onChange: row.getToggleSelectedHandler()
}}
/>
)
},
columnHelper.accessor('id', {
header: '#',
cell: ({ row }) => (
<Typography
component={Link}
href={getLocalizedUrl(`/apps/invoice/preview/${row.original.id}`, locale as Locale)}
color='primary.main'
>{`#${row.original.id}`}</Typography>
)
}),
columnHelper.accessor('invoiceStatus', {
header: 'Status',
cell: ({ row }) => (
<Tooltip
title={
<div>
<Typography variant='body2' component='span' className='text-inherit'>
{row.original.invoiceStatus}
</Typography>
<br />
<Typography variant='body2' component='span' className='text-inherit'>
Balance:
</Typography>{' '}
{row.original.balance}
<br />
<Typography variant='body2' component='span' className='text-inherit'>
Due Date:
</Typography>{' '}
{row.original.dueDate}
</div>
}
>
<CustomAvatar skin='light' color={invoiceStatusObj[row.original.invoiceStatus].color} size={28}>
<i className={classnames('bs-4 is-4', invoiceStatusObj[row.original.invoiceStatus].icon)} />
</CustomAvatar>
</Tooltip>
)
}),
columnHelper.accessor('name', {
header: 'Client',
cell: ({ row }) => (
<div className='flex items-center gap-3'>
{getAvatar({ avatar: row.original.avatar, name: row.original.name })}
<div className='flex flex-col'>
<Typography className='font-medium' color='text.primary'>
{row.original.name}
</Typography>
<Typography variant='body2'>{row.original.companyEmail}</Typography>
</div>
</div>
)
}),
columnHelper.accessor('total', {
header: 'Total',
cell: ({ row }) => <Typography>{`$${row.original.total}`}</Typography>
}),
columnHelper.accessor('issuedDate', {
header: 'Issued Date',
cell: ({ row }) => <Typography>{row.original.issuedDate}</Typography>
}),
columnHelper.accessor('balance', {
header: 'Balance',
cell: ({ row }) => {
return row.original.balance === 0 ? (
<Chip label='Paid' color='success' size='small' variant='tonal' />
) : (
<Typography color='text.primary'>{row.original.balance}</Typography>
)
}
}),
columnHelper.accessor('action', {
header: 'Action',
cell: ({ row }) => (
<div className='flex items-center'>
<IconButton onClick={() => setData(data?.filter(invoice => invoice.id !== row.original.id))}>
<i className='tabler-trash text-textSecondary' />
</IconButton>
<IconButton>
<Link
href={getLocalizedUrl(`/apps/invoice/preview/${row.original.id}`, locale as Locale)}
className='flex'
>
<i className='tabler-eye text-textSecondary' />
</Link>
</IconButton>
<OptionMenu
iconButtonProps={{ size: 'medium' }}
iconClassName='text-textSecondary'
options={[
{
text: 'Download',
icon: 'tabler-download',
menuItemProps: { className: 'flex items-center gap-2 text-textSecondary' }
},
{
text: 'Edit',
icon: 'tabler-pencil',
href: getLocalizedUrl(`/apps/invoice/edit/${row.original.id}`, locale as Locale),
linkProps: {
className: 'flex items-center is-full plb-2 pli-4 gap-2 text-textSecondary'
}
},
{
text: 'Duplicate',
icon: 'tabler-copy',
menuItemProps: { className: 'flex items-center gap-2 text-textSecondary' }
}
]}
/>
</div>
),
enableSorting: false
})
],
// eslint-disable-next-line react-hooks/exhaustive-deps
[data, filteredData]
)
const table = useReactTable({
data: filteredData as InvoiceType[],
columns,
filterFns: {
fuzzy: fuzzyFilter
},
state: {
rowSelection,
globalFilter
},
initialState: {
pagination: {
pageSize: 10
}
},
enableRowSelection: true, //enable row selection for all rows
// enableRowSelection: row => row.original.age > 18, // or enable row selection conditionally per row
globalFilterFn: fuzzyFilter,
onRowSelectionChange: setRowSelection,
getCoreRowModel: getCoreRowModel(),
onGlobalFilterChange: setGlobalFilter,
getFilteredRowModel: getFilteredRowModel(),
getSortedRowModel: getSortedRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getFacetedRowModel: getFacetedRowModel(),
getFacetedUniqueValues: getFacetedUniqueValues(),
getFacetedMinMaxValues: getFacetedMinMaxValues()
})
const getAvatar = (params: Pick<InvoiceType, 'avatar' | 'name'>) => {
const { avatar, name } = params
if (avatar) {
return <CustomAvatar src={avatar} skin='light' size={34} />
} else {
return (
<CustomAvatar skin='light' size={34}>
{getInitials(name as string)}
</CustomAvatar>
)
}
}
useEffect(() => {
const filteredData = data?.filter(invoice => {
if (status && invoice.invoiceStatus.toLowerCase().replace(/\s+/g, '-') !== status) return false
return true
})
setFilteredData(filteredData)
}, [status, data])
return (
<Card>
<CardContent className='flex justify-between flex-col items-start md:items-center md:flex-row gap-4'>
<div className='flex items-center justify-between gap-4'>
<div className='flex items-center gap-2'>
<Typography className='hidden sm:block'>Show</Typography>
<CustomTextField
select
value={table.getState().pagination.pageSize}
onChange={e => table.setPageSize(Number(e.target.value))}
className='max-sm:is-full sm:is-[70px]'
>
<MenuItem value='10'>10</MenuItem>
<MenuItem value='25'>25</MenuItem>
<MenuItem value='50'>50</MenuItem>
</CustomTextField>
</div>
<Button
variant='contained'
component={Link}
startIcon={<i className='tabler-plus' />}
href={getLocalizedUrl('apps/invoice/add', locale as Locale)}
className='max-sm:is-full'
>
Create Invoice
</Button>
</div>
<div className='flex flex-col sm:flex-row max-sm:is-full items-start sm:items-center gap-4'>
<DebouncedInput
value={globalFilter ?? ''}
onChange={value => setGlobalFilter(String(value))}
placeholder='Search Invoice'
className='max-sm:is-full sm:is-[250px]'
/>
<CustomTextField
select
id='select-status'
value={status}
onChange={e => setStatus(e.target.value)}
className='max-sm:is-full sm:is-[160px]'
slotProps={{
select: { displayEmpty: true }
}}
>
<MenuItem value=''>Invoice Status</MenuItem>
<MenuItem value='downloaded'>Downloaded</MenuItem>
<MenuItem value='draft'>Draft</MenuItem>
<MenuItem value='paid'>Paid</MenuItem>
<MenuItem value='partial-payment'>Partial Payment</MenuItem>
<MenuItem value='past-due'>Past Due</MenuItem>
<MenuItem value='sent'>Sent</MenuItem>
</CustomTextField>
</div>
</CardContent>
<div className='overflow-x-auto'>
<table className={tableStyles.table}>
<thead>
{table.getHeaderGroups().map(headerGroup => (
<tr key={headerGroup.id}>
{headerGroup.headers.map(header => (
<th key={header.id}>
{header.isPlaceholder ? null : (
<>
<div
className={classnames({
'flex items-center': header.column.getIsSorted(),
'cursor-pointer select-none': header.column.getCanSort()
})}
onClick={header.column.getToggleSortingHandler()}
>
{flexRender(header.column.columnDef.header, header.getContext())}
{{
asc: <i className='tabler-chevron-up text-xl' />,
desc: <i className='tabler-chevron-down text-xl' />
}[header.column.getIsSorted() as 'asc' | 'desc'] ?? null}
</div>
</>
)}
</th>
))}
</tr>
))}
</thead>
{table.getFilteredRowModel().rows.length === 0 ? (
<tbody>
<tr>
<td colSpan={table.getVisibleFlatColumns().length} className='text-center'>
No data available
</td>
</tr>
</tbody>
) : (
<tbody>
{table
.getRowModel()
.rows.slice(0, table.getState().pagination.pageSize)
.map(row => {
return (
<tr key={row.id} className={classnames({ selected: row.getIsSelected() })}>
{row.getVisibleCells().map(cell => (
<td key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</td>
))}
</tr>
)
})}
</tbody>
)}
</table>
</div>
<TablePagination
component={() => <TablePaginationComponent table={table} />}
count={table.getFilteredRowModel().rows.length}
rowsPerPage={table.getState().pagination.pageSize}
page={table.getState().pagination.pageIndex}
onPageChange={(_, page) => {
table.setPageIndex(page)
}}
onRowsPerPageChange={e => table.setPageSize(Number(e.target.value))}
/>
</Card>
)
}
export default InvoiceListTable
@@ -0,0 +1,225 @@
'use client'
// React Imports
import { useState } from 'react'
// MUI Imports
import Card from '@mui/material/Card'
import Chip from '@mui/material/Chip'
import Grid from '@mui/material/Grid2'
import Radio from '@mui/material/Radio'
import Switch from '@mui/material/Switch'
import Button from '@mui/material/Button'
import RadioGroup from '@mui/material/RadioGroup'
import Typography from '@mui/material/Typography'
import CardHeader from '@mui/material/CardHeader'
import CardContent from '@mui/material/CardContent'
import FormControlLabel from '@mui/material/FormControlLabel'
import type { ButtonProps } from '@mui/material/Button'
// Type Imports
import type { ThemeColor } from '@core/types'
// Component Imports
import BillingCard from '@components/dialogs/billing-card'
import OpenDialogOnElementClick from '@components/dialogs/OpenDialogOnElementClick'
import CustomTextField from '@core/components/mui/TextField'
type DataType = {
cardNumber?: string
name?: string
expiryDate?: string
cardCvv?: string
imgSrc?: string
imgAlt?: string
cardStatus?: string
badgeColor?: ThemeColor
}
// Vars
const data: DataType[] = [
{
cardCvv: '587',
name: 'Tom McBride',
expiryDate: '12/24',
imgAlt: 'Mastercard',
badgeColor: 'primary',
cardStatus: 'Primary',
cardNumber: '5577 0000 5577 9865',
imgSrc: '/images/logos/mastercard.png'
},
{
cardCvv: '681',
name: 'Mildred Wagner',
expiryDate: '02/24',
imgAlt: 'Visa card',
cardNumber: '4532 3616 2070 5678',
imgSrc: '/images/logos/visa.png'
}
]
const PaymentMethod = () => {
// States
const [paymentMethod, setPaymentMethod] = useState<'credit' | 'cod'>('credit')
const [creditCard, setCreditCard] = useState(0)
// Hooks
const [cardData, setCardData] = useState({
cardNumber: '',
name: '',
expiryDate: '',
cardCvv: ''
})
const handleReset = () => {
setCardData({
cardNumber: '',
name: '',
expiryDate: '',
cardCvv: ''
})
}
const buttonProps = (index: number): ButtonProps => ({
variant: 'tonal',
children: 'Edit',
size: 'small',
onClick: () => setCreditCard(index)
})
return (
<Card>
<CardHeader title='Payment Method' />
<CardContent>
<Grid container spacing={6}>
<Grid size={{ xs: 12, md: 6 }}>
<Grid container spacing={6}>
<Grid size={{ xs: 12 }}>
<RadioGroup
row
name='payment-method-radio'
value={paymentMethod}
onChange={e => setPaymentMethod(e.target.value as 'credit' | 'cod')}
className='flex gap-4'
>
<FormControlLabel value='credit' control={<Radio />} label='Credit/Debit/ATM Card' />
<FormControlLabel value='cash' control={<Radio />} label='COD/Cheque' />
</RadioGroup>
</Grid>
{paymentMethod === 'credit' ? (
<>
<Grid size={{ xs: 12 }}>
<CustomTextField
fullWidth
name='number'
autoComplete='off'
label='Card Number'
placeholder='0000 0000 0000 0000'
value={cardData.cardNumber}
onChange={e => setCardData({ ...cardData, cardNumber: e.target.value })}
/>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<CustomTextField
fullWidth
name='name'
label='Name'
autoComplete='off'
placeholder='John Doe'
value={cardData.name}
onChange={e => setCardData({ ...cardData, name: e.target.value })}
/>
</Grid>
<Grid size={{ xs: 6, md: 3 }}>
<CustomTextField
fullWidth
name='expiry'
autoComplete='off'
label='Expiry Date'
placeholder='MM/YY'
value={cardData.expiryDate}
onChange={e => setCardData({ ...cardData, expiryDate: e.target.value })}
/>
</Grid>
<Grid size={{ xs: 6, md: 3 }}>
<CustomTextField
fullWidth
name='cvv'
label='CVV Code'
autoComplete='off'
placeholder='654'
value={cardData.cardCvv}
onChange={e => setCardData({ ...cardData, cardCvv: e.target.value })}
/>
</Grid>
<Grid size={{ xs: 12 }}>
<FormControlLabel control={<Switch defaultChecked />} label='Save Card for future billing?' />
</Grid>
</>
) : (
<Grid size={{ xs: 12 }}>
<Typography>
Cash on delivery is a mode of payment where you make the payment after the goods/services are
received.
</Typography>
<Typography>
You can pay cash or make the payment via debit/credit card directly to the delivery person.
</Typography>
</Grid>
)}
<Grid size={{ xs: 12 }} className='flex gap-4 flex-wrap'>
<Button type='submit' variant='contained'>
Save Changes
</Button>
<Button type='reset' variant='tonal' color='secondary' onClick={handleReset}>
Cancel
</Button>
</Grid>
</Grid>
</Grid>
<Grid size={{ xs: 12, md: 6 }} className='flex flex-col gap-6'>
<Typography color='text.primary' className='font-medium'>
My Cards
</Typography>
{data.map((item: DataType, index: number) => (
<div
key={index}
className='flex flex-col rounded bg-actionHover sm:flex-row items-start sm:justify-between max-sm:gap-4 p-6'
>
<div className='flex flex-col items-start gap-2'>
<img src={item.imgSrc} alt={item.imgAlt} />
<div className='flex items-center gap-4'>
<Typography className='text-textPrimary font-medium'>{item.name}</Typography>
{item.cardStatus ? (
<Chip color={item.badgeColor} variant='tonal' label={item.cardStatus} size='small' />
) : null}
</div>
<Typography>
{item.cardNumber && item.cardNumber.slice(0, -4).replace(/[0-9]/g, '*') + item.cardNumber.slice(-4)}
</Typography>
</div>
<div className='flex flex-col sm:items-end gap-4'>
<div className='flex gap-4'>
<OpenDialogOnElementClick
element={Button}
elementProps={buttonProps(index)}
dialog={BillingCard}
dialogProps={{ data: data[creditCard] }}
/>
<Button variant='tonal' color='error' size='small'>
Delete
</Button>
</div>
<Typography variant='body2'>Card expires at {item.expiryDate}</Typography>
</div>
</div>
))}
</Grid>
</Grid>
</CardContent>
</Card>
)
}
export default PaymentMethod
@@ -0,0 +1,65 @@
// MUI Imports
import Grid from '@mui/material/Grid2'
// Component Imports
import CurrentPlan from './CurrentPlan'
import Address from './Address'
import PaymentMethod from './PaymentMethod'
import InvoiceListTable from './InvoiceListTable'
// Data Imports
import { getPricingData, getInvoiceData } from '@/app/server/actions'
/**
* ! If you need data using an API call, uncomment the below API code, update the `process.env.API_URL` variable in the
* ! `.env` file found at root of your project and also update the API endpoints like `/pages/pricing` in below example.
* ! Also, remove the above server action import and the action itself from the `src/app/server/actions.ts` file to clean up unused code
* ! because we've used the server action for getting our static data.
*/
/* const getPricingData = async () => {
// Vars
const res = await fetch(`${process.env.API_URL}/pages/pricing`)
if (!res.ok) {
throw new Error('Failed to fetch data')
}
return res.json()
} */
/* const getInvoiceData = async () => {
// Vars
const res = await fetch(`${process.env.API_URL}/apps/invoice`)
if (!res.ok) {
throw new Error('Failed to fetch invoice data')
}
return res.json()
} */
const BillingPlans = async () => {
// Vars
const data = await getPricingData()
const invoiceData = await getInvoiceData()
return (
<Grid container spacing={6}>
<Grid size={{ xs: 12 }}>
<CurrentPlan data={data} />
</Grid>
<Grid size={{ xs: 12 }}>
<PaymentMethod />
</Grid>
<Grid size={{ xs: 12 }}>
<Address />
</Grid>
<Grid size={{ xs: 12 }}>
<InvoiceListTable invoiceData={invoiceData} />
</Grid>
</Grid>
)
}
export default BillingPlans
@@ -0,0 +1,156 @@
// Next Imports
import Link from 'next/link'
// MUI Imports
import Card from '@mui/material/Card'
import CardHeader from '@mui/material/CardHeader'
import CardContent from '@mui/material/CardContent'
import Grid from '@mui/material/Grid2'
import Typography from '@mui/material/Typography'
import Switch from '@mui/material/Switch'
// Component Imports
import CustomIconButton from '@core/components/mui/IconButton'
type ConnectedAccountsType = {
title: string
logo: string
checked: boolean
subtitle: string
}
type SocialAccountsType = {
title: string
logo: string
username?: string
isConnected: boolean
href?: string
}
// Vars
const connectedAccountsArr: ConnectedAccountsType[] = [
{
checked: true,
title: 'Google',
logo: '/images/logos/google.png',
subtitle: 'Calendar and Contacts'
},
{
checked: false,
title: 'Slack',
logo: '/images/logos/slack.png',
subtitle: 'Communications'
},
{
checked: true,
title: 'Github',
logo: '/images/logos/github.png',
subtitle: 'Manage your Git repositories'
},
{
checked: true,
title: 'Mailchimp',
subtitle: 'Email marketing service',
logo: '/images/logos/mailchimp.png'
},
{
title: 'Asana',
checked: false,
subtitle: 'Task Communication',
logo: '/images/logos/asana.png'
}
]
const socialAccountsArr: SocialAccountsType[] = [
{
title: 'Facebook',
isConnected: false,
logo: '/images/logos/facebook.png'
},
{
title: 'Twitter',
isConnected: true,
username: '@Pixinvent',
logo: '/images/logos/twitter.png',
href: 'https://twitter.com/pixinvents'
},
{
title: 'Linkedin',
isConnected: true,
username: '@Pixinvent',
logo: '/images/logos/linkedin.png',
href: 'https://in.linkedin.com/company/pixinvent'
},
{
title: 'Dribbble',
isConnected: false,
logo: '/images/logos/dribbble.png'
},
{
title: 'Behance',
isConnected: false,
logo: '/images/logos/behance.png'
}
]
const Connections = () => {
return (
<Card>
<Grid container>
<Grid size={{ xs: 12, md: 6 }}>
<CardHeader
title='Connected Accounts'
subheader='Display content from your connected accounts on your site'
/>
<CardContent className='flex flex-col gap-4'>
{connectedAccountsArr.map((item, index) => (
<div key={index} className='flex items-center justify-between gap-4'>
<div className='flex flex-grow items-center gap-4'>
<img height={32} width={32} src={item.logo} alt={item.title} />
<div className='flex-grow'>
<Typography className='text-textPrimary font-medium'>{item.title}</Typography>
<Typography variant='body2'>{item.subtitle}</Typography>
</div>
</div>
<Switch defaultChecked={item.checked} />
</div>
))}
</CardContent>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<CardHeader title='Social Accounts' subheader='Display content from social accounts on your site' />
<CardContent className='flex flex-col gap-4'>
{socialAccountsArr.map((item, index) => (
<div key={index} className='flex items-center justify-between gap-4'>
<div className='flex flex-grow items-center gap-4'>
<img height={32} width={32} src={item.logo} alt={item.title} />
<div className='flex-grow'>
<Typography className='text-textPrimary font-medium'>{item.title}</Typography>
{item.isConnected ? (
<Typography
variant='body2'
color='primary.main'
component={Link}
href={item.href || '/'}
target='_blank'
>
{item.username}
</Typography>
) : (
<Typography variant='body2'>Not Connected</Typography>
)}
</div>
</div>
<CustomIconButton variant='tonal' color={item.isConnected ? 'error' : 'secondary'}>
<i className={item.isConnected ? 'tabler-trash' : 'tabler-link'} />
</CustomIconButton>
</div>
))}
</CardContent>
</Grid>
</Grid>
</Card>
)
}
export default Connections
@@ -0,0 +1,56 @@
'use client'
// React Imports
import { useState } from 'react'
import type { SyntheticEvent, ReactElement } from 'react'
// MUI Imports
import Grid from '@mui/material/Grid2'
import Tab from '@mui/material/Tab'
import TabContext from '@mui/lab/TabContext'
import TabPanel from '@mui/lab/TabPanel'
// Component Imports
import CustomTabList from '@core/components/mui/TabList'
const AccountSettings = ({ tabContentList }: { tabContentList: { [key: string]: ReactElement } }) => {
// States
const [activeTab, setActiveTab] = useState('account')
const handleChange = (event: SyntheticEvent, value: string) => {
setActiveTab(value)
}
return (
<TabContext value={activeTab}>
<Grid container spacing={6}>
<Grid size={{ xs: 12 }}>
<CustomTabList onChange={handleChange} variant='scrollable' pill='true'>
<Tab label='Account' icon={<i className='tabler-users' />} iconPosition='start' value='account' />
<Tab label='Security' icon={<i className='tabler-lock' />} iconPosition='start' value='security' />
<Tab
label='Billing & Plans'
icon={<i className='tabler-bookmark' />}
iconPosition='start'
value='billing-plans'
/>
<Tab
label='Notifications'
icon={<i className='tabler-bell' />}
iconPosition='start'
value='notifications'
/>
<Tab label='Connections' icon={<i className='tabler-link' />} iconPosition='start' value='connections' />
</CustomTabList>
</Grid>
<Grid size={{ xs: 12 }}>
<TabPanel value={activeTab} className='p-0'>
{tabContentList[activeTab]}
</TabPanel>
</Grid>
</Grid>
</TabContext>
)
}
export default AccountSettings
@@ -0,0 +1,123 @@
'use client'
// MUI Imports
import Card from '@mui/material/Card'
import CardHeader from '@mui/material/CardHeader'
import CardContent from '@mui/material/CardContent'
import Typography from '@mui/material/Typography'
import Checkbox from '@mui/material/Checkbox'
import MenuItem from '@mui/material/MenuItem'
import Grid from '@mui/material/Grid2'
import Button from '@mui/material/Button'
// Component Imports
import Link from '@components/Link'
import Form from '@components/Form'
import CustomTextField from '@core/components/mui/TextField'
// Style Imports
import tableStyles from '@core/styles/table.module.css'
type TableDataType = {
type: string
app: boolean
email: boolean
browser: boolean
}
// Vars
const tableData: TableDataType[] = [
{
app: true,
email: true,
browser: true,
type: 'New for you'
},
{
app: true,
email: true,
browser: true,
type: 'Account activity'
},
{
app: false,
email: true,
browser: true,
type: 'A new browser used to sign in'
},
{
app: false,
email: true,
browser: false,
type: 'A new device is linked'
}
]
const Notifications = () => {
return (
<Card>
<CardHeader
title='Recent Devices'
subheader={
<>
We need permission from your browser to show notifications.
<Link className='text-primary'> Request Permission</Link>
</>
}
/>
<Form>
<div className='overflow-x-auto'>
<table className={tableStyles.table}>
<thead>
<tr>
<th>Type</th>
<th>Email</th>
<th>Browser</th>
<th>App</th>
</tr>
</thead>
<tbody className='border-be'>
{tableData.map((data, index) => (
<tr key={index}>
<td>
<Typography color='text.primary'>{data.type}</Typography>
</td>
<td>
<Checkbox defaultChecked={data.email} />
</td>
<td>
<Checkbox defaultChecked={data.browser} />
</td>
<td>
<Checkbox defaultChecked={data.app} />
</td>
</tr>
))}
</tbody>
</table>
</div>
<CardContent>
<Typography className='mbe-6 font-medium'>When should we send you notifications?</Typography>
<Grid container spacing={6}>
<Grid size={{ xs: 12, sm: 6, md: 4 }}>
<CustomTextField select fullWidth defaultValue='online'>
<MenuItem value='online'>Only when I&#39;m online</MenuItem>
<MenuItem value='anytime'>Anytime</MenuItem>
</CustomTextField>
</Grid>
<Grid size={{ xs: 12 }} className='flex gap-4 flex-wrap'>
<Button variant='contained' type='submit'>
Save Changes
</Button>
<Button variant='tonal' color='secondary' type='reset'>
Discard
</Button>
</Grid>
</Grid>
</CardContent>
</Form>
</Card>
)
}
export default Notifications
@@ -0,0 +1,70 @@
// MUI Imports
import Card from '@mui/material/Card'
import CardHeader from '@mui/material/CardHeader'
import CardContent from '@mui/material/CardContent'
import Typography from '@mui/material/Typography'
import Chip from '@mui/material/Chip'
import IconButton from '@mui/material/IconButton'
type ApiKeyListType = {
title: string
access: string
date: string
key: string
}
// Vars
const apiKeyList: ApiKeyListType[] = [
{
title: 'Server Key 1',
access: 'Full Access',
date: '28 Apr 2021, 18:20 GTM+4:10',
key: '23eaf7f0-f4f7-495e-8b86-fad3261282ac'
},
{
title: 'Server Key 2',
access: 'Read Only',
date: '12 Feb 2021, 10:30 GTM+2:30',
key: 'bb98e571-a2e2-4de8-90a9-2e231b5e99'
},
{
title: 'Server Key 3',
access: 'Full Access',
date: '28 Dec 2021, 12:21 GTM+4:10',
key: '2e915e59-3105-47f2-8838-6e46bf83b711'
}
]
const ApiKeyList = () => {
return (
<Card>
<CardHeader title='API Key List & Access' className='pbe-4' />
<CardContent className='flex flex-col gap-6'>
<Typography>
An API key is a simple encrypted string that identifies an application without any principal. They are useful
for accessing public data anonymously, and are used to associate API requests with your project for quota and
billing.
</Typography>
{apiKeyList.map((item, index) => (
<div key={index} className='flex flex-col gap-2 p-4 rounded bg-actionHover'>
<div className='flex items-center gap-3'>
<Typography variant='h5'>{item.title}</Typography>
<Chip color='primary' variant='tonal' label={item.access} size='small' />
</div>
<div className='flex items-center gap-1'>
<Typography className='font-medium'>{item.key}</Typography>
<div className='flex'>
<IconButton size='small'>
<i className='tabler-copy text-textSecondary' />
</IconButton>
</div>
</div>
<Typography color='text.disabled'>{`Created on ${item.date}`}</Typography>
</div>
))}
</CardContent>
</Card>
)
}
export default ApiKeyList
@@ -0,0 +1,136 @@
'use client'
// React Imports
import { useState } from 'react'
// MUI Imports
import Card from '@mui/material/Card'
import CardHeader from '@mui/material/CardHeader'
import CardContent from '@mui/material/CardContent'
import Grid from '@mui/material/Grid2'
import InputAdornment from '@mui/material/InputAdornment'
import IconButton from '@mui/material/IconButton'
import Typography from '@mui/material/Typography'
import Button from '@mui/material/Button'
//Component Imports
import CustomTextField from '@core/components/mui/TextField'
const ChangePasswordCard = () => {
// States
const [isCurrentPasswordShown, setIsCurrentPasswordShown] = useState(false)
const [isConfirmPasswordShown, setIsConfirmPasswordShown] = useState(false)
const [isNewPasswordShown, setIsNewPasswordShown] = useState(false)
const handleClickShowCurrentPassword = () => {
setIsCurrentPasswordShown(!isCurrentPasswordShown)
}
return (
<Card>
<CardHeader title='Change Password' />
<CardContent>
<form>
<Grid container spacing={6}>
<Grid size={{ xs: 12, sm: 6 }}>
<CustomTextField
fullWidth
label='Current Password'
type={isCurrentPasswordShown ? 'text' : 'password'}
placeholder='············'
slotProps={{
input: {
endAdornment: (
<InputAdornment position='end'>
<IconButton
edge='end'
onClick={handleClickShowCurrentPassword}
onMouseDown={e => e.preventDefault()}
>
<i className={isCurrentPasswordShown ? 'tabler-eye-off' : 'tabler-eye'} />
</IconButton>
</InputAdornment>
)
}
}}
/>
</Grid>
</Grid>
<Grid container className='mbs-0' spacing={6}>
<Grid size={{ xs: 12, sm: 6 }}>
<CustomTextField
fullWidth
label='New Password'
type={isNewPasswordShown ? 'text' : 'password'}
placeholder='············'
slotProps={{
input: {
endAdornment: (
<InputAdornment position='end'>
<IconButton
edge='end'
onClick={() => setIsNewPasswordShown(!isNewPasswordShown)}
onMouseDown={e => e.preventDefault()}
>
<i className={isNewPasswordShown ? 'tabler-eye-off' : 'tabler-eye'} />
</IconButton>
</InputAdornment>
)
}
}}
/>
</Grid>
<Grid size={{ xs: 12, sm: 6 }}>
<CustomTextField
fullWidth
label='Confirm New Password'
type={isConfirmPasswordShown ? 'text' : 'password'}
placeholder='············'
slotProps={{
input: {
endAdornment: (
<InputAdornment position='end'>
<IconButton
edge='end'
onClick={() => setIsConfirmPasswordShown(!isConfirmPasswordShown)}
onMouseDown={e => e.preventDefault()}
>
<i className={isConfirmPasswordShown ? 'tabler-eye-off' : 'tabler-eye'} />
</IconButton>
</InputAdornment>
)
}
}}
/>
</Grid>
<Grid size={{ xs: 12 }} className='flex flex-col gap-4'>
<Typography variant='h6'>Password Requirements:</Typography>
<div className='flex flex-col gap-4'>
<div className='flex items-center gap-2.5'>
<i className='tabler-circle-filled text-[8px]' />
Minimum 8 characters long - the more, the better
</div>
<div className='flex items-center gap-2.5'>
<i className='tabler-circle-filled text-[8px]' />
At least one lowercase & one uppercase character
</div>
<div className='flex items-center gap-2.5'>
<i className='tabler-circle-filled text-[8px]' />
At least one number, symbol, or whitespace character
</div>
</div>
</Grid>
<Grid size={{ xs: 12 }} className='flex gap-4'>
<Button variant='contained'>Save Changes</Button>
<Button variant='tonal' type='reset' color='secondary'>
Reset
</Button>
</Grid>
</Grid>
</form>
</CardContent>
</Card>
)
}
export default ChangePasswordCard
@@ -0,0 +1,45 @@
'use client'
// MUI Imports
import Card from '@mui/material/Card'
import CardHeader from '@mui/material/CardHeader'
import CardContent from '@mui/material/CardContent'
import Grid from '@mui/material/Grid2'
import Button from '@mui/material/Button'
import MenuItem from '@mui/material/MenuItem'
// Component Imports
import CustomTextField from '@core/components/mui/TextField'
const CreateApiKey = () => {
return (
<Card>
<CardHeader title='Create an API Key' />
<CardContent className='!pb-0'>
<Grid container spacing={6}>
<Grid size={{ xs: 12, md: 6 }}>
<form className='flex justify-end items-end bs-full flex-col gap-5 pbe-6'>
<CustomTextField select fullWidth label='Choose the API key type you want to create' defaultValue=''>
<MenuItem value='full-control'>Full Control</MenuItem>
<MenuItem value='modify'>Modify</MenuItem>
<MenuItem value='read-execute'>Read & Execute</MenuItem>
<MenuItem value='list-folder-contents'>List Folder Contents</MenuItem>
<MenuItem value='read-only'>Read Only</MenuItem>
<MenuItem value='read-write'>Read & Write</MenuItem>
</CustomTextField>
<CustomTextField label='Name the API key' placeholder='Server key 1' fullWidth />
<Button variant='contained' fullWidth>
Create Key
</Button>
</form>
</Grid>
<Grid size={{ xs: 12, md: 6 }} className='flex items-end justify-center '>
<img src='/images/illustrations/characters/4.png' width={197} height={224} alt='api illustration' />
</Grid>
</Grid>
</CardContent>
</Card>
)
}
export default CreateApiKey
@@ -0,0 +1,109 @@
// React Imports
import type { ReactElement } from 'react'
// MUI Imports
import Card from '@mui/material/Card'
import CardHeader from '@mui/material/CardHeader'
import Typography from '@mui/material/Typography'
// Style Imports
import tableStyles from '@core/styles/table.module.css'
type RecentDeviceDataType = {
browserIcon: ReactElement
browserName: string
device: string
location: string
date: string
}
// Vars
const recentDeviceData: RecentDeviceDataType[] = [
{
location: 'Switzerland',
device: 'HP Spectre 360',
date: '10, Sept 20:07',
browserName: 'Chrome on Windows',
browserIcon: <i className='tabler-brand-windows text-[22px] text-info' />
},
{
location: 'Los Angeles, CA',
device: 'Google Pixel 3a',
date: '20 Apr 2022, 10:20',
browserName: 'Chrome on Android',
browserIcon: <i className='tabler-brand-android text-[22px] text-success' />
},
{
location: 'San Francisco, CA',
device: 'iPhone 12x',
date: '16 Apr 2022, 04:20',
browserName: 'Chrome on iPhone',
browserIcon: <i className='tabler-device-mobile text-[22px] text-error' />
},
{
location: 'India',
device: 'Apple iMac',
date: '28 Apr 2022, 18:20',
browserName: 'Chrome on MacOS',
browserIcon: <i className='tabler-brand-apple text-[22px] text-secondary' />
},
{
location: 'Switzerland',
device: 'Macbook Pro',
date: '20 Apr 2022, 10:20',
browserName: 'Chrome on Windows',
browserIcon: <i className='tabler-brand-apple text-[22px] text-warning' />
},
{
location: 'Dubai',
device: 'Oneplus 9 Pro',
date: '16 Apr 2022, 04:20',
browserName: 'Chrome on Android',
browserIcon: <i className='tabler-brand-android text-[22px] text-success' />
}
]
const RecentDevicesTable = () => {
return (
<Card>
<CardHeader title='Recent Devices' />
<div className='overflow-x-auto'>
<table className={tableStyles.table}>
<thead>
<tr>
<th>Browser</th>
<th>Device</th>
<th>Location</th>
<th>Recent Activities</th>
</tr>
</thead>
<tbody>
{recentDeviceData.map((device, index) => (
<tr key={index}>
<td>
<div className='flex items-center gap-2.5'>
{device.browserIcon}
<Typography className='font-medium' color='text.primary'>
{device.browserName}
</Typography>
</div>
</td>
<td>
<Typography>{device.device}</Typography>
</td>
<td>
<Typography>{device.location}</Typography>
</td>
<td>
<Typography>{device.date}</Typography>
</td>
</tr>
))}
</tbody>
</table>
</div>
</Card>
)
}
export default RecentDevicesTable
@@ -0,0 +1,45 @@
// MUI Imports
import Card from '@mui/material/Card'
import Button from '@mui/material/Button'
import CardHeader from '@mui/material/CardHeader'
import CardContent from '@mui/material/CardContent'
import Typography from '@mui/material/Typography'
import type { ButtonProps } from '@mui/material/Button'
// Type Imports
import Link from '@components/Link'
// Component Imports
import TwoFactorAuth from '@components/dialogs/two-factor-auth'
import OpenDialogOnElementClick from '@components/dialogs/OpenDialogOnElementClick'
const TwoFactorAuthenticationCard = () => {
// Vars
const buttonProps: ButtonProps = {
variant: 'contained',
children: 'Enable two-factor authentication'
}
return (
<>
<Card>
<CardHeader title='Two-steps verification' />
<CardContent className='flex flex-col items-start gap-6'>
<div className='flex flex-col gap-4'>
<Typography variant='h5' color='text.secondary'>
Two factor authentication is not enabled yet.
</Typography>
<Typography>
Two-factor authentication adds an additional layer of security to your account by requiring more than just
a password to log in.
<Link className='text-primary'>Learn more.</Link>
</Typography>
</div>
<OpenDialogOnElementClick element={Button} elementProps={buttonProps} dialog={TwoFactorAuth} />
</CardContent>
</Card>
</>
)
}
export default TwoFactorAuthenticationCard
@@ -0,0 +1,33 @@
// MUI Imports
import Grid from '@mui/material/Grid2'
// Component Imports
import ChangePasswordCard from './ChangePasswordCard'
import TwoFactorAuthenticationCard from './TwoFactorAuthenticationCard'
import CreateApiKey from './CreateApiKey'
import ApiKeyList from './ApiKeyList'
import RecentDevicesTable from './RecentDevicesTable'
const Security = () => {
return (
<Grid container spacing={6}>
<Grid size={{ xs: 12 }}>
<ChangePasswordCard />
</Grid>
<Grid size={{ xs: 12 }}>
<TwoFactorAuthenticationCard />
</Grid>
<Grid size={{ xs: 12 }}>
<CreateApiKey />
</Grid>
<Grid size={{ xs: 12 }}>
<ApiKeyList />
</Grid>
<Grid size={{ xs: 12 }}>
<RecentDevicesTable />
</Grid>
</Grid>
)
}
export default Security