Separate Client Portal & Dashboard

This commit is contained in:
2022-05-23 10:38:16 +07:00
parent f2e84e6244
commit 89bb57f357
569 changed files with 60252 additions and 280 deletions

View File

@@ -0,0 +1,69 @@
// @mui
import { alpha, styled } from '@mui/material/styles';
import { Box, Grid, RadioGroup, CardActionArea } from '@mui/material';
// hooks
import useSettings from '../../hooks/useSettings';
//
import { BoxMask } from '.';
// ----------------------------------------------------------------------
const BoxStyle = styled(CardActionArea)(({ theme }) => ({
height: 48,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: theme.palette.text.disabled,
border: `solid 1px ${theme.palette.grey[500_12]}`,
borderRadius: Number(theme.shape.borderRadius) * 1.25,
}));
// ----------------------------------------------------------------------
export default function SettingColorPresets() {
const { themeColorPresets, onChangeColor, colorOption } = useSettings();
return (
<RadioGroup name="themeColorPresets" value={themeColorPresets} onChange={onChangeColor}>
<Grid dir="ltr" container spacing={1.5}>
{colorOption.map((color) => {
const colorName = color.name;
const colorValue = color.value;
const isSelected = themeColorPresets === colorName;
return (
<Grid key={colorName} item xs={4}>
<BoxStyle
sx={{
...(isSelected && {
bgcolor: alpha(colorValue, 0.08),
border: `solid 2px ${colorValue}`,
boxShadow: `inset 0 4px 8px 0 ${alpha(colorValue, 0.24)}`,
}),
}}
>
<Box
sx={{
width: 24,
height: 14,
borderRadius: '50%',
bgcolor: colorValue,
transform: 'rotate(-45deg)',
transition: (theme) =>
theme.transitions.create('all', {
easing: theme.transitions.easing.easeInOut,
duration: theme.transitions.duration.shorter,
}),
...(isSelected && { transform: 'none' }),
}}
/>
<BoxMask value={colorName} />
</BoxStyle>
</Grid>
);
})}
</Grid>
</RadioGroup>
);
}

View File

@@ -0,0 +1,56 @@
// @mui
import { styled } from '@mui/material/styles';
import { Grid, RadioGroup, CardActionArea } from '@mui/material';
// hooks
import useSettings from '../../hooks/useSettings';
//
import Iconify from '../Iconify';
import { BoxMask } from '.';
// ----------------------------------------------------------------------
const BoxStyle = styled(CardActionArea)(({ theme }) => ({
height: 72,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: theme.palette.text.disabled,
border: `solid 1px ${theme.palette.grey[500_12]}`,
borderRadius: Number(theme.shape.borderRadius) * 1.25,
}));
// ----------------------------------------------------------------------
export default function SettingDirection() {
const { themeDirection, onChangeDirection } = useSettings();
return (
<RadioGroup name="themeDirection" value={themeDirection} onChange={onChangeDirection}>
<Grid dir="ltr" container spacing={2.5}>
{['ltr', 'rtl'].map((direction, index) => {
const isSelected = themeDirection === direction;
return (
<Grid key={direction} item xs={6}>
<BoxStyle
sx={{
...(isSelected && {
color: 'primary.main',
boxShadow: (theme) => theme.customShadows.z20,
}),
}}
>
<Iconify
icon={index === 0 ? 'ph:align-left-duotone' : 'ph:align-right-duotone'}
width={28}
height={28}
/>
<BoxMask value={direction} />
</BoxStyle>
</Grid>
);
})}
</Grid>
</RadioGroup>
);
}

View File

@@ -0,0 +1,42 @@
import { useState } from 'react';
// @mui
import { alpha } from '@mui/material/styles';
import { Button } from '@mui/material';
// components
import Iconify from '../Iconify';
// ----------------------------------------------------------------------
export default function SettingFullscreen() {
const [fullscreen, setFullscreen] = useState(false);
const toggleFullScreen = () => {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen();
setFullscreen(true);
} else if (document.exitFullscreen) {
document.exitFullscreen();
setFullscreen(false);
}
};
return (
<Button
fullWidth
size="large"
variant="outlined"
color={fullscreen ? 'primary' : 'inherit'}
startIcon={<Iconify icon={fullscreen ? 'ic:round-fullscreen-exit' : 'ic:round-fullscreen'} />}
onClick={toggleFullScreen}
sx={{
fontSize: 14,
...(fullscreen && {
bgcolor: (theme) =>
alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity),
}),
}}
>
{fullscreen ? 'Exit Fullscreen' : 'Fullscreen'}
</Button>
);
}

View File

@@ -0,0 +1,138 @@
// @mui
import { styled, alpha } from '@mui/material/styles';
import { Grid, RadioGroup, CardActionArea, Box, Stack } from '@mui/material';
// hooks
import useSettings from '../../hooks/useSettings';
//
import { BoxMask } from '.';
// ----------------------------------------------------------------------
const BoxStyle = styled(CardActionArea)(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
padding: theme.spacing(1.5),
color: theme.palette.text.disabled,
border: `solid 1px ${theme.palette.grey[500_12]}`,
borderRadius: Number(theme.shape.borderRadius) * 1.25,
}));
// ----------------------------------------------------------------------
export default function SettingLayout() {
const { themeLayout, onChangeLayout } = useSettings();
return (
<RadioGroup name="themeLayout" value={themeLayout} onChange={onChangeLayout}>
<Grid dir="ltr" container spacing={2.5}>
{['horizontal', 'vertical'].map((layout) => {
const isSelected = themeLayout === layout;
const isVertical = layout === 'vertical';
return (
<Grid key={layout} item xs={6}>
<BoxStyle
sx={{
...(isSelected && {
color: 'primary.main',
boxShadow: (theme) => theme.customShadows.z20,
}),
}}
>
{isVertical ? (
<VerticalBox isSelected={isSelected} />
) : (
<HorizontalBox isSelected={isSelected} />
)}
<BoxMask value={layout} />
</BoxStyle>
</Grid>
);
})}
</Grid>
</RadioGroup>
);
}
// ----------------------------------------------------------------------
type LayoutBoxProps = {
isSelected: boolean;
};
const style = {
width: 1,
height: 32,
borderRadius: 0.5,
};
function VerticalBox({ isSelected }: LayoutBoxProps) {
return (
<>
<Box
sx={{
...style,
mb: 0.75,
height: 12,
bgcolor: (theme) => alpha(theme.palette.text.disabled, 0.72),
...(isSelected && {
bgcolor: (theme) => alpha(theme.palette.primary.main, 0.72),
}),
}}
/>
<Box
sx={{
...style,
border: (theme) => `dashed 1px ${theme.palette.divider}`,
bgcolor: (theme) => alpha(theme.palette.text.disabled, 0.08),
...(isSelected && {
border: (theme) => `dashed 1px ${theme.palette.primary.main}`,
bgcolor: (theme) => alpha(theme.palette.primary.main, 0.16),
}),
}}
/>
</>
);
}
function HorizontalBox({ isSelected }: LayoutBoxProps) {
return (
<>
<Box
sx={{
...style,
mb: 0.75,
height: 12,
bgcolor: (theme) => alpha(theme.palette.text.disabled, 0.72),
...(isSelected && {
bgcolor: (theme) => alpha(theme.palette.primary.main, 0.72),
}),
}}
/>
<Stack width={1} direction="row" justifyContent="space-between">
<Box
sx={{
...style,
width: 20,
bgcolor: (theme) => alpha(theme.palette.text.disabled, 0.32),
...(isSelected && {
bgcolor: (theme) => alpha(theme.palette.primary.main, 0.32),
}),
}}
/>
<Box
sx={{
...style,
width: `calc(100% - 26px)`,
border: (theme) => `dashed 1px ${theme.palette.divider}`,
bgcolor: (theme) => alpha(theme.palette.text.disabled, 0.08),
...(isSelected && {
border: (theme) => `dashed 1px ${theme.palette.primary.main}`,
bgcolor: (theme) => alpha(theme.palette.primary.main, 0.16),
}),
}}
/>
</Stack>
</>
);
}

View File

@@ -0,0 +1,57 @@
// @mui
import { styled } from '@mui/material/styles';
import { Grid, RadioGroup, CardActionArea } from '@mui/material';
// hooks
import useSettings from '../../hooks/useSettings';
//
import Iconify from '../Iconify';
import { BoxMask } from '.';
// ----------------------------------------------------------------------
const BoxStyle = styled(CardActionArea)(({ theme }) => ({
height: 72,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: theme.palette.text.disabled,
border: `solid 1px ${theme.palette.grey[500_12]}`,
borderRadius: Number(theme.shape.borderRadius) * 1.25,
}));
// ----------------------------------------------------------------------
export default function SettingMode() {
const { themeMode, onChangeMode } = useSettings();
return (
<RadioGroup name="themeMode" value={themeMode} onChange={onChangeMode}>
<Grid dir="ltr" container spacing={2.5}>
{['light', 'dark'].map((mode, index) => {
const isSelected = themeMode === mode;
return (
<Grid key={mode} item xs={6}>
<BoxStyle
sx={{
bgcolor: mode === 'light' ? 'common.white' : 'grey.800',
...(isSelected && {
color: 'primary.main',
boxShadow: (theme) => theme.customShadows.z20,
}),
}}
>
<Iconify
icon={index === 0 ? 'ph:sun-duotone' : 'ph:moon-duotone'}
width={28}
height={28}
/>
<BoxMask value={mode} />
</BoxStyle>
</Grid>
);
})}
</Grid>
</RadioGroup>
);
}

View File

@@ -0,0 +1,69 @@
// @mui
import { styled } from '@mui/material/styles';
import { CardActionArea, Stack } from '@mui/material';
// hooks
import useSettings from '../../hooks/useSettings';
//
import Iconify from '../Iconify';
// ----------------------------------------------------------------------
const BoxStyle = styled(CardActionArea)(({ theme }) => ({
padding: theme.spacing(2),
color: theme.palette.text.disabled,
border: `solid 1px ${theme.palette.grey[500_12]}`,
backgroundColor: theme.palette.background.neutral,
borderRadius: Number(theme.shape.borderRadius) * 1.25,
}));
// ----------------------------------------------------------------------
export default function SettingStretch() {
const { themeStretch, onToggleStretch } = useSettings();
const ICON_SIZE = {
width: themeStretch ? 24 : 18,
height: themeStretch ? 24 : 18,
};
return (
<BoxStyle
onClick={onToggleStretch}
sx={{
...(themeStretch && {
color: (theme) => theme.palette.primary.main,
}),
}}
>
<Stack
direction="row"
alignItems="center"
justifyContent="space-between"
sx={{
px: 1,
mx: 'auto',
width: 0.5,
height: 40,
borderRadius: 1,
color: 'action.active',
bgcolor: 'background.default',
boxShadow: (theme) => theme.customShadows.z12,
transition: (theme) => theme.transitions.create('width'),
...(themeStretch && {
width: 1,
color: 'primary.main',
}),
}}
>
<Iconify
icon={themeStretch ? 'eva:arrow-ios-back-fill' : 'eva:arrow-ios-forward-fill'}
{...ICON_SIZE}
/>
<Iconify
icon={themeStretch ? 'eva:arrow-ios-forward-fill' : 'eva:arrow-ios-back-fill'}
{...ICON_SIZE}
/>
</Stack>
</BoxStyle>
);
}

View File

@@ -0,0 +1,69 @@
// @mui
import { alpha, styled } from '@mui/material/styles';
import { Tooltip } from '@mui/material';
// utils
import cssStyles from '../../utils/cssStyles';
//
import Iconify from '../Iconify';
import { IconButtonAnimate } from '../animate';
// ----------------------------------------------------------------------
const RootStyle = styled('span')(({ theme }) => ({
...cssStyles(theme).bgBlur({ opacity: 0.64 }),
right: 0,
top: '50%',
position: 'fixed',
marginTop: theme.spacing(-3),
padding: theme.spacing(0.5),
zIndex: theme.zIndex.drawer + 2,
borderRadius: '24px 0 20px 24px',
boxShadow: `-12px 12px 32px -4px ${alpha(
theme.palette.mode === 'light' ? theme.palette.grey[600] : theme.palette.common.black,
0.36
)}`,
}));
const DotStyle = styled('span')(({ theme }) => ({
top: 8,
width: 8,
height: 8,
right: 10,
borderRadius: '50%',
position: 'absolute',
backgroundColor: theme.palette.error.main,
}));
// ----------------------------------------------------------------------
type Props = {
open: boolean;
notDefault: boolean;
onToggle: VoidFunction;
};
export default function ToggleButton({ notDefault, open, onToggle }: Props) {
return (
<RootStyle>
{notDefault && !open && <DotStyle />}
<Tooltip title="Settings" placement="left">
<IconButtonAnimate
color="inherit"
onClick={onToggle}
sx={{
p: 1.25,
transition: (theme) => theme.transitions.create('all'),
'&:hover': {
color: 'primary.main',
bgcolor: (theme) =>
alpha(theme.palette.primary.main, theme.palette.action.hoverOpacity),
},
}}
>
<Iconify icon="eva:options-2-fill" width={20} height={20} />
</IconButtonAnimate>
</Tooltip>
</RootStyle>
);
}

View File

@@ -0,0 +1,189 @@
import { AnimatePresence, m } from 'framer-motion';
import { useState, useEffect } from 'react';
// @mui
import { alpha, styled } from '@mui/material/styles';
import { Backdrop, Divider, Typography, Stack, FormControlLabel, Radio } from '@mui/material';
// hooks
import useSettings from '../../hooks/useSettings';
// utils
import cssStyles from '../../utils/cssStyles';
// config
import { NAVBAR, defaultSettings } from '../../config';
//
import Iconify from '../Iconify';
import Scrollbar from '../Scrollbar';
import { IconButtonAnimate, varFade } from '../animate';
//
import ToggleButton from './ToggleButton';
import SettingMode from './SettingMode';
import SettingLayout from './SettingLayout';
import SettingStretch from './SettingStretch';
import SettingDirection from './SettingDirection';
import SettingFullscreen from './SettingFullscreen';
import SettingColorPresets from './SettingColorPresets';
// ----------------------------------------------------------------------
const RootStyle = styled(m.div)(({ theme }) => ({
...cssStyles(theme).bgBlur({ color: theme.palette.background.paper, opacity: 0.92 }),
top: 0,
right: 0,
bottom: 0,
display: 'flex',
position: 'fixed',
overflow: 'hidden',
width: NAVBAR.BASE_WIDTH,
flexDirection: 'column',
margin: theme.spacing(2),
paddingBottom: theme.spacing(3),
zIndex: theme.zIndex.drawer + 3,
borderRadius: Number(theme.shape.borderRadius) * 1.5,
boxShadow: `-24px 12px 32px -4px ${alpha(
theme.palette.mode === 'light' ? theme.palette.grey[500] : theme.palette.common.black,
0.16
)}`,
}));
// ----------------------------------------------------------------------
export default function Settings() {
const {
themeMode,
themeDirection,
themeColorPresets,
themeStretch,
themeLayout,
onResetSetting,
} = useSettings();
const [open, setOpen] = useState(false);
const notDefault =
themeMode !== defaultSettings.themeMode ||
themeDirection !== defaultSettings.themeDirection ||
themeColorPresets !== defaultSettings.themeColorPresets ||
themeLayout !== defaultSettings.themeLayout ||
themeStretch !== defaultSettings.themeStretch;
const varSidebar =
themeDirection !== 'rtl'
? varFade({
distance: NAVBAR.BASE_WIDTH,
durationIn: 0.32,
durationOut: 0.32,
}).inRight
: varFade({
distance: NAVBAR.BASE_WIDTH,
durationIn: 0.32,
durationOut: 0.32,
}).inLeft;
useEffect(() => {
if (open) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = 'unset';
}
}, [open]);
const handleToggle = () => {
setOpen((prev) => !prev);
};
const handleClose = () => {
setOpen(false);
};
return (
<>
<Backdrop
open={open}
onClick={handleClose}
sx={{ background: 'transparent', zIndex: (theme) => theme.zIndex.drawer + 1 }}
/>
{!open && <ToggleButton open={open} notDefault={notDefault} onToggle={handleToggle} />}
<AnimatePresence>
{open && (
<>
<RootStyle {...varSidebar}>
<Stack
direction="row"
alignItems="center"
justifyContent="space-between"
sx={{ py: 2, pr: 1, pl: 2.5 }}
>
<Typography variant="subtitle1">Settings</Typography>
<div>
<IconButtonAnimate onClick={onResetSetting}>
<Iconify icon={'ic:round-refresh'} width={20} height={20} />
</IconButtonAnimate>
<IconButtonAnimate onClick={handleClose}>
<Iconify icon={'eva:close-fill'} width={20} height={20} />
</IconButtonAnimate>
</div>
</Stack>
<Divider sx={{ borderStyle: 'dashed' }} />
<Scrollbar sx={{ flexGrow: 1 }}>
<Stack spacing={3} sx={{ p: 3 }}>
<Stack spacing={1.5}>
<Typography variant="subtitle2">Mode</Typography>
<SettingMode />
</Stack>
<Stack spacing={1.5}>
<Typography variant="subtitle2">Direction</Typography>
<SettingDirection />
</Stack>
<Stack spacing={1.5}>
<Typography variant="subtitle2">Layout</Typography>
<SettingLayout />
</Stack>
<Stack spacing={1.5}>
<Typography variant="subtitle2">Presets</Typography>
<SettingColorPresets />
</Stack>
<Stack spacing={1.5}>
<Typography variant="subtitle2">Stretch</Typography>
<SettingStretch />
</Stack>
<SettingFullscreen />
</Stack>
</Scrollbar>
</RootStyle>
</>
)}
</AnimatePresence>
</>
);
}
// ----------------------------------------------------------------------
type Props = {
value: string;
};
export function BoxMask({ value }: Props) {
return (
<FormControlLabel
label=""
value={value}
control={<Radio sx={{ display: 'none' }} />}
sx={{
m: 0,
top: 0,
right: 0,
bottom: 0,
left: 0,
position: 'absolute',
}}
/>
);
}

View File

@@ -0,0 +1,45 @@
// ----------------------------------------------------------------------
export type ThemeMode = 'light' | 'dark';
export type ThemeDirection = 'rtl' | 'ltr';
export type ThemeColorPresets = 'default' | 'purple' | 'cyan' | 'blue' | 'orange' | 'red';
export type ThemeLayout = 'vertical' | 'horizontal';
export type ThemeStretch = boolean;
type ColorVariants = {
name: string;
lighter: string;
light: string;
main: string;
dark: string;
darker: string;
contrastText: string;
};
export type SettingsValueProps = {
themeMode: ThemeMode;
themeDirection: ThemeDirection;
themeColorPresets: ThemeColorPresets;
themeStretch: ThemeStretch;
themeLayout: ThemeLayout;
};
export type SettingsContextProps = {
themeMode: ThemeMode;
themeDirection: ThemeDirection;
themeColorPresets: ThemeColorPresets;
themeLayout: ThemeLayout;
themeStretch: boolean;
setColor: ColorVariants;
colorOption: {
name: string;
value: string;
}[];
onToggleMode: VoidFunction;
onToggleStretch: VoidFunction;
onResetSetting: VoidFunction;
onChangeMode: (event: React.ChangeEvent<HTMLInputElement>) => void;
onChangeDirection: (event: React.ChangeEvent<HTMLInputElement>) => void;
onChangeColor: (event: React.ChangeEvent<HTMLInputElement>) => void;
onChangeLayout: (event: React.ChangeEvent<HTMLInputElement>) => void;
};