PPR-ify admin nav
This commit is contained in:
parent
85813353e6
commit
0b523a1c95
@ -1,65 +1,52 @@
|
||||
'use client';
|
||||
|
||||
import SiteGrid from '@/components/SiteGrid';
|
||||
import {
|
||||
PATH_ADMIN_CONFIGURATION,
|
||||
checkPathPrefix,
|
||||
isPathAdminConfiguration,
|
||||
getBlobUploadUrlsNoStore,
|
||||
getPhotosCountIncludingHiddenCached,
|
||||
getUniqueTagsCached,
|
||||
} from '@/cache';
|
||||
import {
|
||||
PATH_ADMIN_PHOTOS,
|
||||
PATH_ADMIN_TAGS,
|
||||
PATH_ADMIN_UPLOADS,
|
||||
} from '@/site/paths';
|
||||
import { clsx } from 'clsx/lite';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { BiCog } from 'react-icons/bi';
|
||||
import AdminNavClient from './AdminNavClient';
|
||||
|
||||
export default function AdminNav({
|
||||
items,
|
||||
}: {
|
||||
items: {
|
||||
label: string,
|
||||
href: string,
|
||||
count: number,
|
||||
}[]
|
||||
}) {
|
||||
const pathname = usePathname();
|
||||
export default async function AdminNav() {
|
||||
// await sleep(20_000);
|
||||
|
||||
const [
|
||||
countPhotos,
|
||||
countUploads,
|
||||
countTags,
|
||||
] = await Promise.all([
|
||||
getPhotosCountIncludingHiddenCached(),
|
||||
getBlobUploadUrlsNoStore().then(urls => urls.length),
|
||||
getUniqueTagsCached().then(tags => tags.length),
|
||||
]);
|
||||
|
||||
const navItemPhotos = {
|
||||
label: 'Photos',
|
||||
href: PATH_ADMIN_PHOTOS,
|
||||
count: countPhotos,
|
||||
};
|
||||
|
||||
const navItemUploads = {
|
||||
label: 'Uploads',
|
||||
href: PATH_ADMIN_UPLOADS,
|
||||
count: countUploads,
|
||||
};
|
||||
|
||||
const navItemTags = {
|
||||
label: 'Tags',
|
||||
href: PATH_ADMIN_TAGS,
|
||||
count: countTags,
|
||||
};
|
||||
|
||||
const navItems = [navItemPhotos];
|
||||
|
||||
if (countUploads > 0) { navItems.push(navItemUploads); }
|
||||
if (countTags > 0) { navItems.push(navItemTags); }
|
||||
|
||||
return (
|
||||
<SiteGrid
|
||||
contentMain={
|
||||
<div className={clsx(
|
||||
'flex gap-2 md:gap-4',
|
||||
'border-b border-gray-200 dark:border-gray-800 pb-3',
|
||||
)}>
|
||||
<div className={clsx(
|
||||
'flex gap-2 md:gap-4',
|
||||
'flex-grow overflow-x-scroll',
|
||||
)}>
|
||||
{items.map(({ label, href, count }) =>
|
||||
<Link
|
||||
key={label}
|
||||
href={href}
|
||||
className={clsx(
|
||||
'flex gap-0.5',
|
||||
checkPathPrefix(pathname, href) ? 'font-bold' : 'text-dim',
|
||||
)}
|
||||
>
|
||||
<span>{label}</span>
|
||||
<span>({count})</span>
|
||||
</Link>)}
|
||||
</div>
|
||||
<Link
|
||||
href={PATH_ADMIN_CONFIGURATION}
|
||||
className={isPathAdminConfiguration(pathname)
|
||||
? 'font-bold'
|
||||
: 'text-dim'}
|
||||
>
|
||||
<BiCog
|
||||
size={18}
|
||||
className="inline-block"
|
||||
aria-label="Blog Configuration"
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<AdminNavClient items={navItems} />
|
||||
);
|
||||
}
|
||||
|
||||
66
src/admin/AdminNavClient.tsx
Normal file
66
src/admin/AdminNavClient.tsx
Normal file
@ -0,0 +1,66 @@
|
||||
'use client';
|
||||
|
||||
import SiteGrid from '@/components/SiteGrid';
|
||||
import {
|
||||
PATH_ADMIN_CONFIGURATION,
|
||||
checkPathPrefix,
|
||||
isPathAdminConfiguration,
|
||||
} from '@/site/paths';
|
||||
import { clsx } from 'clsx/lite';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { BiCog } from 'react-icons/bi';
|
||||
|
||||
export default function AdminNavClient({
|
||||
items,
|
||||
}: {
|
||||
items: {
|
||||
label: string,
|
||||
href: string,
|
||||
count: number,
|
||||
}[]
|
||||
}) {
|
||||
const pathname = usePathname();
|
||||
|
||||
return (
|
||||
<SiteGrid
|
||||
contentMain={
|
||||
<div className={clsx(
|
||||
'flex gap-2 md:gap-4',
|
||||
'border-b border-gray-200 dark:border-gray-800 pb-3',
|
||||
)}>
|
||||
<div className={clsx(
|
||||
'flex gap-2 md:gap-4',
|
||||
'flex-grow overflow-x-scroll',
|
||||
)}>
|
||||
{items.map(({ label, href, count }) =>
|
||||
<Link
|
||||
key={label}
|
||||
href={href}
|
||||
className={clsx(
|
||||
'flex gap-0.5',
|
||||
checkPathPrefix(pathname, href) ? 'font-bold' : 'text-dim',
|
||||
)}
|
||||
>
|
||||
<span>{label}</span>
|
||||
{count > 0 &&
|
||||
<span>({count})</span>}
|
||||
</Link>)}
|
||||
</div>
|
||||
<Link
|
||||
href={PATH_ADMIN_CONFIGURATION}
|
||||
className={isPathAdminConfiguration(pathname)
|
||||
? 'font-bold'
|
||||
: 'text-dim'}
|
||||
>
|
||||
<BiCog
|
||||
size={18}
|
||||
className="inline-block"
|
||||
aria-label="Blog Configuration"
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -1,56 +1,21 @@
|
||||
import AdminNav from '@/admin/AdminNav';
|
||||
import {
|
||||
getBlobUploadUrlsNoStore,
|
||||
getPhotosCountIncludingHiddenCached,
|
||||
getUniqueTagsCached,
|
||||
} from '@/cache';
|
||||
import {
|
||||
PATH_ADMIN_PHOTOS,
|
||||
PATH_ADMIN_TAGS,
|
||||
PATH_ADMIN_UPLOADS,
|
||||
} from '@/site/paths';
|
||||
import AdminNavClient from '@/admin/AdminNavClient';
|
||||
import { Suspense } from 'react';
|
||||
|
||||
export default async function AdminLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const [
|
||||
countPhotos,
|
||||
countUploads,
|
||||
countTags,
|
||||
] = await Promise.all([
|
||||
getPhotosCountIncludingHiddenCached(),
|
||||
getBlobUploadUrlsNoStore().then(urls => urls.length),
|
||||
getUniqueTagsCached().then(tags => tags.length),
|
||||
]);
|
||||
|
||||
const navItemPhotos = {
|
||||
label: 'Photos',
|
||||
href: PATH_ADMIN_PHOTOS,
|
||||
count: countPhotos,
|
||||
};
|
||||
|
||||
const navItemUploads = {
|
||||
label: 'Uploads',
|
||||
href: PATH_ADMIN_UPLOADS,
|
||||
count: countUploads,
|
||||
};
|
||||
|
||||
const navItemTags = {
|
||||
label: 'Tags',
|
||||
href: PATH_ADMIN_TAGS,
|
||||
count: countTags,
|
||||
};
|
||||
|
||||
const navItems = [navItemPhotos];
|
||||
|
||||
if (countUploads > 0) { navItems.push(navItemUploads); }
|
||||
if (countTags > 0) { navItems.push(navItemTags); }
|
||||
|
||||
return (
|
||||
<div className="mt-4 space-y-5">
|
||||
<AdminNav items={navItems} />
|
||||
<Suspense fallback={<AdminNavClient items={[{
|
||||
label: 'Photos',
|
||||
count: 0,
|
||||
href: '/admin/photos',
|
||||
}]} />}>
|
||||
<AdminNav />
|
||||
</Suspense>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -36,15 +36,22 @@ export default function FooterClient({
|
||||
key="footer"
|
||||
className={clsx(
|
||||
'flex items-center',
|
||||
'text-dim min-h-[4rem]',
|
||||
'text-dim min-h-10',
|
||||
)}>
|
||||
<div className={clsx(
|
||||
'flex items-center flex-grow flex-wrap h-10',
|
||||
'gap-x-4 min-w-0',
|
||||
)}>
|
||||
<div className="flex gap-x-4 gap-y-1 flex-grow flex-wrap h-4">
|
||||
{isPathAdmin(pathname)
|
||||
? <>
|
||||
{userEmail === undefined &&
|
||||
<Spinner />}
|
||||
{userEmail && <>
|
||||
<div>{userEmail}</div>
|
||||
<div className={clsx(
|
||||
'truncate max-w-full',
|
||||
)}>
|
||||
{userEmail}
|
||||
</div>
|
||||
<form action={signOutAction}>
|
||||
<SubmitButtonWithStatus styleAsLink>
|
||||
Sign out
|
||||
@ -60,7 +67,7 @@ export default function FooterClient({
|
||||
<RepoLink />}
|
||||
</>}
|
||||
</div>
|
||||
<div className="flex items-center h-4">
|
||||
<div className="flex items-center h-10">
|
||||
<ThemeSwitcher />
|
||||
</div>
|
||||
</div>]
|
||||
|
||||
@ -105,7 +105,7 @@
|
||||
button.link {
|
||||
@apply
|
||||
p-0 min-h-0
|
||||
border-none active:bg-transparent shadow-none
|
||||
border-none bg-transparent active:bg-transparent shadow-none
|
||||
}
|
||||
a, .link {
|
||||
@apply
|
||||
|
||||
3
src/utility/sleep.ts
Normal file
3
src/utility/sleep.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export default function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user