initial commit
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
// MUI Imports
|
||||
import Card from '@mui/material/Card'
|
||||
import CardContent from '@mui/material/CardContent'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import Chip from '@mui/material/Chip'
|
||||
import Divider from '@mui/material/Divider'
|
||||
import Button from '@mui/material/Button'
|
||||
import type { ButtonProps } from '@mui/material/Button'
|
||||
|
||||
// Type Imports
|
||||
import type { ThemeColor } from '@core/types'
|
||||
|
||||
// Component Imports
|
||||
import EditUserInfo from '@components/dialogs/edit-user-info'
|
||||
import ConfirmationDialog from '@components/dialogs/confirmation-dialog'
|
||||
import OpenDialogOnElementClick from '@components/dialogs/OpenDialogOnElementClick'
|
||||
import CustomAvatar from '@core/components/mui/Avatar'
|
||||
|
||||
// Vars
|
||||
const userData = {
|
||||
firstName: 'Seth',
|
||||
lastName: 'Hallam',
|
||||
userName: '@shallamb',
|
||||
billingEmail: 'shallamb@gmail.com',
|
||||
status: 'active',
|
||||
role: 'Subscriber',
|
||||
taxId: 'Tax-8894',
|
||||
contact: '+1 (234) 464-0600',
|
||||
language: ['English'],
|
||||
country: 'France',
|
||||
useAsBillingAddress: true
|
||||
}
|
||||
|
||||
const UserDetails = () => {
|
||||
// Vars
|
||||
const buttonProps = (children: string, color: ThemeColor, variant: ButtonProps['variant']): ButtonProps => ({
|
||||
children,
|
||||
color,
|
||||
variant
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card>
|
||||
<CardContent className='flex flex-col pbs-12 gap-6'>
|
||||
<div className='flex flex-col gap-6'>
|
||||
<div className='flex items-center justify-center flex-col gap-4'>
|
||||
<div className='flex flex-col items-center gap-4'>
|
||||
<CustomAvatar alt='user-profile' src='/images/avatars/1.png' variant='rounded' size={120} />
|
||||
<Typography variant='h5'>{`${userData.firstName} ${userData.lastName}`}</Typography>
|
||||
</div>
|
||||
<Chip label='Author' color='secondary' size='small' variant='tonal' />
|
||||
</div>
|
||||
<div className='flex items-center justify-around flex-wrap gap-4'>
|
||||
<div className='flex items-center gap-4'>
|
||||
<CustomAvatar variant='rounded' color='primary' skin='light'>
|
||||
<i className='tabler-checkbox' />
|
||||
</CustomAvatar>
|
||||
<div>
|
||||
<Typography variant='h5'>1.23k</Typography>
|
||||
<Typography>Task Done</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex items-center gap-4'>
|
||||
<CustomAvatar variant='rounded' color='primary' skin='light'>
|
||||
<i className='tabler-briefcase' />
|
||||
</CustomAvatar>
|
||||
<div>
|
||||
<Typography variant='h5'>568</Typography>
|
||||
<Typography>Project Done</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Typography variant='h5'>Details</Typography>
|
||||
<Divider className='mlb-4' />
|
||||
<div className='flex flex-col gap-2'>
|
||||
<div className='flex items-center flex-wrap gap-x-1.5'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
Username:
|
||||
</Typography>
|
||||
<Typography>{userData.userName}</Typography>
|
||||
</div>
|
||||
<div className='flex items-center flex-wrap gap-x-1.5'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
Billing Email:
|
||||
</Typography>
|
||||
<Typography>{userData.billingEmail}</Typography>
|
||||
</div>
|
||||
<div className='flex items-center flex-wrap gap-x-1.5'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
Status
|
||||
</Typography>
|
||||
<Typography color='text.primary'>{userData.status}</Typography>
|
||||
</div>
|
||||
<div className='flex items-center flex-wrap gap-x-1.5'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
Role:
|
||||
</Typography>
|
||||
<Typography color='text.primary'>{userData.role}</Typography>
|
||||
</div>
|
||||
<div className='flex items-center flex-wrap gap-x-1.5'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
Tax ID:
|
||||
</Typography>
|
||||
<Typography color='text.primary'>{userData.taxId}</Typography>
|
||||
</div>
|
||||
<div className='flex items-center flex-wrap gap-x-1.5'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
Contact:
|
||||
</Typography>
|
||||
<Typography color='text.primary'>{userData.contact}</Typography>
|
||||
</div>
|
||||
<div className='flex items-center flex-wrap gap-x-1.5'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
Language:
|
||||
</Typography>
|
||||
<Typography color='text.primary'>{userData.language}</Typography>
|
||||
</div>
|
||||
<div className='flex items-center flex-wrap gap-x-1.5'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
Country:
|
||||
</Typography>
|
||||
<Typography color='text.primary'>{userData.country}</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex gap-4 justify-center'>
|
||||
<OpenDialogOnElementClick
|
||||
element={Button}
|
||||
elementProps={buttonProps('Edit', 'primary', 'contained')}
|
||||
dialog={EditUserInfo}
|
||||
dialogProps={{ data: userData }}
|
||||
/>
|
||||
<OpenDialogOnElementClick
|
||||
element={Button}
|
||||
elementProps={buttonProps('Suspend', 'error', 'tonal')}
|
||||
dialog={ConfirmationDialog}
|
||||
dialogProps={{ type: 'suspend-account' }}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default UserDetails
|
||||
@@ -0,0 +1,72 @@
|
||||
// MUI Imports
|
||||
import Card from '@mui/material/Card'
|
||||
import CardContent from '@mui/material/CardContent'
|
||||
import Chip from '@mui/material/Chip'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import LinearProgress from '@mui/material/LinearProgress'
|
||||
import Button from '@mui/material/Button'
|
||||
import type { ButtonProps } from '@mui/material/Button'
|
||||
|
||||
// Component Imports
|
||||
import UpgradePlan from '@components/dialogs/upgrade-plan'
|
||||
import OpenDialogOnElementClick from '@components/dialogs/OpenDialogOnElementClick'
|
||||
|
||||
const UserPlan = () => {
|
||||
// Vars
|
||||
const buttonProps: ButtonProps = {
|
||||
variant: 'contained',
|
||||
children: 'Upgrade Plan'
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card className='border-2 border-primary rounded shadow-primarySm'>
|
||||
<CardContent className='flex flex-col gap-6'>
|
||||
<div className='flex justify-between'>
|
||||
<Chip label='Standard' size='small' color='primary' variant='tonal' />
|
||||
<div className='flex justify-center'>
|
||||
<Typography variant='h5' component='sup' className='self-start' color='primary.main'>
|
||||
$
|
||||
</Typography>
|
||||
<Typography component='span' variant='h1' color='primary.main'>
|
||||
99
|
||||
</Typography>
|
||||
<Typography component='sub' className='self-end' color='text.primary'>
|
||||
/month
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex flex-col gap-2'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<i className='tabler-circle-filled text-[10px] text-secondary' />
|
||||
<Typography component='span'>10 Users</Typography>
|
||||
</div>
|
||||
<div className='flex items-center gap-2'>
|
||||
<i className='tabler-circle-filled text-[10px] text-secondary' />
|
||||
<Typography component='span'>Up to 10 GB storage</Typography>
|
||||
</div>
|
||||
<div className='flex items-center gap-2'>
|
||||
<i className='tabler-circle-filled text-[10px] text-secondary' />
|
||||
<Typography component='span'>Basic Support</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex flex-col gap-1'>
|
||||
<div className='flex items-center justify-between'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
Days
|
||||
</Typography>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
26 of 30 Days
|
||||
</Typography>
|
||||
</div>
|
||||
<LinearProgress variant='determinate' value={65} />
|
||||
<Typography variant='body2'>4 days remaining</Typography>
|
||||
</div>
|
||||
<OpenDialogOnElementClick element={Button} elementProps={buttonProps} dialog={UpgradePlan} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default UserPlan
|
||||
@@ -0,0 +1,21 @@
|
||||
// MUI Imports
|
||||
import Grid from '@mui/material/Grid2'
|
||||
|
||||
// Component Imports
|
||||
import UserDetails from './UserDetails'
|
||||
import UserPlan from './UserPlan'
|
||||
|
||||
const UserLeftOverview = () => {
|
||||
return (
|
||||
<Grid container spacing={6}>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<UserDetails />
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<UserPlan />
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
export default UserLeftOverview
|
||||
@@ -0,0 +1,184 @@
|
||||
// MUI Imports
|
||||
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 Grid from '@mui/material/Grid2'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import type { ButtonProps } from '@mui/material/Button'
|
||||
|
||||
// Component Imports
|
||||
import AddNewAddress from '@components/dialogs/add-edit-address'
|
||||
import OpenDialogOnElementClick from '@components/dialogs/OpenDialogOnElementClick'
|
||||
|
||||
// Vars
|
||||
const data = {
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
email: 'johndoe@gmail.com',
|
||||
country: 'US',
|
||||
address1: '100 Water Plant Avenue,',
|
||||
address2: 'Building 1303 Wake Island',
|
||||
landmark: 'Near Water Plant',
|
||||
city: 'New York',
|
||||
state: 'Capholim',
|
||||
zipCode: '403114',
|
||||
taxId: 'TAX-875623',
|
||||
vatNumber: 'SDF754K77',
|
||||
contact: '+1(609) 933-44-22'
|
||||
}
|
||||
|
||||
const BillingAddress = () => {
|
||||
const buttonProps: ButtonProps = {
|
||||
variant: 'contained',
|
||||
children: 'Edit Address',
|
||||
size: 'small',
|
||||
startIcon: <i className='tabler-plus' />
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card>
|
||||
<CardHeader
|
||||
title='Billing Address'
|
||||
action={
|
||||
<OpenDialogOnElementClick
|
||||
element={Button}
|
||||
elementProps={buttonProps}
|
||||
dialog={AddNewAddress}
|
||||
dialogProps={{ data }}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<CardContent>
|
||||
<Grid container>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<table>
|
||||
<tbody className='align-top'>
|
||||
<tr>
|
||||
<td className='p-1 pis-0 is-[150px]'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
Name:
|
||||
</Typography>
|
||||
</td>
|
||||
<td className='p-1'>
|
||||
<Typography>{`${data.firstName} ${data.lastName}`}</Typography>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='p-1 pis-0 is-[150px]'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
Billing Email:
|
||||
</Typography>
|
||||
</td>
|
||||
<td className='p-1'>
|
||||
<Typography>{data.email}</Typography>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='p-1 pis-0 is-[150px]'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
Tax ID:
|
||||
</Typography>
|
||||
</td>
|
||||
<td className='p-1'>
|
||||
<Typography>{data.taxId}</Typography>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='p-1 pis-0 is-[150px]'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
VAT Number:
|
||||
</Typography>
|
||||
</td>
|
||||
<td className='p-1'>
|
||||
<Typography>{data.vatNumber}</Typography>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='p-1 pis-0 is-[150px]'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
Billing Address:
|
||||
</Typography>
|
||||
</td>
|
||||
<td className='p-1'>
|
||||
<Typography>{`${data.address1} ${data.address2}`}</Typography>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<table>
|
||||
<tbody className='align-top'>
|
||||
<tr>
|
||||
<td className='p-1 pis-0 is-[150px]'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
Contact:
|
||||
</Typography>
|
||||
</td>
|
||||
<td className='p-1'>
|
||||
<Typography>{data.contact}</Typography>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='p-1 pis-0 is-[150px]'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
Landmark:
|
||||
</Typography>
|
||||
</td>
|
||||
<td className='p-1'>
|
||||
<Typography>{data.landmark}</Typography>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='p-1 pis-0 is-[150px]'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
Landmark:
|
||||
</Typography>
|
||||
</td>
|
||||
<td className='p-1'>
|
||||
<Typography>{data.city}</Typography>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='p-1 pis-0 is-[150px]'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
Country:
|
||||
</Typography>
|
||||
</td>
|
||||
<td className='p-1'>
|
||||
<Typography>{data.country}</Typography>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='p-1 pis-0 is-[150px]'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
State:
|
||||
</Typography>
|
||||
</td>
|
||||
<td className='p-1'>
|
||||
<Typography>{data.state}</Typography>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='p-1 pis-0 is-[150px]'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
Zip Code:
|
||||
</Typography>
|
||||
</td>
|
||||
<td className='p-1'>
|
||||
<Typography>{data.zipCode}</Typography>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default BillingAddress
|
||||
@@ -0,0 +1,94 @@
|
||||
'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 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 { ThemeColor } from '@core/types'
|
||||
import type { PricingPlanType } from '@/types/pages/pricingTypes'
|
||||
|
||||
// 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, variant: ButtonProps['variant'], color: ThemeColor): 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-4'>
|
||||
<div>
|
||||
<Typography className='font-medium text-textPrimary'>Your Current Plan is Basic</Typography>
|
||||
<Typography>A simple start for everyone</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
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-2'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
$99 Per Month
|
||||
</Typography>
|
||||
<Chip color='primary' label='Popular' size='small' variant='tonal' />
|
||||
</div>
|
||||
<Typography>Standard plan for small to medium businesses</Typography>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Alert icon={false} severity='warning' className='mbe-4'>
|
||||
<AlertTitle>We need your attention!</AlertTitle>
|
||||
Your plan requires update
|
||||
</Alert>
|
||||
<div className='flex items-center justify-between'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
Days
|
||||
</Typography>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
26 of 30 Days
|
||||
</Typography>
|
||||
</div>
|
||||
<LinearProgress variant='determinate' value={80} className='mlb-1 bs-2.5' />
|
||||
<Typography variant='body2'>Your plan requires update</Typography>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12 }} className='flex gap-4 flex-wrap'>
|
||||
<OpenDialogOnElementClick
|
||||
element={Button}
|
||||
elementProps={buttonProps('Upgrade plan', 'contained', 'primary')}
|
||||
dialog={UpgradePlan}
|
||||
dialogProps={{ data: data }}
|
||||
/>
|
||||
<OpenDialogOnElementClick
|
||||
element={Button}
|
||||
elementProps={buttonProps('Cancel Subscription', 'tonal', 'error')}
|
||||
dialog={ConfirmationDialog}
|
||||
dialogProps={{ type: 'unsubscribe' }}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export default CurrentPlan
|
||||
@@ -0,0 +1,143 @@
|
||||
'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 Typography from '@mui/material/Typography'
|
||||
import Button from '@mui/material/Button'
|
||||
import Chip from '@mui/material/Chip'
|
||||
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'
|
||||
|
||||
type DataType = {
|
||||
name: string
|
||||
imgSrc: string
|
||||
imgAlt: string
|
||||
cardCvv: string
|
||||
expiryDate: string
|
||||
cardNumber: 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',
|
||||
imgAlt: 'Visa card',
|
||||
expiryDate: '02/24',
|
||||
name: 'Mildred Wagner',
|
||||
cardNumber: '4532 3616 2070 5678',
|
||||
imgSrc: '/images/logos/visa.png'
|
||||
},
|
||||
{
|
||||
cardCvv: '3845',
|
||||
expiryDate: '08/20',
|
||||
badgeColor: 'error',
|
||||
cardStatus: 'Expired',
|
||||
name: 'Lester Jennings',
|
||||
imgAlt: 'American Express card',
|
||||
cardNumber: '3700 000000 00002',
|
||||
imgSrc: '/images/logos/american-express.png'
|
||||
}
|
||||
]
|
||||
|
||||
const PaymentMethod = () => {
|
||||
// States
|
||||
const [creditCard, setCreditCard] = useState(0)
|
||||
|
||||
const handleAddCard = () => {
|
||||
setCreditCard(-1)
|
||||
}
|
||||
|
||||
const handleClickOpen = (index: number) => {
|
||||
setCreditCard(index)
|
||||
}
|
||||
|
||||
// Vars
|
||||
const addButtonProps: ButtonProps = {
|
||||
variant: 'contained',
|
||||
children: 'Add Card',
|
||||
size: 'small',
|
||||
color: 'primary',
|
||||
startIcon: <i className='tabler-plus' />,
|
||||
onClick: handleAddCard
|
||||
}
|
||||
|
||||
const editButtonProps = (index: number): ButtonProps => ({
|
||||
variant: 'tonal',
|
||||
children: 'Edit',
|
||||
size: 'small',
|
||||
onClick: () => handleClickOpen(index)
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card>
|
||||
<CardHeader
|
||||
title='Payment Methods'
|
||||
action={<OpenDialogOnElementClick element={Button} elementProps={addButtonProps} dialog={BillingCard} />}
|
||||
/>
|
||||
<CardContent className='flex flex-col gap-4'>
|
||||
{data.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className='flex justify-between border rounded sm:items-center p-6 flex-col !items-start sm:flex-row gap-2'
|
||||
>
|
||||
<div className='flex flex-col items-start gap-2'>
|
||||
<img src={item.imgSrc} alt={item.imgAlt} height={25} />
|
||||
<div className='flex items-center gap-2'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
{item.name}
|
||||
</Typography>
|
||||
{item.cardStatus ? (
|
||||
<Chip color={item.badgeColor} label={item.cardStatus} size='small' variant='tonal' />
|
||||
) : 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 gap-4'>
|
||||
<div className='flex items-center justify-end gap-4'>
|
||||
<OpenDialogOnElementClick
|
||||
element={Button}
|
||||
elementProps={editButtonProps(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>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default PaymentMethod
|
||||
@@ -0,0 +1,28 @@
|
||||
// MUI Imports
|
||||
import Grid from '@mui/material/Grid2'
|
||||
|
||||
// Type Imports
|
||||
import type { PricingPlanType } from '@/types/pages/pricingTypes'
|
||||
|
||||
// Component Imports
|
||||
import CurrentPlan from './CurrentPlan'
|
||||
import PaymentMethod from './PaymentMethod'
|
||||
import BillingAddress from './BillingAddress'
|
||||
|
||||
const BillingPlans = ({ data }: { data?: PricingPlanType[] }) => {
|
||||
return (
|
||||
<Grid container spacing={6}>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<CurrentPlan data={data} />
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<PaymentMethod />
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<BillingAddress />
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
export default BillingPlans
|
||||
@@ -0,0 +1,161 @@
|
||||
'use client'
|
||||
|
||||
// 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'
|
||||
|
||||
// Third-party Imports
|
||||
import classnames from 'classnames'
|
||||
|
||||
// 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://www.linkedin.com/company/pixinvent'
|
||||
},
|
||||
{
|
||||
title: 'Dribbble',
|
||||
isConnected: false,
|
||||
logo: '/images/logos/dribbble.png'
|
||||
},
|
||||
{
|
||||
title: 'Behance',
|
||||
isConnected: false,
|
||||
logo: '/images/logos/behance.png'
|
||||
}
|
||||
]
|
||||
|
||||
const ConnectionsTab = () => {
|
||||
return (
|
||||
<Grid container spacing={6}>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<Card>
|
||||
<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={36} width={36} src={item.logo} alt={item.title} />
|
||||
<div className='flex flex-col flex-grow gap-0.5'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
{item.title}
|
||||
</Typography>
|
||||
<Typography>{item.subtitle}</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<Switch defaultChecked={item.checked} />
|
||||
</div>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<Card>
|
||||
<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={36} width={36} src={item.logo} alt={item.title} />
|
||||
<div className='flex flex-col flex-grow gap-0.5'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
{item.title}
|
||||
</Typography>
|
||||
{item.isConnected ? (
|
||||
<Typography color='primary.main' component={Link} href={item.href || '/'} target='_blank'>
|
||||
{item.username}
|
||||
</Typography>
|
||||
) : (
|
||||
<Typography>Not Connected</Typography>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<CustomIconButton variant='tonal' color={item.isConnected ? 'error' : 'secondary'}>
|
||||
<i className={classnames(item.isConnected ? 'tabler-trash text-error' : 'tabler-link')} />
|
||||
</CustomIconButton>
|
||||
</div>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConnectionsTab
|
||||
@@ -0,0 +1,58 @@
|
||||
'use client'
|
||||
|
||||
// React Imports
|
||||
import { useState } from 'react'
|
||||
import type { SyntheticEvent, ReactElement } from 'react'
|
||||
|
||||
// MUI Imports
|
||||
import Tab from '@mui/material/Tab'
|
||||
import TabContext from '@mui/lab/TabContext'
|
||||
import TabPanel from '@mui/lab/TabPanel'
|
||||
import Grid from '@mui/material/Grid2'
|
||||
|
||||
// Component Imports
|
||||
import CustomTabList from '@core/components/mui/TabList'
|
||||
|
||||
const UserRight = ({ tabContentList }: { tabContentList: { [key: string]: ReactElement } }) => {
|
||||
// States
|
||||
const [activeTab, setActiveTab] = useState('overview')
|
||||
|
||||
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 icon={<i className='tabler-users' />} value='overview' label='Overview' iconPosition='start' />
|
||||
<Tab icon={<i className='tabler-lock' />} value='security' label='Security' iconPosition='start' />
|
||||
<Tab
|
||||
icon={<i className='tabler-bookmark' />}
|
||||
value='billing-plans'
|
||||
label='Billing & Plans'
|
||||
iconPosition='start'
|
||||
/>
|
||||
<Tab
|
||||
icon={<i className='tabler-bell' />}
|
||||
value='notifications'
|
||||
label='Notifications'
|
||||
iconPosition='start'
|
||||
/>
|
||||
<Tab icon={<i className='tabler-link' />} value='connections' label='Connections' iconPosition='start' />
|
||||
</CustomTabList>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<TabPanel value={activeTab} className='p-0'>
|
||||
{tabContentList[activeTab]}
|
||||
</TabPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</TabContext>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default UserRight
|
||||
@@ -0,0 +1,93 @@
|
||||
// MUI Imports
|
||||
import Card from '@mui/material/Card'
|
||||
import CardHeader from '@mui/material/CardHeader'
|
||||
import CardActions from '@mui/material/CardActions'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import Checkbox from '@mui/material/Checkbox'
|
||||
import Button from '@mui/material/Button'
|
||||
|
||||
// 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: false,
|
||||
email: true,
|
||||
browser: false,
|
||||
type: 'New for you'
|
||||
},
|
||||
{
|
||||
app: true,
|
||||
email: false,
|
||||
browser: true,
|
||||
type: 'Account activity'
|
||||
},
|
||||
{
|
||||
app: true,
|
||||
email: true,
|
||||
browser: true,
|
||||
type: 'A new browser used to sign in'
|
||||
},
|
||||
{
|
||||
app: false,
|
||||
email: false,
|
||||
browser: true,
|
||||
type: 'A new device is linked'
|
||||
}
|
||||
]
|
||||
|
||||
const NotificationsTab = () => {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader title='Notifications' subheader='You will receive notification for the below selected items' />
|
||||
<div className='overflow-x-auto'>
|
||||
<table className={tableStyles.table}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Type</th>
|
||||
<th>App</th>
|
||||
<th>Email</th>
|
||||
<th>Browser</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.app} />
|
||||
</td>
|
||||
<td>
|
||||
<Checkbox defaultChecked={data.email} />
|
||||
</td>
|
||||
<td>
|
||||
<Checkbox defaultChecked={data.browser} />
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<CardActions className='flex items-center'>
|
||||
<Button variant='contained' type='submit'>
|
||||
Save Changes
|
||||
</Button>
|
||||
<Button variant='tonal' color='secondary' type='reset'>
|
||||
Discard
|
||||
</Button>
|
||||
</CardActions>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export default NotificationsTab
|
||||
@@ -0,0 +1,355 @@
|
||||
'use client'
|
||||
|
||||
// React Imports
|
||||
import { useState, useMemo } from 'react'
|
||||
import type { MouseEvent } from 'react'
|
||||
|
||||
// Next Imports
|
||||
import Link from 'next/link'
|
||||
import { useParams } from 'next/navigation'
|
||||
|
||||
// MUI Imports
|
||||
import Card from '@mui/material/Card'
|
||||
import CardHeader from '@mui/material/CardHeader'
|
||||
import Button from '@mui/material/Button'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import IconButton from '@mui/material/IconButton'
|
||||
import Menu from '@mui/material/Menu'
|
||||
import MenuItem from '@mui/material/MenuItem'
|
||||
import Tooltip from '@mui/material/Tooltip'
|
||||
import TablePagination from '@mui/material/TablePagination'
|
||||
|
||||
// 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 CustomTextField from '@core/components/mui/TextField'
|
||||
import TablePaginationComponent from '@components/TablePaginationComponent'
|
||||
|
||||
// Util Imports
|
||||
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
|
||||
}
|
||||
|
||||
// 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 [rowSelection, setRowSelection] = useState({})
|
||||
const [data, setData] = useState(...[invoiceData])
|
||||
const [globalFilter, setGlobalFilter] = useState('')
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null)
|
||||
|
||||
// Vars
|
||||
const open = Boolean(anchorEl)
|
||||
|
||||
// Hooks
|
||||
const { lang: locale } = useParams()
|
||||
|
||||
const columns = useMemo<ColumnDef<InvoiceTypeWithAction, any>[]>(
|
||||
() => [
|
||||
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('text-base', invoiceStatusObj[row.original.invoiceStatus].icon)} />
|
||||
</CustomAvatar>
|
||||
</Tooltip>
|
||||
)
|
||||
}),
|
||||
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('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-edit',
|
||||
href: getLocalizedUrl(`/apps/invoice/edit/${row.original.id}`, locale as Locale),
|
||||
linkProps: {
|
||||
className: classnames('flex items-center bs-[40px] plb-2 pli-4 is-full 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
|
||||
[]
|
||||
)
|
||||
|
||||
const table = useReactTable({
|
||||
data: data 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 handleClick = (event: MouseEvent<HTMLButtonElement>) => {
|
||||
setAnchorEl(event.currentTarget)
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null)
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader
|
||||
title='Invoice List'
|
||||
sx={{ '& .MuiCardHeader-action': { m: 0 } }}
|
||||
className='flex items-center justify-between flex-wrap gap-4'
|
||||
action={
|
||||
<div className='flex items-center gap-4 flex-wrap'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Typography>Show</Typography>
|
||||
<CustomTextField
|
||||
select
|
||||
value={table.getState().pagination.pageSize}
|
||||
onChange={e => table.setPageSize(Number(e.target.value))}
|
||||
className='is-[70px]'
|
||||
>
|
||||
<MenuItem value='10'>10</MenuItem>
|
||||
<MenuItem value='25'>25</MenuItem>
|
||||
<MenuItem value='50'>50</MenuItem>
|
||||
</CustomTextField>
|
||||
</div>
|
||||
<Button
|
||||
variant='tonal'
|
||||
aria-haspopup='true'
|
||||
onClick={handleClick}
|
||||
color='secondary'
|
||||
aria-expanded={open ? 'true' : undefined}
|
||||
endIcon={<i className='tabler-upload' />}
|
||||
aria-controls={open ? 'user-view-overview-export' : undefined}
|
||||
>
|
||||
Export
|
||||
</Button>
|
||||
<Menu open={open} anchorEl={anchorEl} onClose={handleClose} id='user-view-overview-export'>
|
||||
<MenuItem onClick={handleClose} className='uppercase'>
|
||||
pdf
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleClose} className='uppercase'>
|
||||
xlsx
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleClose} className='uppercase'>
|
||||
csv
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<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.id === 'action' && { className: 'is-24' })}>
|
||||
{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>
|
||||
<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} {...(cell.id.includes('action') && { className: 'is-24' })}>
|
||||
{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)
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export default InvoiceListTable
|
||||
@@ -0,0 +1,354 @@
|
||||
'use client'
|
||||
|
||||
// React Imports
|
||||
import { useState, useMemo, useEffect } from 'react'
|
||||
|
||||
// MUI Imports
|
||||
import Typography from '@mui/material/Typography'
|
||||
import LinearProgress from '@mui/material/LinearProgress'
|
||||
import Card from '@mui/material/Card'
|
||||
import CardHeader from '@mui/material/CardHeader'
|
||||
import MenuItem from '@mui/material/MenuItem'
|
||||
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'
|
||||
|
||||
// Component Imports
|
||||
import CustomAvatar from '@core/components/mui/Avatar'
|
||||
import CustomTextField from '@core/components/mui/TextField'
|
||||
import TablePaginationComponent from '@components/TablePaginationComponent'
|
||||
|
||||
// Style Imports
|
||||
import tableStyles from '@core/styles/table.module.css'
|
||||
|
||||
type ProjectListDataType = {
|
||||
id: number
|
||||
img: string
|
||||
hours: string
|
||||
totalTask: string
|
||||
projectType: string
|
||||
projectTitle: string
|
||||
progressValue: number
|
||||
progressColor: ThemeColor
|
||||
}
|
||||
|
||||
declare module '@tanstack/table-core' {
|
||||
interface FilterFns {
|
||||
fuzzy: FilterFn<unknown>
|
||||
}
|
||||
interface FilterMeta {
|
||||
itemRank: RankingInfo
|
||||
}
|
||||
}
|
||||
|
||||
// Vars
|
||||
const projectTable: ProjectListDataType[] = [
|
||||
{
|
||||
id: 1,
|
||||
hours: '18:42',
|
||||
progressValue: 78,
|
||||
totalTask: '122/240',
|
||||
progressColor: 'success',
|
||||
projectType: 'React Project',
|
||||
projectTitle: 'BGC eCommerce App',
|
||||
img: '/images/logos/react-bg.png'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
hours: '20:42',
|
||||
progressValue: 18,
|
||||
totalTask: '9/56',
|
||||
progressColor: 'error',
|
||||
projectType: 'Figma Project',
|
||||
projectTitle: 'Falcon Logo Design',
|
||||
img: '/images/logos/figma-bg.png'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
hours: '120:87',
|
||||
progressValue: 62,
|
||||
totalTask: '290/320',
|
||||
progressColor: 'primary',
|
||||
projectType: 'VueJs Project',
|
||||
projectTitle: 'Dashboard Design',
|
||||
img: '/images/logos/vue-bg.png'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
hours: '89:19',
|
||||
progressValue: 8,
|
||||
totalTask: '7/63',
|
||||
progressColor: 'error',
|
||||
projectType: 'Xamarin Project',
|
||||
projectTitle: 'Foodista Mobile App',
|
||||
img: '/images/icons/mobile-bg.png'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
hours: '230:10',
|
||||
progressValue: 49,
|
||||
totalTask: '120/186',
|
||||
progressColor: 'warning',
|
||||
projectType: 'Python Project',
|
||||
projectTitle: 'Dojo React Project',
|
||||
img: '/images/logos/python-bg.png'
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
hours: '342:41',
|
||||
progressValue: 92,
|
||||
totalTask: '99/109',
|
||||
progressColor: 'success',
|
||||
projectType: 'Sketch Project',
|
||||
projectTitle: 'Blockchain Website',
|
||||
img: '/images/logos/sketch-bg.png'
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
hours: '12:45',
|
||||
progressValue: 88,
|
||||
totalTask: '98/110',
|
||||
progressColor: 'success',
|
||||
projectType: 'HTML Project',
|
||||
projectTitle: 'Hoffman Website',
|
||||
img: '/images/logos/html-bg.png'
|
||||
}
|
||||
]
|
||||
|
||||
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)} />
|
||||
}
|
||||
|
||||
// Column Definitions
|
||||
const columnHelper = createColumnHelper<ProjectListDataType>()
|
||||
|
||||
const ProjectListTable = () => {
|
||||
// States
|
||||
const [rowSelection, setRowSelection] = useState({})
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [data, setData] = useState(...[projectTable])
|
||||
const [globalFilter, setGlobalFilter] = useState('')
|
||||
|
||||
// Hooks
|
||||
const columns = useMemo<ColumnDef<ProjectListDataType, any>[]>(
|
||||
() => [
|
||||
columnHelper.accessor('projectTitle', {
|
||||
header: 'Project',
|
||||
cell: ({ row }) => (
|
||||
<div className='flex items-center gap-4'>
|
||||
<CustomAvatar src={row.original.img} size={34} />
|
||||
<div className='flex flex-col'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
{row.original.projectTitle}
|
||||
</Typography>
|
||||
<Typography variant='body2'>{row.original.projectType}</Typography>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}),
|
||||
columnHelper.accessor('totalTask', {
|
||||
header: 'Total Task',
|
||||
cell: ({ row }) => <Typography color='text.primary'>{row.original.totalTask}</Typography>
|
||||
}),
|
||||
columnHelper.accessor('progressValue', {
|
||||
header: 'Progress',
|
||||
cell: ({ row }) => (
|
||||
<>
|
||||
<Typography color='text.primary'>{`${row.original.progressValue}%`}</Typography>
|
||||
<LinearProgress
|
||||
color={row.original.progressColor}
|
||||
value={row.original.progressValue}
|
||||
variant='determinate'
|
||||
className='is-full'
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}),
|
||||
columnHelper.accessor('hours', {
|
||||
header: 'Hours',
|
||||
cell: ({ row }) => <Typography>{row.original.hours}</Typography>
|
||||
})
|
||||
],
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[]
|
||||
)
|
||||
|
||||
const table = useReactTable({
|
||||
data,
|
||||
columns,
|
||||
filterFns: {
|
||||
fuzzy: fuzzyFilter
|
||||
},
|
||||
state: {
|
||||
rowSelection,
|
||||
globalFilter
|
||||
},
|
||||
initialState: {
|
||||
pagination: {
|
||||
pageSize: 7
|
||||
}
|
||||
},
|
||||
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()
|
||||
})
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader title='User's Project List' className='flex flex-wrap gap-4' />
|
||||
<div className='flex items-center justify-between p-6 gap-4'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Typography>Show</Typography>
|
||||
<CustomTextField
|
||||
select
|
||||
value={table.getState().pagination.pageSize}
|
||||
onChange={e => table.setPageSize(Number(e.target.value))}
|
||||
className='is-[70px]'
|
||||
>
|
||||
<MenuItem value='5'>5</MenuItem>
|
||||
<MenuItem value='7'>7</MenuItem>
|
||||
<MenuItem value='10'>10</MenuItem>
|
||||
</CustomTextField>
|
||||
</div>
|
||||
<DebouncedInput
|
||||
value={globalFilter ?? ''}
|
||||
onChange={value => setGlobalFilter(String(value))}
|
||||
placeholder='Search Project'
|
||||
/>
|
||||
</div>
|
||||
<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)
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export default ProjectListTable
|
||||
@@ -0,0 +1,115 @@
|
||||
'use client'
|
||||
|
||||
// MUI Imports
|
||||
import Card from '@mui/material/Card'
|
||||
import CardHeader from '@mui/material/CardHeader'
|
||||
import CardContent from '@mui/material/CardContent'
|
||||
import Avatar from '@mui/material/Avatar'
|
||||
import AvatarGroup from '@mui/material/AvatarGroup'
|
||||
import { styled } from '@mui/material/styles'
|
||||
import TimelineDot from '@mui/lab/TimelineDot'
|
||||
import TimelineItem from '@mui/lab/TimelineItem'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import TimelineContent from '@mui/lab/TimelineContent'
|
||||
import TimelineSeparator from '@mui/lab/TimelineSeparator'
|
||||
import TimelineConnector from '@mui/lab/TimelineConnector'
|
||||
import MuiTimeline from '@mui/lab/Timeline'
|
||||
import type { TimelineProps } from '@mui/lab/Timeline'
|
||||
|
||||
// Component Imports
|
||||
import CustomAvatar from '@core/components/mui/Avatar'
|
||||
|
||||
// Styled Timeline component
|
||||
const Timeline = styled(MuiTimeline)<TimelineProps>({
|
||||
paddingLeft: 0,
|
||||
paddingRight: 0,
|
||||
'& .MuiTimelineItem-root': {
|
||||
width: '100%',
|
||||
'&:before': {
|
||||
display: 'none'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const UserActivityTimeLine = () => {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader title='User Activity Timeline' />
|
||||
<CardContent>
|
||||
<Timeline>
|
||||
<TimelineItem>
|
||||
<TimelineSeparator>
|
||||
<TimelineDot color='primary' />
|
||||
<TimelineConnector />
|
||||
</TimelineSeparator>
|
||||
<TimelineContent>
|
||||
<div className='flex flex-wrap items-center justify-between gap-x-2 mbe-2.5'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
12 Invoices have been paid
|
||||
</Typography>
|
||||
<Typography variant='caption' color='text.disabled'>
|
||||
12 min ago
|
||||
</Typography>
|
||||
</div>
|
||||
<Typography className='mbe-2'>Invoices have been paid to the company</Typography>
|
||||
<div className='flex items-center gap-2.5 is-fit bg-actionHover rounded plb-[5px] pli-2.5'>
|
||||
<img height={20} alt='invoice.pdf' src='/images/icons/pdf-document.png' />
|
||||
<Typography className='font-medium'>invoices.pdf</Typography>
|
||||
</div>
|
||||
</TimelineContent>
|
||||
</TimelineItem>
|
||||
<TimelineItem>
|
||||
<TimelineSeparator>
|
||||
<TimelineDot color='success' />
|
||||
<TimelineConnector />
|
||||
</TimelineSeparator>
|
||||
<TimelineContent>
|
||||
<div className='flex flex-wrap items-center justify-between gap-x-2 mbe-2.5'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
Client Meeting
|
||||
</Typography>
|
||||
<Typography variant='caption' color='text.disabled'>
|
||||
45 min ago
|
||||
</Typography>
|
||||
</div>
|
||||
<Typography className='mbe-2'>Project meeting with john @10:15am</Typography>
|
||||
<div className='flex items-center gap-2.5'>
|
||||
<CustomAvatar src='/images/avatars/1.png' size={32} />
|
||||
<div className='flex flex-col flex-wrap'>
|
||||
<Typography variant='body2' className='font-medium'>
|
||||
Lester McCarthy (Client)
|
||||
</Typography>
|
||||
<Typography variant='body2'>CEO of Pixinvent</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</TimelineContent>
|
||||
</TimelineItem>
|
||||
<TimelineItem>
|
||||
<TimelineSeparator>
|
||||
<TimelineDot color='info' />
|
||||
<TimelineConnector />
|
||||
</TimelineSeparator>
|
||||
<TimelineContent>
|
||||
<div className='flex flex-wrap items-center justify-between gap-x-2 mbe-2.5'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
Create a new project for client
|
||||
</Typography>
|
||||
<Typography variant='caption' color='text.disabled'>
|
||||
2 Day Ago
|
||||
</Typography>
|
||||
</div>
|
||||
<Typography className='mbe-2'>6 team members in a project</Typography>
|
||||
<AvatarGroup total={6} className='pull-up'>
|
||||
<Avatar alt='Travis Howard' src='/images/avatars/1.png' />
|
||||
<Avatar alt='Agnes Walker' src='/images/avatars/4.png' />
|
||||
<Avatar alt='John Doe' src='/images/avatars/2.png' />
|
||||
</AvatarGroup>
|
||||
</TimelineContent>
|
||||
</TimelineItem>
|
||||
</Timeline>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export default UserActivityTimeLine
|
||||
@@ -0,0 +1,48 @@
|
||||
// MUI Imports
|
||||
import Grid from '@mui/material/Grid2'
|
||||
|
||||
// Component Imports
|
||||
import ProjectListTable from './ProjectListTable'
|
||||
import UserActivityTimeLine from './UserActivityTimeline'
|
||||
import InvoiceListTable from './InvoiceListTable'
|
||||
|
||||
// Data Imports
|
||||
import { 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 `/apps/invoice` 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 getInvoiceData = async () => {
|
||||
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 OverViewTab = async () => {
|
||||
// Vars
|
||||
const invoiceData = await getInvoiceData()
|
||||
|
||||
return (
|
||||
<Grid container spacing={6}>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<ProjectListTable />
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<UserActivityTimeLine />
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<InvoiceListTable invoiceData={invoiceData} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
export default OverViewTab
|
||||
@@ -0,0 +1,90 @@
|
||||
'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 Alert from '@mui/material/Alert'
|
||||
import AlertTitle from '@mui/material/AlertTitle'
|
||||
import Button from '@mui/material/Button'
|
||||
|
||||
// Component Imports
|
||||
import CustomTextField from '@core/components/mui/TextField'
|
||||
|
||||
const ChangePassword = () => {
|
||||
// States
|
||||
const [isPasswordShown, setIsPasswordShown] = useState(false)
|
||||
const [isConfirmPasswordShown, setIsConfirmPasswordShown] = useState(false)
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader title='Change Password' />
|
||||
<CardContent className='flex flex-col gap-4'>
|
||||
<Alert icon={false} severity='warning' onClose={() => {}}>
|
||||
<AlertTitle>Ensure that these requirements are met</AlertTitle>
|
||||
Minimum 8 characters long, uppercase & symbol
|
||||
</Alert>
|
||||
<form>
|
||||
<Grid container spacing={4}>
|
||||
<Grid size={{ xs: 12, sm: 6 }}>
|
||||
<CustomTextField
|
||||
fullWidth
|
||||
label='Password'
|
||||
type={isPasswordShown ? 'text' : 'password'}
|
||||
slotProps={{
|
||||
input: {
|
||||
endAdornment: (
|
||||
<InputAdornment position='end'>
|
||||
<IconButton
|
||||
edge='end'
|
||||
onClick={() => setIsPasswordShown(!isPasswordShown)}
|
||||
onMouseDown={e => e.preventDefault()}
|
||||
>
|
||||
<i className={isPasswordShown ? 'tabler-eye-off' : 'tabler-eye'} />
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, sm: 6 }}>
|
||||
<CustomTextField
|
||||
fullWidth
|
||||
label='Confirm Password'
|
||||
type={isConfirmPasswordShown ? 'text' : 'password'}
|
||||
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 gap-4'>
|
||||
<Button variant='contained'>Change Password</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export default ChangePassword
|
||||
@@ -0,0 +1,93 @@
|
||||
// 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 DataType = {
|
||||
device: string
|
||||
browser: string
|
||||
location: string
|
||||
recentActivity: string
|
||||
browserIcon: ReactElement
|
||||
}
|
||||
|
||||
// Vars
|
||||
const recentDeviceData: DataType[] = [
|
||||
{
|
||||
device: 'Dell XPS 15',
|
||||
location: 'United States',
|
||||
browser: 'Chrome on Windows',
|
||||
recentActivity: '10, Jan 2020 20:07',
|
||||
browserIcon: <i className='tabler-brand-windows text-[22px] text-info' />
|
||||
},
|
||||
{
|
||||
location: 'Ghana',
|
||||
device: 'Google Pixel 3a',
|
||||
browser: 'Chrome on Android',
|
||||
recentActivity: '11, Jan 2020 10:16',
|
||||
browserIcon: <i className='tabler-brand-android text-[22px] text-success' />
|
||||
},
|
||||
{
|
||||
location: 'Mayotte',
|
||||
device: 'Apple iMac',
|
||||
browser: 'Chrome on MacOS',
|
||||
recentActivity: '11, Jan 2020 12:10',
|
||||
browserIcon: <i className='tabler-brand-apple text-[22px] text-secondary' />
|
||||
},
|
||||
{
|
||||
location: 'Mauritania',
|
||||
device: 'Apple iPhone XR',
|
||||
browser: 'Chrome on iPhone',
|
||||
recentActivity: '12, Jan 2020 8:29',
|
||||
browserIcon: <i className='tabler-device-mobile text-[22px] text-error' />
|
||||
}
|
||||
]
|
||||
|
||||
const RecentDevice = () => {
|
||||
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-4'>
|
||||
{device.browserIcon}
|
||||
<Typography color='text.primary'>{device.browser}</Typography>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<Typography>{device.device}</Typography>
|
||||
</td>
|
||||
<td>
|
||||
<Typography>{device.location}</Typography>
|
||||
</td>
|
||||
<td>
|
||||
<Typography>{device.recentActivity}</Typography>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export default RecentDevice
|
||||
@@ -0,0 +1,42 @@
|
||||
'use client'
|
||||
|
||||
// MUI Imports
|
||||
import Card from '@mui/material/Card'
|
||||
import CardHeader from '@mui/material/CardHeader'
|
||||
import CardContent from '@mui/material/CardContent'
|
||||
import InputLabel from '@mui/material/InputLabel'
|
||||
import Typography from '@mui/material/Typography'
|
||||
|
||||
// Component Imports
|
||||
import CustomIconButton from '@core/components/mui/IconButton'
|
||||
import CustomTextField from '@core/components/mui/TextField'
|
||||
|
||||
const TwoStepVerification = () => {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader title='Two-step verification' subheader='Keep your account secure with authentication step.' />
|
||||
<CardContent>
|
||||
<Typography htmlFor='sms' component={InputLabel} className='inline-flex font-medium mbe-1' color='text.primary'>
|
||||
SMS
|
||||
</Typography>
|
||||
<div className='flex items-center mbe-4 gap-5'>
|
||||
<CustomTextField id='sms' placeholder='+1(968) 819-2547' fullWidth />
|
||||
<div className='flex items-center gap-1'>
|
||||
<CustomIconButton color='secondary'>
|
||||
<i className='tabler-edit' />
|
||||
</CustomIconButton>
|
||||
<CustomIconButton color='secondary'>
|
||||
<i className='tabler-user-plus' />
|
||||
</CustomIconButton>
|
||||
</div>
|
||||
</div>
|
||||
<Typography>
|
||||
Two-factor authentication adds an additional layer of security to your account by requiring more than just a
|
||||
password to log in. <span className='text-primary'>Learn more.</span>
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export default TwoStepVerification
|
||||
@@ -0,0 +1,25 @@
|
||||
// MUI Imports
|
||||
import Grid from '@mui/material/Grid2'
|
||||
|
||||
// Component Imports
|
||||
import ChangePassword from './ChangePassword'
|
||||
import TwoStepVerification from './TwoStepVerification'
|
||||
import RecentDevice from './RecentDevice'
|
||||
|
||||
const SecurityTab = () => {
|
||||
return (
|
||||
<Grid container spacing={6}>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<ChangePassword />
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<TwoStepVerification />
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<RecentDevice />
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
export default SecurityTab
|
||||
Reference in New Issue
Block a user