ok
This commit is contained in:
@@ -6,7 +6,8 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "bun --hot src/index.tsx",
|
"dev": "bun --hot src/index.tsx",
|
||||||
"build": "bun build ./src/index.html --outdir=dist --sourcemap --target=browser --minify --define:process.env.NODE_ENV='\"production\"' --env='BUN_PUBLIC_*'",
|
"build": "bun build ./src/index.html --outdir=dist --sourcemap --target=browser --minify --define:process.env.NODE_ENV='\"production\"' --env='BUN_PUBLIC_*'",
|
||||||
"start": "NODE_ENV=production bun src/index.tsx"
|
"start": "NODE_ENV=production bun src/index.tsx",
|
||||||
|
"seed": "bun prisma/seed.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@elysiajs/cors": "^1.4.0",
|
"@elysiajs/cors": "^1.4.0",
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import {
|
|||||||
ActionIcon,
|
ActionIcon,
|
||||||
AppShell,
|
AppShell,
|
||||||
Avatar,
|
Avatar,
|
||||||
|
Button,
|
||||||
Card,
|
Card,
|
||||||
|
Divider,
|
||||||
Flex,
|
Flex,
|
||||||
Group,
|
Group,
|
||||||
NavLink,
|
NavLink,
|
||||||
@@ -22,142 +24,158 @@ import {
|
|||||||
IconDashboard
|
IconDashboard
|
||||||
} from '@tabler/icons-react'
|
} from '@tabler/icons-react'
|
||||||
import type { User } from 'generated/prisma'
|
import type { User } from 'generated/prisma'
|
||||||
import { data, Outlet, useLocation, useNavigate } from 'react-router-dom'
|
import { Outlet, useLocation, useNavigate } from 'react-router-dom'
|
||||||
|
|
||||||
import { default as clientRoute, default as clientRoutes } from '@/clientRoutes'
|
import { default as clientRoute, default as clientRoutes } from '@/clientRoutes'
|
||||||
import apiFetch from '@/lib/apiFetch'
|
import apiFetch from '@/lib/apiFetch'
|
||||||
import { showNotification } from '@mantine/notifications'
|
|
||||||
|
|
||||||
|
function Logout() {
|
||||||
|
return <Group>
|
||||||
|
<Button variant='transparent' size='compact-xs' onClick={async () => {
|
||||||
|
await apiFetch.auth.logout.delete()
|
||||||
|
localStorage.removeItem('token')
|
||||||
|
window.location.href = '/login'
|
||||||
|
}}>Logout</Button>
|
||||||
|
</Group>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export default function DashboardLayout() {
|
export default function DashboardLayout() {
|
||||||
const [opened, setOpened] = useLocalStorage({
|
const [opened, setOpened] = useLocalStorage({
|
||||||
key: 'nav_open',
|
key: 'nav_open',
|
||||||
defaultValue: true,
|
defaultValue: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppShell
|
<AppShell
|
||||||
padding="md"
|
padding="md"
|
||||||
navbar={{
|
navbar={{
|
||||||
width: 260,
|
width: 260,
|
||||||
breakpoint: 'sm',
|
breakpoint: 'sm',
|
||||||
collapsed: { mobile: !opened, desktop: !opened },
|
collapsed: { mobile: !opened, desktop: !opened },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AppShell.Navbar>
|
<AppShell.Navbar>
|
||||||
<AppShell.Section>
|
<AppShell.Section>
|
||||||
<Group justify="flex-end" p="xs">
|
<Group justify="flex-end" p="xs">
|
||||||
<Tooltip
|
<Tooltip
|
||||||
label={opened ? 'Collapse navigation' : 'Expand navigation'}
|
label={opened ? 'Collapse navigation' : 'Expand navigation'}
|
||||||
withArrow
|
withArrow
|
||||||
>
|
>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
variant="light"
|
variant="light"
|
||||||
color="gray"
|
color="gray"
|
||||||
onClick={() => setOpened(v => !v)}
|
onClick={() => setOpened(v => !v)}
|
||||||
aria-label="Toggle navigation"
|
aria-label="Toggle navigation"
|
||||||
radius="xl"
|
radius="xl"
|
||||||
>
|
>
|
||||||
{opened ? <IconChevronLeft /> : <IconChevronRight />}
|
{opened ? <IconChevronLeft /> : <IconChevronRight />}
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Group>
|
</Group>
|
||||||
</AppShell.Section>
|
</AppShell.Section>
|
||||||
|
|
||||||
<AppShell.Section grow component={ScrollArea} flex={1}>
|
<AppShell.Section grow component={ScrollArea} flex={1}>
|
||||||
<NavigationDashboard />
|
<NavigationDashboard />
|
||||||
</AppShell.Section>
|
</AppShell.Section>
|
||||||
|
|
||||||
<AppShell.Section>
|
<AppShell.Section>
|
||||||
<HostView />
|
<HostView />
|
||||||
</AppShell.Section>
|
</AppShell.Section>
|
||||||
</AppShell.Navbar>
|
</AppShell.Navbar>
|
||||||
|
|
||||||
<AppShell.Main>
|
<AppShell.Main>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Paper withBorder shadow="md" radius="lg" p="md">
|
<Paper withBorder shadow="md" radius="lg" p="md">
|
||||||
<Flex align="center" gap="md">
|
<Flex align="center" gap="md">
|
||||||
{!opened && (
|
{!opened && (
|
||||||
<Tooltip label="Open navigation menu" withArrow>
|
<Tooltip label="Open navigation menu" withArrow>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
variant="light"
|
variant="light"
|
||||||
color="gray"
|
color="gray"
|
||||||
onClick={() => setOpened(true)}
|
onClick={() => setOpened(true)}
|
||||||
aria-label="Open navigation"
|
aria-label="Open navigation"
|
||||||
radius="xl"
|
radius="xl"
|
||||||
>
|
>
|
||||||
<IconChevronRight />
|
<IconChevronRight />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
<Title order={3} fw={600}>
|
<Title order={3} fw={600}>
|
||||||
App Dashboard
|
App Dashboard
|
||||||
</Title>
|
</Title>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Paper>
|
</Paper>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</Stack>
|
</Stack>
|
||||||
</AppShell.Main>
|
</AppShell.Main>
|
||||||
</AppShell>
|
</AppShell>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------- Host Info ----------------------- */
|
/* ----------------------- Host Info ----------------------- */
|
||||||
function HostView() {
|
function HostView() {
|
||||||
const [host, setHost] = useState<User | null>(null)
|
const [host, setHost] = useState<User | null>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchHost() {
|
async function fetchHost() {
|
||||||
const {data} = await apiFetch.api.user.find.get()
|
const { data } = await apiFetch.api.user.find.get()
|
||||||
setHost(data?.user ?? null)
|
setHost(data?.user ?? null)
|
||||||
}
|
}
|
||||||
fetchHost()
|
fetchHost()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card radius="lg" withBorder shadow="sm" p="md">
|
<Card radius="lg" withBorder shadow="sm" p="md">
|
||||||
{host ? (
|
{host ? (
|
||||||
<Flex gap="md" align="center">
|
<Stack>
|
||||||
<Avatar size="md" radius="xl" color="blue">
|
<Flex gap="md" align="center">
|
||||||
{host.name?.[0]}
|
<Avatar size="md" radius="xl" color="blue">
|
||||||
</Avatar>
|
{host.name?.[0]}
|
||||||
<Stack gap={2}>
|
</Avatar>
|
||||||
<Text fw={600}>{host.name}</Text>
|
<Stack gap={2}>
|
||||||
<Text size="sm" c="dimmed">{host.email}</Text>
|
<Text fw={600}>{host.name}</Text>
|
||||||
</Stack>
|
<Text size="sm" c="dimmed">{host.email}</Text>
|
||||||
</Flex>
|
</Stack>
|
||||||
) : (
|
</Flex>
|
||||||
<Text size="sm" c="dimmed" ta="center">
|
<Divider />
|
||||||
No host information available
|
<Logout />
|
||||||
</Text>
|
</Stack>
|
||||||
)}
|
) : (
|
||||||
</Card>
|
<Text size="sm" c="dimmed" ta="center">
|
||||||
)
|
No host information available
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ----------------------- Navigation ----------------------- */
|
/* ----------------------- Navigation ----------------------- */
|
||||||
function NavigationDashboard() {
|
function NavigationDashboard() {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
|
||||||
const isActive = (path: keyof typeof clientRoute) =>
|
const isActive = (path: keyof typeof clientRoute) =>
|
||||||
location.pathname.startsWith(clientRoute[path])
|
location.pathname.startsWith(clientRoute[path])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap="xs" p="sm">
|
<Stack gap="xs" p="sm">
|
||||||
<NavLink
|
<NavLink
|
||||||
active={isActive('/dashboard/landing')}
|
active={isActive('/dashboard/landing')}
|
||||||
leftSection={<IconDashboard size={20} />}
|
leftSection={<IconDashboard size={20} />}
|
||||||
label="Dashboard Overview"
|
label="Dashboard Overview"
|
||||||
description="Quick summary and activity highlights"
|
description="Quick summary and activity highlights"
|
||||||
onClick={() => navigate(clientRoutes['/dashboard/landing'])}
|
onClick={() => navigate(clientRoutes['/dashboard/landing'])}
|
||||||
/>
|
/>
|
||||||
<NavLink
|
<NavLink
|
||||||
active={isActive('/dashboard/apikey')}
|
active={isActive('/dashboard/apikey')}
|
||||||
leftSection={<IconDashboard size={20} />}
|
leftSection={<IconDashboard size={20} />}
|
||||||
label="Dashboard Overview"
|
label="Dashboard Overview"
|
||||||
description="Quick summary and activity highlights"
|
description="Quick summary and activity highlights"
|
||||||
onClick={() => navigate(clientRoutes['/dashboard/apikey'])}
|
onClick={() => navigate(clientRoutes['/dashboard/apikey'])}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user