initial commit
This commit is contained in:
@@ -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'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
|
||||
Reference in New Issue
Block a user