Leverage adjacent meta for about description

This commit is contained in:
Sam Becker 2026-03-01 22:36:11 -06:00
parent be859f380e
commit 8569347c27
6 changed files with 84 additions and 77 deletions

View File

@ -1,12 +1,11 @@
import AdminAboutEditPage from '@/about/AdminAboutEditPage';
import { getAbout } from '@/about/query';
import { getAboutData } from '@/about/data';
import { PRESERVE_ORIGINAL_UPLOADS } from '@/app/config';
import { feedQueryOptions } from '@/feed';
import {
getPhotosCached,
getPhotosMetaCached,
} from '@/photo/cache';
import { getPhoto } from '@/photo/query';
import { TAG_FAVS } from '@/tag';
const PHOTO_CHOOSER_QUERY_OPTIONS = feedQueryOptions({
@ -25,24 +24,7 @@ export default async function AboutEditPage() {
photosCount,
photosFavs,
] = await Promise.all([
getAbout()
.then(async about => {
const photoAvatar = about?.photoIdAvatar
? await getPhoto(about?.photoIdAvatar ?? '', true)
.catch(() => undefined)
: undefined;
const photoHero = about?.photoIdHero
? await getPhoto(about?.photoIdHero ?? '', true)
.catch(() => undefined)
: undefined;
return {
about,
photoAvatar,
photoHero,
};
})
getAboutData()
.catch(() => ({
about: undefined,
photoAvatar: undefined,

View File

@ -1,5 +1,6 @@
import { getDescriptionWithFallback } from '@/about';
import AboutPageClient from '@/about/AboutPageClient';
import { getAboutCached } from '@/about/cache';
import { getAboutDataCached } from '@/about/data';
import { SHOW_ABOUT_PAGE } from '@/app/config';
import { PATH_ROOT } from '@/app/path';
import { getDataForCategoriesCached } from '@/category/cache';
@ -7,11 +8,7 @@ import {
getLastModifiedForCategories,
NULL_CATEGORY_DATA,
} from '@/category/data';
import {
getPhotoCached,
getPhotosCached,
getPhotosMetaCached,
} from '@/photo/cache';
import { getPhotosMetaCached } from '@/photo/cache';
import PhotosEmptyState from '@/photo/PhotosEmptyState';
import { getAllPhotoIdsWithUpdatedAt } from '@/photo/query';
import { TAG_FAVS } from '@/tag';
@ -34,26 +31,8 @@ export default async function AboutPage() {
photos,
categories,
] = await Promise.all([
getAboutCached()
.then(async about => {
const photoAvatar = await (about?.photoIdAvatar
? getPhotoCached(about?.photoIdAvatar ?? '', true)
: undefined);
const photoHero = await (about?.photoIdHero
? getPhotoCached(about?.photoIdHero ?? '', true)
// Fall back to favorite photos if no hero photo is set
: getPhotosCached({ tag: TAG_FAVS, limit: 1 })
.then(photos => photos.length > 0
? photos[0]
// Fall back to oldest photo if no favorite photos exist
: getPhotosCached({ limit: 1, sortBy: 'takenAtAsc' })
.then(photos => photos[0])));
return {
about,
photoAvatar,
photoHero,
};
}).catch(() => ({
getAboutDataCached()
.catch(() => ({
about: undefined,
photoAvatar: undefined,
photoHero: undefined,
@ -63,10 +42,15 @@ export default async function AboutPage() {
getDataForCategoriesCached().catch(() => (NULL_CATEGORY_DATA)),
]);
const description = about?.description
? <div dangerouslySetInnerHTML={{
__html: safelyParseFormattedHtml(about.description),
}} />
const description = getDescriptionWithFallback(about);
const descriptionHtml = description
? <div
className="text-medium [&>*>a]:underline"
dangerouslySetInnerHTML={{
__html: safelyParseFormattedHtml(description),
}}
/>
: undefined;
const {
@ -88,7 +72,7 @@ export default async function AboutPage() {
? <AboutPageClient
title={about?.title}
subhead={about?.subhead}
description={description}
descriptionHtml={descriptionHtml}
photosCount={photosMeta?.count}
photosOldest={photosMeta?.dateRange?.start}
photoAvatar={photoAvatar}

View File

@ -28,7 +28,7 @@ import AdminEmptyState from '@/admin/AdminEmptyState';
export default function AboutPageClient({
title,
subhead,
description,
descriptionHtml,
photosCount = 0,
photosOldest,
photoAvatar,
@ -43,7 +43,7 @@ export default function AboutPageClient({
}: {
title?: string
subhead?: string
description?: ReactNode
descriptionHtml?: ReactNode
photosCount?: number
photosOldest?: string
photoAvatar?: Photo
@ -178,26 +178,24 @@ export default function AboutPageClient({
</div>
{isUserSignedIn && <AdminAboutMenu />}
</div>
{description
? <div className="text-medium [&>*>a]:underline">
{description}
</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!"
<Link
href={PATH_ADMIN_ABOUT_EDIT}
className={clsx(
'flex items-center justify-center gap-2.5',
'border border-dashed border-medium rounded-lg',
)}
>
Add optional description
</AdminEmptyState>
</Link>}
<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',

View File

@ -3,7 +3,7 @@
import { PATH_ABOUT } from '@/app/path';
import LinkWithStatus from '@/components/LinkWithStatus';
import { useState } from 'react';
import { About, AboutInsert } from '.';
import { About, AboutInsert, getDescriptionWithFallback } from '.';
import FieldsetWithStatus from '@/components/FieldsetWithStatus';
import AdminChildPage from '@/components/AdminChildPage';
import { updateAboutAction } from './actions';
@ -32,8 +32,6 @@ export default function AdminAboutEditPage({
const [aboutForm, setAboutForm] = useState<Partial<AboutInsert>>(about ?? {});
const convertUrlToPhotoId = (url?: string) => url?.split('/').pop();
return (
<AdminChildPage
backPath={PATH_ABOUT}
@ -48,7 +46,7 @@ export default function AdminAboutEditPage({
<FieldsetPhotoChooser
id="photoIdAvatar"
label="Avatar"
value={aboutForm?.photoIdAvatar ?? ''}
value={aboutForm?.photoIdAvatar || photoAvatar?.id || ''}
onChange={photoIdAvatar => setAboutForm(form =>
({ ...form, photoIdAvatar }))}
photo={photoAvatar}
@ -74,15 +72,16 @@ export default function AdminAboutEditPage({
label="Description"
type="textarea"
value={aboutForm?.description ?? ''}
placeholder={getDescriptionWithFallback(about)}
onChange={description => setAboutForm(form =>
({ ...form, description }))}
/>
<FieldsetPhotoChooser
id="photoIdHero"
label="Hero"
value={aboutForm?.photoIdHero ?? ''}
value={aboutForm?.photoIdHero || photoHero?.id || ''}
onChange={photoIdHero => setAboutForm(form =>
({ ...form, photoIdHero: convertUrlToPhotoId(photoIdHero) }))}
({ ...form, photoIdHero }))}
photo={photoHero}
photos={photos}
photosCount={photosCount}

37
src/about/data.ts Normal file
View File

@ -0,0 +1,37 @@
import { getPhotoCached, getPhotosCached } from '@/photo/cache';
import { About } from '.';
import { TAG_FAVS } from '@/tag';
import { getAbout } from './query';
import { getAboutCached } from './cache';
const getAboutAvatar = (about?: About) =>
about?.photoIdAvatar
? getPhotoCached(about?.photoIdAvatar ?? '', true)
: undefined;
const getAboutHero = (about?: About) =>
about?.photoIdHero
? getPhotoCached(about?.photoIdHero ?? '', true)
// Fall back to favorite photos if no hero photo is set
: getPhotosCached({ tag: TAG_FAVS, limit: 1 })
.then(photos => photos.length > 0
? photos[0]
// Fall back to oldest photo if no favorite photos exist
: getPhotosCached({ limit: 1, sortBy: 'takenAtAsc' })
.then(photos => photos[0]));
export const getAboutData = () =>
getAbout()
.then(async about => ({
about,
photoAvatar: await getAboutAvatar(about),
photoHero: await getAboutHero(about),
}));
export const getAboutDataCached = () =>
getAboutCached()
.then(async about => ({
about,
photoAvatar: await getAboutAvatar(about),
photoHero: await getAboutHero(about),
}));

View File

@ -1,3 +1,5 @@
import { META_DESCRIPTION, SIDEBAR_TEXT } from '@/app/config';
export interface AboutInsert {
id: number
title?: string
@ -11,3 +13,8 @@ export interface About extends AboutInsert {
createdAt: Date
updatedAt: Date
}
export const getDescriptionWithFallback = (about?: About) =>
about?.description ||
META_DESCRIPTION ||
SIDEBAR_TEXT;