Vercel/src/about/AboutPageClient.tsx
2026-03-01 22:36:11 -06:00

212 lines
5.3 KiB
TypeScript

'use client';
import PhotoAlbum from '@/album/PhotoAlbum';
import { useAppState } from '@/app/AppState';
import PhotoCamera from '@/camera/PhotoCamera';
import AnimateItems from '@/components/AnimateItems';
import AppGrid from '@/components/AppGrid';
import PhotoFilm from '@/film/PhotoFilm';
import PhotoLens from '@/lens/PhotoLens';
import { Photo } from '@/photo';
import PhotoRecipe from '@/recipe/PhotoRecipe';
import PhotoTag from '@/tag/PhotoTag';
import clsx from 'clsx/lite';
import { formatDistanceToNowStrict } from 'date-fns';
import AdminAboutMenu from './AdminAboutMenu';
import PhotoLarge from '@/photo/PhotoLarge';
import { ReactNode, useMemo } from 'react';
import { Camera } from '@/camera';
import { Lens } from '@/lens';
import { Album } from '@/album';
import { useAppText } from '@/i18n/state/client';
import PhotoAvatar from '@/photo/PhotoAvatar';
import Link from 'next/link';
import { PATH_ADMIN_ABOUT_EDIT } from '@/app/path';
import { LuCirclePlus, LuUser } from 'react-icons/lu';
import AdminEmptyState from '@/admin/AdminEmptyState';
export default function AboutPageClient({
title,
subhead,
descriptionHtml,
photosCount = 0,
photosOldest,
photoAvatar,
photoHero,
camera,
lens,
recipe,
film,
album,
tag,
lastUpdated,
}: {
title?: string
subhead?: string
descriptionHtml?: ReactNode
photosCount?: number
photosOldest?: string
photoAvatar?: Photo
photoHero?: Photo
camera?: Camera
lens?: Lens
recipe?: string
film?: string
album?: Album
tag?: string
lastUpdated?: Date
}) {
const {
isUserSignedIn,
} = useAppState();
const appText = useAppText();
const renderItem = (label: string, content?: ReactNode) => (
<div
key={label}
className="border-t border-medium pt-1 space-y-px"
>
<div className="text-[13px] uppercase tracking-wide text-dim truncate">
{label}
</div>
<div className="text-[16px] truncate">
{content || '--'}
</div>
</div>
);
const items = useMemo(() => [
renderItem(
appText.about.photoCount,
photosCount.toString().padStart(4, '0'),
),
renderItem(
appText.about.firstPhoto,
photosOldest?.slice(0, 10),
),
camera && renderItem(
appText.about.topCamera,
<PhotoCamera
camera={camera}
type="text-only"
contrast="high"
/>,
),
lens && renderItem(
appText.about.topLens,
<PhotoLens
lens={lens}
type="text-only"
contrast="high"
/>,
),
recipe && renderItem(
appText.about.topRecipe,
<PhotoRecipe
recipe={recipe}
type="text-only"
contrast="high"
/>,
),
film && renderItem(
appText.about.topFilm,
<PhotoFilm
film={film}
type="text-only"
contrast="high"
badged={false}
/>,
),
album && renderItem(
appText.about.recentAlbum,
<PhotoAlbum
album={album}
type="text-only"
contrast="high"
/>,
),
tag && renderItem(
appText.about.popularTag,
<PhotoTag
tag={tag}
type="text-only"
contrast="high"
/>,
),
].filter(Boolean), [
appText.about,
photosCount,
photosOldest,
camera,
lens,
recipe,
film,
album,
tag,
]);
return (
<AnimateItems
type="bottom"
items={[<div
key="about-page"
className="space-y-12 mt-5"
>
<AppGrid
contentMain={<div className="space-y-8">
<div className="flex items-center gap-4 sm:gap-6">
<PhotoAvatar
photo={photoAvatar}
placeholder={<LuUser size={22} className="text-dim" />}
/>
<div
className={clsx('sm:flex items-center justify-between grow')}
>
<div>
<div className="font-bold">
{title || appText.about.titleDefault}
</div>
{subhead &&
<div>{subhead}</div>}
</div>
{lastUpdated && <div className={clsx('text-dim')}>
{appText.about.updated(
formatDistanceToNowStrict(lastUpdated),
)}
</div>}
</div>
{isUserSignedIn && <AdminAboutMenu />}
</div>
{descriptionHtml
? descriptionHtml
: isUserSignedIn &&
<Link
href={PATH_ADMIN_ABOUT_EDIT}
className={clsx(
'flex items-center justify-center gap-2.5',
'border border-dashed border-medium rounded-lg',
)}
>
<AdminEmptyState
icon={<LuCirclePlus size={22} />}
includeContainer={false}
className="gap-3! p-6!"
>
Add optional description
</AdminEmptyState>
</Link>}
<AnimateItems
className={clsx(
'grid gap-x-2 gap-y-6 grid-cols-2 lg:grid-cols-4',
)}
items={items}
/>
</div>} />
{photoHero &&
<PhotoLarge photo={photoHero} />}
</div>]}
/>
);
}