Refactor admin sub-nav

This commit is contained in:
Sam Becker 2025-02-25 20:58:01 -06:00
parent 9f483bcf21
commit bd7cf64f2a
8 changed files with 101 additions and 48 deletions

View File

@ -1,13 +1,9 @@
import ClearCacheButton from '@/admin/ClearCacheButton';
import AdminAppConfiguration from '@/admin/AdminAppConfiguration';
import AdminInfoPage from '@/admin/AdminInfoPage';
export default function AdminAppConfigurationPage() {
return (
<AdminInfoPage
title="App Configuration"
accessory={<ClearCacheButton />}
>
<AdminInfoPage page="Config">
<AdminAppConfiguration />
</AdminInfoPage>
);

View File

@ -2,7 +2,7 @@ import AdminAppInsights from '@/admin/insights/AdminAppInsights';
import AdminInfoPage from '@/admin/AdminInfoPage';
export default async function AdminInsightsPage() {
return <AdminInfoPage title="App Insights">
return <AdminInfoPage page="Insights">
<AdminAppInsights />
</AdminInfoPage>;
}

View File

@ -0,0 +1,36 @@
import { useAppState } from '@/state/AppState';
import clsx from 'clsx/lite';
import { FaCircle } from 'react-icons/fa6';
import { LuCog } from 'react-icons/lu';
export default function AdminAppInfoIcon({
className,
}: {
className?: string
}) {
const { insightIndicatorStatus } = useAppState();
return (
<span className={clsx(
'inline-flex relative',
className,
)}>
<LuCog
size={20}
className="inline-flex translate-y-[1px]"
aria-label="App Info"
/>
{insightIndicatorStatus &&
<FaCircle
size={8}
className={clsx(
'absolute',
'top-[2px] right-[0.5px]',
insightIndicatorStatus === 'blue'
? 'text-blue-500'
: 'text-amber-500',
)}
/>}
</span>
);
}

View File

@ -1,14 +1,27 @@
import { PATH_ADMIN_CONFIGURATION, PATH_ADMIN_INSIGHTS } from '@/app/paths';
import Container from '@/components/Container';
import LinkWithStatus from '@/components/LinkWithStatus';
import ResponsiveText from '@/components/primitives/ResponsiveText';
import SiteGrid from '@/components/SiteGrid';
import clsx from 'clsx/lite';
import { ReactNode } from 'react';
import ClearCacheButton from '@/admin/ClearCacheButton';
const ADMIN_INFO_PAGES = [{
titleShort: 'Insights',
path: PATH_ADMIN_INSIGHTS,
},
{
title: 'Configuration',
titleShort: 'Config',
path: PATH_ADMIN_CONFIGURATION,
}];
export default function AdminInfoPage({
title,
accessory,
page,
children,
}: {
title: string
accessory?: ReactNode
page: (typeof ADMIN_INFO_PAGES)[number]['titleShort']
children: ReactNode
}) {
return (
@ -16,10 +29,28 @@ export default function AdminInfoPage({
contentMain={
<div className="space-y-4">
<div className="flex items-center gap-4 min-h-9">
<div className="grow">
{title}
<div className={clsx(
'grow -translate-x-1 -translate-y-1',
'flex items-center gap-3',
)}>
{ADMIN_INFO_PAGES.map(({ title, titleShort, path }) =>
<LinkWithStatus
key={path}
href={path}
className={clsx(
page === titleShort
? 'underline underline-offset-10 decoration-[1.5px]'
: 'text-dim',
'px-1 py-0.5 rounded-md',
)}
loadingClassName="bg-dim"
>
<ResponsiveText shortText={titleShort}>
{title ?? titleShort}
</ResponsiveText>
</LinkWithStatus>)}
</div>
{accessory}
<ClearCacheButton />
</div>
<Container spaceChildren={false}>
{children}

View File

@ -6,11 +6,9 @@ import Note from '@/components/Note';
import SiteGrid from '@/components/SiteGrid';
import Spinner from '@/components/Spinner';
import {
PATH_ADMIN_CONFIGURATION,
PATH_ADMIN_INSIGHTS,
checkPathPrefix,
isPathAdminConfiguration,
isPathAdminInsights,
isPathAdminInfo,
isPathTopLevelAdmin,
} from '@/app/paths';
import { useAppState } from '@/state/AppState';
@ -19,8 +17,7 @@ import { differenceInMinutes } from 'date-fns';
import { usePathname } from 'next/navigation';
import { useEffect, useMemo, useState } from 'react';
import { FaRegClock } from 'react-icons/fa';
import AdminAppInsightsIcon from './insights/AdminAppInsightsIcon';
import { LuCog } from 'react-icons/lu';
import AdminAppInfoIcon from './AdminAppInfoIcon';
// Updates considered recent if they occurred in past 5 minutes
const areTimesRecent = (dates: Date[]) => dates
@ -29,6 +26,8 @@ const areTimesRecent = (dates: Date[]) => dates
export default function AdminNavClient({
items,
mostRecentPhotoUpdateTime,
// TODO: use this with new <AdminSubNav> component
// eslint-disable-next-line @typescript-eslint/no-unused-vars
includeInsights,
}: {
items: {
@ -91,33 +90,15 @@ export default function AdminNavClient({
<span>({count})</span>}
</LinkWithStatus>)}
</div>
<div className="flex gap-3">
{includeInsights &&
<LinkWithLoader
href={PATH_ADMIN_INSIGHTS}
className={clsx(
'translate-y-[-2px]',
isPathAdminInsights(pathname)
? 'font-bold'
: 'text-dim')}
loader={<Spinner className="translate-y-[1px]" />}
>
<AdminAppInsightsIcon />
</LinkWithLoader>}
<LinkWithLoader
href={PATH_ADMIN_CONFIGURATION}
className={isPathAdminConfiguration(pathname)
? 'font-bold'
: 'text-dim'}
loader={<Spinner className="translate-y-[-0.75px]" />}
>
<LuCog
size={20}
className="inline-flex translate-y-[1px]"
aria-label="App Configuration"
/>
</LinkWithLoader>
</div>
<LinkWithLoader
href={PATH_ADMIN_INSIGHTS}
className={isPathAdminInfo(pathname)
? 'font-bold'
: 'text-dim'}
loader={<Spinner className="translate-y-[-0.75px]" />}
>
<AdminAppInfoIcon />
</LinkWithLoader>
</div>
{shouldShowBanner &&
<Note icon={<FaRegClock className="shrink-0" />}>

View File

@ -64,6 +64,7 @@ export const PATHS_ADMIN = [
PATH_ADMIN_PHOTOS,
PATH_ADMIN_UPLOADS,
PATH_ADMIN_TAGS,
PATH_ADMIN_INSIGHTS,
PATH_ADMIN_CONFIGURATION,
PATH_ADMIN_BASELINE,
PATH_ADMIN_COMPONENTS,
@ -225,11 +226,15 @@ export const isPathAdmin = (pathname?: string) =>
export const isPathTopLevelAdmin = (pathname?: string) =>
PATHS_ADMIN.some(path => path === pathname);
export const isPathAdminInsights = (pathname?: string) =>
checkPathPrefix(pathname, PATH_ADMIN_INSIGHTS);
export const isPathAdminConfiguration = (pathname?: string) =>
checkPathPrefix(pathname, PATH_ADMIN_CONFIGURATION);
export const isPathAdminInsights = (pathname?: string) =>
checkPathPrefix(pathname, PATH_ADMIN_INSIGHTS);
export const isPathAdminInfo = (pathname?: string) =>
isPathAdminInsights(pathname) ||
isPathAdminConfiguration(pathname);
export const isPathProtected = (pathname?: string) =>
checkPathPrefix(pathname, PATH_ADMIN) ||

View File

@ -24,6 +24,7 @@ export type LinkWithStatusProps = Omit<
children: ReactNode | ((props: {
isLoading: boolean
}) => ReactNode)
debugLoading?: boolean
}
export default function LinkWithStatus({
@ -32,12 +33,14 @@ export default function LinkWithStatus({
className,
onClick,
children,
debugLoading = false,
...props
}: LinkWithStatusProps) {
const path = usePathname();
const [pathWhenClicked, setPathWhenClicked] = useState<string>();
const [isLoading, setIsLoading] = useState(false);
const [_isLoading, setIsLoading] = useState(false);
const isLoading = _isLoading || debugLoading;
const isLoadingStartTime = useRef<number | undefined>(undefined);

View File

@ -301,6 +301,7 @@
disabled:bg-gray-100 dark:disabled:bg-gray-900
disabled:border-gray-200 disabled:dark:border-gray-700
border-gray-900 dark:border-gray-100
hover:border-gray-900 dark:hover:border-gray-100
active:bg-gray-700 active:border-gray-700
active:dark:bg-gray-300 active:dark:border-gray-300
shadow-none