Compare commits
4 Commits
01a8d2ca02
...
dashboard/
Author | SHA1 | Date | |
---|---|---|---|
![]() |
39a7d32fcd | ||
![]() |
85d1ecdc90 | ||
![]() |
b26e1ff167 | ||
![]() |
de415a37bc |
@@ -112,9 +112,15 @@ export function ContactsPopover({ anchorEl, onClose, open = false }: ContactsPop
|
|||||||
<Typography variant="h6">Contacts</Typography>
|
<Typography variant="h6">Contacts</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ maxHeight: '400px', overflowY: 'auto', px: 1, pb: 2 }}>
|
<Box sx={{ maxHeight: '400px', overflowY: 'auto', px: 1, pb: 2 }}>
|
||||||
<List disablePadding sx={{ '& .MuiListItemButton-root': { borderRadius: 1 } }}>
|
<List
|
||||||
|
disablePadding
|
||||||
|
sx={{ '& .MuiListItemButton-root': { borderRadius: 1 } }}
|
||||||
|
>
|
||||||
{contacts.map((contact) => (
|
{contacts.map((contact) => (
|
||||||
<ListItem disablePadding key={contact.id}>
|
<ListItem
|
||||||
|
disablePadding
|
||||||
|
key={contact.id}
|
||||||
|
>
|
||||||
<ListItemButton>
|
<ListItemButton>
|
||||||
<ListItemAvatar>
|
<ListItemAvatar>
|
||||||
<Avatar src={contact.avatar} />
|
<Avatar src={contact.avatar} />
|
||||||
@@ -122,14 +128,28 @@ export function ContactsPopover({ anchorEl, onClose, open = false }: ContactsPop
|
|||||||
<ListItemText
|
<ListItemText
|
||||||
disableTypography
|
disableTypography
|
||||||
primary={
|
primary={
|
||||||
<Link color="text.primary" noWrap underline="none" variant="subtitle2">
|
<Link
|
||||||
|
color="text.primary"
|
||||||
|
noWrap
|
||||||
|
underline="none"
|
||||||
|
variant="subtitle2"
|
||||||
|
>
|
||||||
{contact.name}
|
{contact.name}
|
||||||
</Link>
|
</Link>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{contact.status !== 'offline' ? <Presence size="small" status={contact.status} /> : null}
|
{contact.status !== 'offline' ? (
|
||||||
|
<Presence
|
||||||
|
size="small"
|
||||||
|
status={contact.status}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
{contact.status === 'offline' && Boolean(contact.lastActivity) ? (
|
{contact.status === 'offline' && Boolean(contact.lastActivity) ? (
|
||||||
<Typography color="text.secondary" sx={{ whiteSpace: 'nowrap' }} variant="caption">
|
<Typography
|
||||||
|
color="text.secondary"
|
||||||
|
sx={{ whiteSpace: 'nowrap' }}
|
||||||
|
variant="caption"
|
||||||
|
>
|
||||||
{dayjs(contact.lastActivity).fromNow()}
|
{dayjs(contact.lastActivity).fromNow()}
|
||||||
</Typography>
|
</Typography>
|
||||||
) : null}
|
) : null}
|
||||||
|
@@ -34,6 +34,7 @@ export function HorizontalLayout({ children }: HorizontalLayoutProps): React.JSX
|
|||||||
color={settings.navColor}
|
color={settings.navColor}
|
||||||
items={layoutConfig.navItems}
|
items={layoutConfig.navItems}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
component="main"
|
component="main"
|
||||||
sx={{
|
sx={{
|
||||||
|
@@ -36,15 +36,15 @@ import { Logo } from '@/components/core/logo';
|
|||||||
import { SearchDialog } from '@/components/dashboard/layout/search-dialog';
|
import { SearchDialog } from '@/components/dashboard/layout/search-dialog';
|
||||||
import type { ColorScheme } from '@/styles/theme/types';
|
import type { ColorScheme } from '@/styles/theme/types';
|
||||||
|
|
||||||
import { ContactsPopover } from '../contacts-popover';
|
import { ContactsPopover } from '../../contacts-popover';
|
||||||
import { languageFlags, LanguagePopover } from '../language-popover';
|
import { languageFlags, LanguagePopover } from '../../language-popover';
|
||||||
import type { Language } from '../language-popover';
|
import type { Language } from '../../language-popover';
|
||||||
import { MobileNav } from '../mobile-nav';
|
import { MobileNav } from '../../mobile-nav';
|
||||||
import { icons } from '../nav-icons';
|
import { icons } from '../../nav-icons';
|
||||||
import { NotificationsPopover } from '../notifications-popover';
|
import { NotificationsPopover } from '../../notifications-popover';
|
||||||
import { UserPopover } from '../user-popover/user-popover';
|
import { UserPopover } from '../../user-popover/user-popover';
|
||||||
import { WorkspacesSwitch } from '../workspaces-switch';
|
import { WorkspacesSwitch } from '../../workspaces-switch';
|
||||||
import { navColorStyles } from './styles';
|
import { navColorStyles } from '../styles';
|
||||||
|
|
||||||
const logoColors = {
|
const logoColors = {
|
||||||
dark: { blend_in: 'light', discrete: 'light', evident: 'light' },
|
dark: { blend_in: 'light', discrete: 'light', evident: 'light' },
|
||||||
|
@@ -9,9 +9,12 @@ import type { User } from '@/types/user';
|
|||||||
import { usePopover } from '@/hooks/use-popover';
|
import { usePopover } from '@/hooks/use-popover';
|
||||||
|
|
||||||
import { UserPopover } from '../../user-popover/user-popover';
|
import { UserPopover } from '../../user-popover/user-popover';
|
||||||
|
import { useUser } from '@/hooks/use-user';
|
||||||
|
import getImageUrlFromFile from '@/lib/get-image-url-from-file.ts';
|
||||||
// import { NotificationsButton } from './notifications-button';
|
// import { NotificationsButton } from './notifications-button';
|
||||||
|
|
||||||
const user = {
|
// TODO:remove me
|
||||||
|
const user1 = {
|
||||||
id: 'USR-000',
|
id: 'USR-000',
|
||||||
name: 'Sofia Rivers',
|
name: 'Sofia Rivers',
|
||||||
avatar: '/assets/avatar.png',
|
avatar: '/assets/avatar.png',
|
||||||
@@ -20,6 +23,9 @@ const user = {
|
|||||||
|
|
||||||
export function UserButton(): React.JSX.Element {
|
export function UserButton(): React.JSX.Element {
|
||||||
const popover = usePopover<HTMLButtonElement>();
|
const popover = usePopover<HTMLButtonElement>();
|
||||||
|
const { user, error, isLoading } = useUser();
|
||||||
|
|
||||||
|
if (!user) return <>loading</>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
@@ -44,7 +50,7 @@ export function UserButton(): React.JSX.Element {
|
|||||||
}}
|
}}
|
||||||
variant="dot"
|
variant="dot"
|
||||||
>
|
>
|
||||||
<Avatar src={user.avatar} />
|
<Avatar src={getImageUrlFromFile(user.collectionId, user.id, user.avatar)} />
|
||||||
</Badge>
|
</Badge>
|
||||||
</Box>
|
</Box>
|
||||||
<UserPopover
|
<UserPopover
|
||||||
|
@@ -0,0 +1,98 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import RouterLink from 'next/link';
|
||||||
|
import { usePathname } from 'next/navigation';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import Chip from '@mui/material/Chip';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import { ArrowSquareOut as ArrowSquareOutIcon } from '@phosphor-icons/react/dist/ssr/ArrowSquareOut';
|
||||||
|
import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/CaretDown';
|
||||||
|
import { CaretRight as CaretRightIcon } from '@phosphor-icons/react/dist/ssr/CaretRight';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import type { NavItemConfig } from '@/types/nav';
|
||||||
|
import type { NavColor } from '@/types/settings';
|
||||||
|
import { paths } from '@/paths';
|
||||||
|
import { isNavItemActive } from '@/lib/is-nav-item-active';
|
||||||
|
import { useSettings } from '@/hooks/use-settings';
|
||||||
|
import { Logo } from '@/components/core/logo';
|
||||||
|
import type { ColorScheme } from '@/styles/theme/types';
|
||||||
|
|
||||||
|
import { icons } from '../../nav-icons';
|
||||||
|
import { WorkspacesSwitch } from '../../workspaces-switch';
|
||||||
|
import { navColorStyles } from '../styles';
|
||||||
|
import { RenderNavGroups } from './render-nav-groups';
|
||||||
|
|
||||||
|
const logoColors = {
|
||||||
|
dark: { blend_in: 'light', discrete: 'light', evident: 'light' },
|
||||||
|
light: { blend_in: 'dark', discrete: 'dark', evident: 'light' },
|
||||||
|
} as Record<ColorScheme, Record<NavColor, 'dark' | 'light'>>;
|
||||||
|
|
||||||
|
export interface SideNavProps {
|
||||||
|
color?: NavColor;
|
||||||
|
items?: NavItemConfig[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SideNav({ color = 'evident', items = [] }: SideNavProps): React.JSX.Element {
|
||||||
|
const pathname = usePathname();
|
||||||
|
|
||||||
|
const {
|
||||||
|
settings: { colorScheme = 'light' },
|
||||||
|
} = useSettings();
|
||||||
|
|
||||||
|
const styles = navColorStyles[colorScheme][color];
|
||||||
|
const logoColor = logoColors[colorScheme][color];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
...styles,
|
||||||
|
bgcolor: 'var(--SideNav-background)',
|
||||||
|
borderRight: 'var(--SideNav-border)',
|
||||||
|
color: 'var(--SideNav-color)',
|
||||||
|
display: { xs: 'none', lg: 'flex' },
|
||||||
|
flexDirection: 'column',
|
||||||
|
height: '100%',
|
||||||
|
left: 0,
|
||||||
|
position: 'fixed',
|
||||||
|
top: 0,
|
||||||
|
width: 'var(--SideNav-width)',
|
||||||
|
zIndex: 'var(--SideNav-zIndex)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Stack
|
||||||
|
spacing={2}
|
||||||
|
sx={{ p: 2 }}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<Box
|
||||||
|
component={RouterLink}
|
||||||
|
href={paths.home}
|
||||||
|
sx={{ display: 'inline-flex' }}
|
||||||
|
>
|
||||||
|
<Logo
|
||||||
|
color={logoColor}
|
||||||
|
height={32}
|
||||||
|
width={122}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</div>
|
||||||
|
<WorkspacesSwitch />
|
||||||
|
</Stack>
|
||||||
|
<Box
|
||||||
|
component="nav"
|
||||||
|
sx={{
|
||||||
|
flex: '1 1 auto',
|
||||||
|
overflowY: 'auto',
|
||||||
|
p: 2,
|
||||||
|
scrollbarWidth: 'none',
|
||||||
|
'&::-webkit-scrollbar': { display: 'none' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{RenderNavGroups({ items, pathname })}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
@@ -2,10 +2,8 @@
|
|||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import RouterLink from 'next/link';
|
import RouterLink from 'next/link';
|
||||||
import { usePathname } from 'next/navigation';
|
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import Chip from '@mui/material/Chip';
|
import Chip from '@mui/material/Chip';
|
||||||
import Stack from '@mui/material/Stack';
|
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import { ArrowSquareOut as ArrowSquareOutIcon } from '@phosphor-icons/react/dist/ssr/ArrowSquareOut';
|
import { ArrowSquareOut as ArrowSquareOutIcon } from '@phosphor-icons/react/dist/ssr/ArrowSquareOut';
|
||||||
import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/CaretDown';
|
import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/CaretDown';
|
||||||
@@ -13,136 +11,9 @@ import { CaretRight as CaretRightIcon } from '@phosphor-icons/react/dist/ssr/Car
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import type { NavItemConfig } from '@/types/nav';
|
import type { NavItemConfig } from '@/types/nav';
|
||||||
import type { NavColor } from '@/types/settings';
|
|
||||||
import { paths } from '@/paths';
|
|
||||||
import { isNavItemActive } from '@/lib/is-nav-item-active';
|
import { isNavItemActive } from '@/lib/is-nav-item-active';
|
||||||
import { useSettings } from '@/hooks/use-settings';
|
|
||||||
import { Logo } from '@/components/core/logo';
|
|
||||||
import type { ColorScheme } from '@/styles/theme/types';
|
|
||||||
|
|
||||||
import { icons } from '../nav-icons';
|
import { icons } from '../../nav-icons';
|
||||||
import { WorkspacesSwitch } from '../workspaces-switch';
|
|
||||||
import { navColorStyles } from './styles';
|
|
||||||
|
|
||||||
const logoColors = {
|
|
||||||
dark: { blend_in: 'light', discrete: 'light', evident: 'light' },
|
|
||||||
light: { blend_in: 'dark', discrete: 'dark', evident: 'light' },
|
|
||||||
} as Record<ColorScheme, Record<NavColor, 'dark' | 'light'>>;
|
|
||||||
|
|
||||||
export interface SideNavProps {
|
|
||||||
color?: NavColor;
|
|
||||||
items?: NavItemConfig[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SideNav({ color = 'evident', items = [] }: SideNavProps): React.JSX.Element {
|
|
||||||
const pathname = usePathname();
|
|
||||||
|
|
||||||
const {
|
|
||||||
settings: { colorScheme = 'light' },
|
|
||||||
} = useSettings();
|
|
||||||
|
|
||||||
const styles = navColorStyles[colorScheme][color];
|
|
||||||
const logoColor = logoColors[colorScheme][color];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
...styles,
|
|
||||||
bgcolor: 'var(--SideNav-background)',
|
|
||||||
borderRight: 'var(--SideNav-border)',
|
|
||||||
color: 'var(--SideNav-color)',
|
|
||||||
display: { xs: 'none', lg: 'flex' },
|
|
||||||
flexDirection: 'column',
|
|
||||||
height: '100%',
|
|
||||||
left: 0,
|
|
||||||
position: 'fixed',
|
|
||||||
top: 0,
|
|
||||||
width: 'var(--SideNav-width)',
|
|
||||||
zIndex: 'var(--SideNav-zIndex)',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Stack spacing={2} sx={{ p: 2 }}>
|
|
||||||
<div>
|
|
||||||
<Box component={RouterLink} href={paths.home} sx={{ display: 'inline-flex' }}>
|
|
||||||
<Logo color={logoColor} height={32} width={122} />
|
|
||||||
</Box>
|
|
||||||
</div>
|
|
||||||
<WorkspacesSwitch />
|
|
||||||
</Stack>
|
|
||||||
<Box
|
|
||||||
component="nav"
|
|
||||||
sx={{
|
|
||||||
flex: '1 1 auto',
|
|
||||||
overflowY: 'auto',
|
|
||||||
p: 2,
|
|
||||||
scrollbarWidth: 'none',
|
|
||||||
'&::-webkit-scrollbar': { display: 'none' },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{RenderNavGroups({ items, pathname })}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function RenderNavGroups({ items, pathname }: { items: NavItemConfig[]; pathname: string }): React.JSX.Element {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const children = items.reduce((acc: React.ReactNode[], curr: NavItemConfig): React.ReactNode[] => {
|
|
||||||
acc.push(
|
|
||||||
<Stack component="li" key={curr.key} spacing={1.5}>
|
|
||||||
{curr.title ? (
|
|
||||||
<div>
|
|
||||||
<Typography sx={{ color: 'var(--NavGroup-title-color)', fontSize: '0.875rem', fontWeight: 500 }}>
|
|
||||||
{t(curr.title)}
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
<div>{renderNavItems({ depth: 0, items: curr.items, pathname })}</div>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Stack component="ul" spacing={2} sx={{ listStyle: 'none', m: 0, p: 0 }}>
|
|
||||||
{children}
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderNavItems({
|
|
||||||
depth = 0,
|
|
||||||
items = [],
|
|
||||||
pathname,
|
|
||||||
}: {
|
|
||||||
depth: number;
|
|
||||||
items?: NavItemConfig[];
|
|
||||||
pathname: string;
|
|
||||||
}): React.JSX.Element {
|
|
||||||
const children = items.reduce((acc: React.ReactNode[], curr: NavItemConfig): React.ReactNode[] => {
|
|
||||||
const { items: childItems, key, ...item } = curr;
|
|
||||||
|
|
||||||
const forceOpen = childItems
|
|
||||||
? Boolean(childItems.find((childItem) => childItem.href && pathname.startsWith(childItem.href)))
|
|
||||||
: false;
|
|
||||||
|
|
||||||
acc.push(
|
|
||||||
<NavItem depth={depth} forceOpen={forceOpen} key={key} pathname={pathname} {...item}>
|
|
||||||
{childItems ? renderNavItems({ depth: depth + 1, pathname, items: childItems }) : null}
|
|
||||||
</NavItem>
|
|
||||||
);
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Stack component="ul" data-depth={depth} spacing={1} sx={{ listStyle: 'none', m: 0, p: 0 }}>
|
|
||||||
{children}
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface NavItemProps extends Omit<NavItemConfig, 'items'> {
|
interface NavItemProps extends Omit<NavItemConfig, 'items'> {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
@@ -151,7 +22,7 @@ interface NavItemProps extends Omit<NavItemConfig, 'items'> {
|
|||||||
pathname: string;
|
pathname: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function NavItem({
|
export function NavItem({
|
||||||
children,
|
children,
|
||||||
depth,
|
depth,
|
||||||
disabled,
|
disabled,
|
||||||
@@ -173,7 +44,11 @@ function NavItem({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box component="li" data-depth={depth} sx={{ userSelect: 'none' }}>
|
<Box
|
||||||
|
component="li"
|
||||||
|
data-depth={depth}
|
||||||
|
sx={{ userSelect: 'none' }}
|
||||||
|
>
|
||||||
<Box
|
<Box
|
||||||
{...(isBranch
|
{...(isBranch
|
||||||
? {
|
? {
|
||||||
@@ -254,15 +129,27 @@ function NavItem({
|
|||||||
{t(title || '')}
|
{t(title || '')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
{label ? <Chip color="primary" label={label} size="small" /> : null}
|
{label ? (
|
||||||
|
<Chip
|
||||||
|
color="primary"
|
||||||
|
label={label}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
{external ? (
|
{external ? (
|
||||||
<Box sx={{ alignItems: 'center', display: 'flex', flex: '0 0 auto' }}>
|
<Box sx={{ alignItems: 'center', display: 'flex', flex: '0 0 auto' }}>
|
||||||
<ArrowSquareOutIcon color="var(--NavItem-icon-color)" fontSize="var(--icon-fontSize-sm)" />
|
<ArrowSquareOutIcon
|
||||||
|
color="var(--NavItem-icon-color)"
|
||||||
|
fontSize="var(--icon-fontSize-sm)"
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
) : null}
|
) : null}
|
||||||
{isBranch ? (
|
{isBranch ? (
|
||||||
<Box sx={{ alignItems: 'center', display: 'flex', flex: '0 0 auto' }}>
|
<Box sx={{ alignItems: 'center', display: 'flex', flex: '0 0 auto' }}>
|
||||||
<ExpandIcon color="var(--NavItem-expand-color)" fontSize="var(--icon-fontSize-sm)" />
|
<ExpandIcon
|
||||||
|
color="var(--NavItem-expand-color)"
|
||||||
|
fontSize="var(--icon-fontSize-sm)"
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
) : null}
|
) : null}
|
||||||
</Box>
|
</Box>
|
@@ -0,0 +1,45 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import type { NavItemConfig } from '@/types/nav';
|
||||||
|
|
||||||
|
import { renderNavItems } from './render-nav-items';
|
||||||
|
|
||||||
|
export function RenderNavGroups({ items, pathname }: { items: NavItemConfig[]; pathname: string }): React.JSX.Element {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const children = items.reduce((acc: React.ReactNode[], curr: NavItemConfig): React.ReactNode[] => {
|
||||||
|
acc.push(
|
||||||
|
<Stack
|
||||||
|
component="li"
|
||||||
|
key={curr.key}
|
||||||
|
spacing={1.5}
|
||||||
|
>
|
||||||
|
{curr.title ? (
|
||||||
|
<div>
|
||||||
|
<Typography sx={{ color: 'var(--NavGroup-title-color)', fontSize: '0.875rem', fontWeight: 500 }}>
|
||||||
|
{t(curr.title)}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
<div>{renderNavItems({ depth: 0, items: curr.items, pathname })}</div>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack
|
||||||
|
component="ul"
|
||||||
|
spacing={2}
|
||||||
|
sx={{ listStyle: 'none', m: 0, p: 0 }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
@@ -0,0 +1,51 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
|
|
||||||
|
import type { NavItemConfig } from '@/types/nav';
|
||||||
|
|
||||||
|
import { NavItem } from './nav-item';
|
||||||
|
|
||||||
|
export function renderNavItems({
|
||||||
|
depth = 0,
|
||||||
|
items = [],
|
||||||
|
pathname,
|
||||||
|
}: {
|
||||||
|
depth: number;
|
||||||
|
items?: NavItemConfig[];
|
||||||
|
pathname: string;
|
||||||
|
}): React.JSX.Element {
|
||||||
|
const children = items.reduce((acc: React.ReactNode[], curr: NavItemConfig): React.ReactNode[] => {
|
||||||
|
const { items: childItems, key, ...item } = curr;
|
||||||
|
|
||||||
|
const forceOpen = childItems
|
||||||
|
? Boolean(childItems.find((childItem) => childItem.href && pathname.startsWith(childItem.href)))
|
||||||
|
: false;
|
||||||
|
|
||||||
|
acc.push(
|
||||||
|
<NavItem
|
||||||
|
depth={depth}
|
||||||
|
forceOpen={forceOpen}
|
||||||
|
key={key}
|
||||||
|
pathname={pathname}
|
||||||
|
{...item}
|
||||||
|
>
|
||||||
|
{childItems ? renderNavItems({ depth: depth + 1, pathname, items: childItems }) : null}
|
||||||
|
</NavItem>
|
||||||
|
);
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack
|
||||||
|
component="ul"
|
||||||
|
data-depth={depth}
|
||||||
|
spacing={1}
|
||||||
|
sx={{ listStyle: 'none', m: 0, p: 0 }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
@@ -1,138 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import * as React from 'react';
|
|
||||||
import Avatar from '@mui/material/Avatar';
|
|
||||||
import Box from '@mui/material/Box';
|
|
||||||
import Button from '@mui/material/Button';
|
|
||||||
import Card from '@mui/material/Card';
|
|
||||||
import CardActions from '@mui/material/CardActions';
|
|
||||||
import CardContent from '@mui/material/CardContent';
|
|
||||||
import CardHeader from '@mui/material/CardHeader';
|
|
||||||
import FormControl from '@mui/material/FormControl';
|
|
||||||
import FormHelperText from '@mui/material/FormHelperText';
|
|
||||||
import InputAdornment from '@mui/material/InputAdornment';
|
|
||||||
import InputLabel from '@mui/material/InputLabel';
|
|
||||||
import Link from '@mui/material/Link';
|
|
||||||
import OutlinedInput from '@mui/material/OutlinedInput';
|
|
||||||
import Select from '@mui/material/Select';
|
|
||||||
import Stack from '@mui/material/Stack';
|
|
||||||
import Typography from '@mui/material/Typography';
|
|
||||||
import { Camera as CameraIcon } from '@phosphor-icons/react/dist/ssr/Camera';
|
|
||||||
import { User as UserIcon } from '@phosphor-icons/react/dist/ssr/User';
|
|
||||||
|
|
||||||
import { Option } from '@/components/core/option';
|
|
||||||
|
|
||||||
export function AccountDetails(): React.JSX.Element {
|
|
||||||
return (
|
|
||||||
<Card>
|
|
||||||
<CardHeader
|
|
||||||
avatar={
|
|
||||||
<Avatar>
|
|
||||||
<UserIcon fontSize="var(--Icon-fontSize)" />
|
|
||||||
</Avatar>
|
|
||||||
}
|
|
||||||
title="Basic details"
|
|
||||||
/>
|
|
||||||
<CardContent>
|
|
||||||
<Stack spacing={3}>
|
|
||||||
<Stack direction="row" spacing={2} sx={{ alignItems: 'center' }}>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
border: '1px dashed var(--mui-palette-divider)',
|
|
||||||
borderRadius: '50%',
|
|
||||||
display: 'inline-flex',
|
|
||||||
p: '4px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box sx={{ borderRadius: 'inherit', position: 'relative' }}>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
alignItems: 'center',
|
|
||||||
bgcolor: 'rgba(0, 0, 0, 0.5)',
|
|
||||||
borderRadius: 'inherit',
|
|
||||||
bottom: 0,
|
|
||||||
color: 'var(--mui-palette-common-white)',
|
|
||||||
cursor: 'pointer',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
left: 0,
|
|
||||||
opacity: 0,
|
|
||||||
position: 'absolute',
|
|
||||||
right: 0,
|
|
||||||
top: 0,
|
|
||||||
zIndex: 1,
|
|
||||||
'&:hover': { opacity: 1 },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Stack direction="row" spacing={1} sx={{ alignItems: 'center' }}>
|
|
||||||
<CameraIcon fontSize="var(--icon-fontSize-md)" />
|
|
||||||
<Typography color="inherit" variant="subtitle2">
|
|
||||||
Select
|
|
||||||
</Typography>
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
<Avatar src="/assets/avatar.png" sx={{ '--Avatar-size': '100px' }} />
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
<Button color="secondary" size="small">
|
|
||||||
Remove
|
|
||||||
</Button>
|
|
||||||
</Stack>
|
|
||||||
<Stack spacing={2}>
|
|
||||||
<FormControl>
|
|
||||||
<InputLabel>Full name</InputLabel>
|
|
||||||
<OutlinedInput defaultValue="Sofia Rivers" name="fullName" />
|
|
||||||
</FormControl>
|
|
||||||
<FormControl disabled>
|
|
||||||
<InputLabel>Email address</InputLabel>
|
|
||||||
<OutlinedInput name="email" type="email" value="sofia@devias.io" />
|
|
||||||
<FormHelperText>
|
|
||||||
Please <Link variant="inherit">contact us</Link> to change your email
|
|
||||||
</FormHelperText>
|
|
||||||
</FormControl>
|
|
||||||
<Stack direction="row" spacing={2}>
|
|
||||||
<FormControl sx={{ width: '160px' }}>
|
|
||||||
<InputLabel>Dial code</InputLabel>
|
|
||||||
<Select
|
|
||||||
name="countryCode"
|
|
||||||
startAdornment={
|
|
||||||
<InputAdornment position="start">
|
|
||||||
<Box
|
|
||||||
alt="Spain"
|
|
||||||
component="img"
|
|
||||||
src="/assets/flag-es.svg"
|
|
||||||
sx={{ display: 'block', height: '20px', width: 'auto' }}
|
|
||||||
/>
|
|
||||||
</InputAdornment>
|
|
||||||
}
|
|
||||||
value="+34"
|
|
||||||
>
|
|
||||||
<Option value="+1">United States</Option>
|
|
||||||
<Option value="+49">Germany</Option>
|
|
||||||
<Option value="+34">Spain</Option>
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
<FormControl sx={{ flex: '1 1 auto' }}>
|
|
||||||
<InputLabel>Phone number</InputLabel>
|
|
||||||
<OutlinedInput defaultValue="965 245 7623" name="phone" />
|
|
||||||
</FormControl>
|
|
||||||
</Stack>
|
|
||||||
<FormControl>
|
|
||||||
<InputLabel>Title</InputLabel>
|
|
||||||
<OutlinedInput name="title" placeholder="e.g Golang Developer" />
|
|
||||||
</FormControl>
|
|
||||||
<FormControl>
|
|
||||||
<InputLabel>Biography (optional)</InputLabel>
|
|
||||||
<OutlinedInput name="bio" placeholder="Describe yourself..." />
|
|
||||||
<FormHelperText>0/200 characters</FormHelperText>
|
|
||||||
</FormControl>
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
|
||||||
</CardContent>
|
|
||||||
<CardActions sx={{ justifyContent: 'flex-end' }}>
|
|
||||||
<Button color="secondary">Cancel</Button>
|
|
||||||
<Button variant="contained">Save changes</Button>
|
|
||||||
</CardActions>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
@@ -0,0 +1,3 @@
|
|||||||
|
# GUIDELINES
|
||||||
|
|
||||||
|
- use i18n
|
@@ -0,0 +1,348 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import RouterLink from 'next/link';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { COL_USER_METAS } from '@/constants';
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import { LoadingButton } from '@mui/lab';
|
||||||
|
import Avatar from '@mui/material/Avatar';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
import Card from '@mui/material/Card';
|
||||||
|
import CardActions from '@mui/material/CardActions';
|
||||||
|
import CardContent from '@mui/material/CardContent';
|
||||||
|
import CardHeader from '@mui/material/CardHeader';
|
||||||
|
import FormControl from '@mui/material/FormControl';
|
||||||
|
import FormHelperText from '@mui/material/FormHelperText';
|
||||||
|
import InputAdornment from '@mui/material/InputAdornment';
|
||||||
|
import InputLabel from '@mui/material/InputLabel';
|
||||||
|
import Link from '@mui/material/Link';
|
||||||
|
import OutlinedInput from '@mui/material/OutlinedInput';
|
||||||
|
import Select from '@mui/material/Select';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import { Camera as CameraIcon } from '@phosphor-icons/react/dist/ssr/Camera';
|
||||||
|
import { User as UserIcon } from '@phosphor-icons/react/dist/ssr/User';
|
||||||
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { z as zod } from 'zod';
|
||||||
|
|
||||||
|
import { paths } from '@/paths';
|
||||||
|
import { logger } from '@/lib/default-logger';
|
||||||
|
import { fileToBase64 } from '@/lib/file-to-base64';
|
||||||
|
import getImageUrlFromFile from '@/lib/get-image-url-from-file.ts';
|
||||||
|
import { pb } from '@/lib/pb';
|
||||||
|
import { useUser } from '@/hooks/use-user';
|
||||||
|
import { Option } from '@/components/core/option';
|
||||||
|
import { toast } from '@/components/core/toaster';
|
||||||
|
import FormLoading from '@/components/loading';
|
||||||
|
|
||||||
|
import ErrorDisplay from '../../error';
|
||||||
|
|
||||||
|
const schema = zod.object({
|
||||||
|
name: zod.string().min(1, 'Name is required').max(255),
|
||||||
|
email: zod.string().email('Must be a valid email').min(1, 'Email is required').max(255),
|
||||||
|
phone: zod.string().min(1, 'Phone is required').max(25),
|
||||||
|
company: zod.string().max(255).optional(),
|
||||||
|
billingAddress: zod.object({
|
||||||
|
country: zod.string().min(1, 'Country is required').max(255),
|
||||||
|
state: zod.string().min(1, 'State is required').max(255),
|
||||||
|
city: zod.string().min(1, 'City is required').max(255),
|
||||||
|
zipCode: zod.string().min(1, 'Zip code is required').max(255),
|
||||||
|
line1: zod.string().min(1, 'Street line 1 is required').max(255),
|
||||||
|
line2: zod.string().max(255).optional(),
|
||||||
|
}),
|
||||||
|
taxId: zod.string().max(255).optional(),
|
||||||
|
timezone: zod.string().min(1, 'Timezone is required').max(255),
|
||||||
|
language: zod.string().min(1, 'Language is required').max(255),
|
||||||
|
currency: zod.string().min(1, 'Currency is required').max(255),
|
||||||
|
avatar: zod.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
type Values = zod.infer<typeof schema>;
|
||||||
|
|
||||||
|
const defaultValues = {
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
phone: '',
|
||||||
|
company: '',
|
||||||
|
billingAddress: {
|
||||||
|
country: '',
|
||||||
|
state: '',
|
||||||
|
city: '',
|
||||||
|
zipCode: '',
|
||||||
|
line1: '',
|
||||||
|
line2: '',
|
||||||
|
},
|
||||||
|
taxId: '',
|
||||||
|
timezone: '',
|
||||||
|
language: '',
|
||||||
|
currency: '',
|
||||||
|
avatar: '',
|
||||||
|
} satisfies Values;
|
||||||
|
|
||||||
|
export function AccountDetails(): React.JSX.Element {
|
||||||
|
const router = useRouter();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const { user, isLoading } = useUser();
|
||||||
|
const [isUpdating, setIsUpdating] = React.useState<boolean>(false);
|
||||||
|
const [showLoading, setShowLoading] = React.useState<boolean>(false);
|
||||||
|
const [showError, setShowError] = React.useState({ show: false, detail: '' });
|
||||||
|
|
||||||
|
const onSubmit = React.useCallback(
|
||||||
|
async (values: Values): Promise<void> => {
|
||||||
|
setIsUpdating(true);
|
||||||
|
|
||||||
|
// const updateData = {
|
||||||
|
// name: values.name,
|
||||||
|
// email: values.email,
|
||||||
|
// phone: values.phone,
|
||||||
|
// company: values.company,
|
||||||
|
// billingAddress: values.billingAddress,
|
||||||
|
// taxId: values.taxId,
|
||||||
|
// timezone: values.timezone,
|
||||||
|
// language: values.language,
|
||||||
|
// currency: values.currency,
|
||||||
|
// avatar: values.avatar ? await base64ToFile(values.avatar) : null,
|
||||||
|
// };
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// await pb.collection(COL_CUSTOMERS).update(customerId, updateData);
|
||||||
|
// toast.success('Customer updated successfully');
|
||||||
|
// router.push(paths.dashboard.students.list);
|
||||||
|
// } catch (error) {
|
||||||
|
// logger.error(error);
|
||||||
|
// toast.error('Failed to update customer');
|
||||||
|
// } finally {
|
||||||
|
// setIsUpdating(false);
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
[router]
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { errors },
|
||||||
|
setValue,
|
||||||
|
reset,
|
||||||
|
watch,
|
||||||
|
} = useForm<Values>({ defaultValues, resolver: zodResolver(schema) });
|
||||||
|
|
||||||
|
const loadExistingData = React.useCallback(async () => {
|
||||||
|
setShowLoading(true);
|
||||||
|
if (user) {
|
||||||
|
try {
|
||||||
|
const result = await pb.collection(COL_USER_METAS).getOne(user.id);
|
||||||
|
reset({ ...defaultValues, ...result });
|
||||||
|
console.log({ result });
|
||||||
|
|
||||||
|
if (result.avatar) {
|
||||||
|
// TODO: remove me
|
||||||
|
// const fetchResult = await fetch(
|
||||||
|
// `http://127.0.0.1:8090/api/files/${result.collectionId}/${result.id}/${result.avatar}`
|
||||||
|
// );
|
||||||
|
const fetchResult = await fetch(getImageUrlFromFile(result.collectionId, result.id, result.avatar));
|
||||||
|
const blob = await fetchResult.blob();
|
||||||
|
const url = await fileToBase64(blob);
|
||||||
|
setValue('avatar', url);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
|
|
||||||
|
// TODO: add i18n here
|
||||||
|
toast.error('Failed to load customer data');
|
||||||
|
|
||||||
|
setShowError({ show: true, detail: JSON.stringify(error, null, 2) });
|
||||||
|
} finally {
|
||||||
|
setShowLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [user, reset, setValue]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
void loadExistingData();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (showLoading) return <FormLoading />;
|
||||||
|
if (!user) return <>loading</>;
|
||||||
|
|
||||||
|
if (showError.show)
|
||||||
|
return (
|
||||||
|
<ErrorDisplay
|
||||||
|
message={t('error.unable-to-process-request')}
|
||||||
|
code="500"
|
||||||
|
details={showError.detail}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<Card>
|
||||||
|
<CardHeader
|
||||||
|
avatar={
|
||||||
|
<Avatar>
|
||||||
|
<UserIcon fontSize="var(--Icon-fontSize)" />
|
||||||
|
</Avatar>
|
||||||
|
}
|
||||||
|
title="Basic details"
|
||||||
|
/>
|
||||||
|
<CardContent>
|
||||||
|
<Stack spacing={3}>
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
spacing={2}
|
||||||
|
sx={{ alignItems: 'center' }}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
border: '1px dashed var(--mui-palette-divider)',
|
||||||
|
borderRadius: '50%',
|
||||||
|
display: 'inline-flex',
|
||||||
|
p: '4px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ borderRadius: 'inherit', position: 'relative' }}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
alignItems: 'center',
|
||||||
|
bgcolor: 'rgba(0, 0, 0, 0.5)',
|
||||||
|
borderRadius: 'inherit',
|
||||||
|
bottom: 0,
|
||||||
|
color: 'var(--mui-palette-common-white)',
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
left: 0,
|
||||||
|
opacity: 0,
|
||||||
|
position: 'absolute',
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
zIndex: 1,
|
||||||
|
'&:hover': { opacity: 1 },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
spacing={1}
|
||||||
|
sx={{ alignItems: 'center' }}
|
||||||
|
>
|
||||||
|
<CameraIcon fontSize="var(--icon-fontSize-md)" />
|
||||||
|
<Typography
|
||||||
|
color="inherit"
|
||||||
|
variant="subtitle2"
|
||||||
|
>
|
||||||
|
{t('select')}
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
<Avatar
|
||||||
|
src={getImageUrlFromFile(user.collectionId, user.id, user.avatar)}
|
||||||
|
sx={{ '--Avatar-size': '100px' }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Button
|
||||||
|
color="secondary"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{t('remove')}
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
<Stack spacing={2}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="name"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormControl
|
||||||
|
error={Boolean(errors.name)}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
<InputLabel required>Name</InputLabel>
|
||||||
|
<OutlinedInput {...field} />
|
||||||
|
{errors.name ? <FormHelperText>{errors.name.message}</FormHelperText> : null}
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
disabled
|
||||||
|
control={control}
|
||||||
|
name="email"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormControl
|
||||||
|
error={Boolean(errors.email)}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
<InputLabel required>Email</InputLabel>
|
||||||
|
<OutlinedInput
|
||||||
|
{...field}
|
||||||
|
type="email"
|
||||||
|
/>
|
||||||
|
{errors.email ? <FormHelperText>{errors.email.message}</FormHelperText> : null}
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
spacing={2}
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="phone"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormControl
|
||||||
|
error={Boolean(errors.phone)}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
<InputLabel required>Phone</InputLabel>
|
||||||
|
<OutlinedInput {...field} />
|
||||||
|
{errors.phone ? <FormHelperText>{errors.phone.message}</FormHelperText> : null}
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<FormControl>
|
||||||
|
<InputLabel>Title</InputLabel>
|
||||||
|
<OutlinedInput
|
||||||
|
name="title"
|
||||||
|
placeholder="e.g Golang Developer"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl>
|
||||||
|
<InputLabel>Biography (optional)</InputLabel>
|
||||||
|
<OutlinedInput
|
||||||
|
name="bio"
|
||||||
|
placeholder="Describe yourself..."
|
||||||
|
/>
|
||||||
|
<FormHelperText>0/200 characters</FormHelperText>
|
||||||
|
</FormControl>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</CardContent>
|
||||||
|
<CardActions sx={{ justifyContent: 'flex-end' }}>
|
||||||
|
<Button
|
||||||
|
color="secondary"
|
||||||
|
component={RouterLink}
|
||||||
|
href={paths.dashboard.overview}
|
||||||
|
>
|
||||||
|
{t('edit.cancelButton')}
|
||||||
|
</Button>
|
||||||
|
{/* <Button variant="contained">Save changes</Button> */}
|
||||||
|
<LoadingButton
|
||||||
|
disabled={isUpdating}
|
||||||
|
loading={isUpdating}
|
||||||
|
type="submit"
|
||||||
|
variant="contained"
|
||||||
|
>
|
||||||
|
{t('edit.updateButton')}
|
||||||
|
</LoadingButton>
|
||||||
|
</CardActions>
|
||||||
|
</Card>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
@@ -20,11 +20,17 @@ export function DeleteAccount(): React.JSX.Element {
|
|||||||
title="Delete account"
|
title="Delete account"
|
||||||
/>
|
/>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Stack spacing={3} sx={{ alignItems: 'flex-start' }}>
|
<Stack
|
||||||
|
spacing={3}
|
||||||
|
sx={{ alignItems: 'flex-start' }}
|
||||||
|
>
|
||||||
<Typography variant="subtitle1">
|
<Typography variant="subtitle1">
|
||||||
Delete your account and all of your source data. This is irreversible.
|
Delete your account and all of your source data. This is irreversible.
|
||||||
</Typography>
|
</Typography>
|
||||||
<Button color="error" variant="outlined">
|
<Button
|
||||||
|
color="error"
|
||||||
|
variant="outlined"
|
||||||
|
>
|
||||||
Delete account
|
Delete account
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
Reference in New Issue
Block a user