diff --git a/app/admin/components/page.tsx b/app/admin/components/page.tsx new file mode 100644 index 00000000..b1513dba --- /dev/null +++ b/app/admin/components/page.tsx @@ -0,0 +1,21 @@ +'use client'; + +import SiteGrid from '@/components/SiteGrid'; +import StatusIcon from '@/components/StatusIcon'; +import clsx from 'clsx/lite'; + +export default function ComponentsPage() { + return ( + + + + + + } + /> + ); +} diff --git a/src/admin/AdminAppConfigurationClient.tsx b/src/admin/AdminAppConfigurationClient.tsx index 0982188e..6f5b4da7 100644 --- a/src/admin/AdminAppConfigurationClient.tsx +++ b/src/admin/AdminAppConfigurationClient.tsx @@ -114,12 +114,10 @@ export default function AdminAppConfigurationClient({ const renderSubStatus = ( type: ComponentProps['type'], label: ReactNode, - iconClassName?: string, + iconClassName = 'translate-y-[3.5px]', ) => -
- - - +
+ {label} @@ -132,7 +130,7 @@ export default function AdminAppConfigurationClient({ renderSubStatus( type, renderEnvVars([variable]), - 'translate-y-[3px]', + 'translate-y-[4.5px]', ); const renderError = ({ @@ -165,498 +163,496 @@ export default function AdminAppConfigurationClient({ return ( <> -
- } + } + > + - - {databaseError && renderError({ - connection: { provider: 'Database', error: databaseError}, - })} - {hasVercelPostgres - ? renderSubStatus('checked', 'Vercel Postgres: connected') - : renderSubStatus('optional', <> - Vercel Postgres: - {' '} - - create store - - {' '} - and connect to project - )} - {hasDatabase && !hasVercelPostgres && - renderSubStatus('checked', <> - Postgres-compatible: connected - {' '} - (SSL {isPostgresSslEnabled ? 'enabled' : 'disabled'}) - )} - - - {storageError && renderError({ - connection: { provider: 'Storage', error: storageError}, - })} - {hasVercelBlobStorage - ? renderSubStatus('checked', 'Vercel Blob: connected') - : renderSubStatus('optional', <> - {labelForStorage('vercel-blob')}: - {' '} - - create store - - {' '} - and connect to project - , - )} - {hasCloudflareR2Storage - ? renderSubStatus('checked', 'Cloudflare R2: connected') - : renderSubStatus('optional', <> - {labelForStorage('cloudflare-r2')}: - {' '} - - create/configure bucket - - )} - {hasAwsS3Storage - ? renderSubStatus('checked', 'AWS S3: connected') - : renderSubStatus('optional', <> - {labelForStorage('aws-s3')}: - {' '} - - create/configure bucket - - )} - - - } - > - - Store auth secret in environment variable: - {!hasAuthSecret && -
- -
} - {renderEnvVars(['AUTH_SECRET'])} -
- - Store admin email/password - {' '} - in environment variables: - {renderEnvVars([ - 'ADMIN_EMAIL', - 'ADMIN_PASSWORD', - ])} - -
- } - > - - {!hasDomain && - renderWarning({message: - 'Not explicitly setting a domain may cause ' + - 'certain features to behave unexpectedly', - })} - Store in environment variable (seen in top-right nav): - {renderEnvVars(['NEXT_PUBLIC_SITE_DOMAIN'])} - - - Store in environment variable (seen in browser tab): - {renderEnvVars(['NEXT_PUBLIC_SITE_TITLE'])} - - - Store in environment variable (seen in nav, under title): - {renderEnvVars(['NEXT_PUBLIC_SITE_DESCRIPTION'])} - - - Store in environment variable (seen in grid sidebar): - {renderEnvVars(['NEXT_PUBLIC_SITE_ABOUT'])} - - - {!simplifiedView && <> - } - optional - > - - {aiError && renderError({ - connection: { provider: 'OpenAI', error: aiError}, - })} - Store your OpenAI secret key in order to add experimental support - for AI-generated text descriptions and enable an invisible field - called {'"Semantic Description"'} used to support CMD-K search: - {renderEnvVars(['OPENAI_SECRET_KEY'])} - - - {redisError && renderError({ - connection: { provider: 'Redis', error: redisError}, - })} - Create Upstash Redis store from storage tab - on Vercel dashboard and connect to this project - to enable rate limiting - - - Comma-separated fields to auto-generate when - uploading photos. Accepted values: title, caption, - tags, description, all, or none + {databaseError && renderError({ + connection: { provider: 'Database', error: databaseError}, + })} + {hasVercelPostgres + ? renderSubStatus('checked', 'Vercel Postgres: connected') + : renderSubStatus('optional', <> + Vercel Postgres: {' '} - (default: {'"title, tags, semantic"'}): - {renderEnvVars(['AI_TEXT_AUTO_GENERATED_FIELDS'])} - - - } - optional - > - - Set environment variable to {'"1"'} to make site more responsive - by enabling static optimization - (i.e., rendering pages and images at build time): - {renderSubStatusWithEnvVar( - arePhotosStaticallyOptimized ? 'checked' : 'optional', - 'NEXT_PUBLIC_STATICALLY_OPTIMIZE_PHOTOS', - )} - {renderSubStatusWithEnvVar( - arePhotoOGImagesStaticallyOptimized ? 'checked' : 'optional', - 'NEXT_PUBLIC_STATICALLY_OPTIMIZE_PHOTO_OG_IMAGES', - )} - {renderSubStatusWithEnvVar( - arePhotoCategoriesStaticallyOptimized ? 'checked' : 'optional', - 'NEXT_PUBLIC_STATICALLY_OPTIMIZE_PHOTO_CATEGORIES', - )} - {renderSubStatusWithEnvVar( + + create store + + {' '} + and connect to project + )} + {hasDatabase && !hasVercelPostgres && + renderSubStatus('checked', <> + Postgres-compatible: connected + {' '} + (SSL {isPostgresSslEnabled ? 'enabled' : 'disabled'}) + )} + + + {storageError && renderError({ + connection: { provider: 'Storage', error: storageError}, + })} + {hasVercelBlobStorage + ? renderSubStatus('checked', 'Vercel Blob: connected') + : renderSubStatus('optional', <> + {labelForStorage('vercel-blob')}: + {' '} + + create store + + {' '} + and connect to project + , + )} + {hasCloudflareR2Storage + ? renderSubStatus('checked', 'Cloudflare R2: connected') + : renderSubStatus('optional', <> + {labelForStorage('cloudflare-r2')}: + {' '} + + create/configure bucket + + )} + {hasAwsS3Storage + ? renderSubStatus('checked', 'AWS S3: connected') + : renderSubStatus('optional', <> + {labelForStorage('aws-s3')}: + {' '} + + create/configure bucket + + )} + + + } + > + + Store auth secret in environment variable: + {!hasAuthSecret && +
+ +
} + {renderEnvVars(['AUTH_SECRET'])} +
+ + Store admin email/password + {' '} + in environment variables: + {renderEnvVars([ + 'ADMIN_EMAIL', + 'ADMIN_PASSWORD', + ])} + +
+ } + > + + {!hasDomain && + renderWarning({message: + 'Not explicitly setting a domain may cause ' + + 'certain features to behave unexpectedly', + })} + Store in environment variable (seen in top-right nav): + {renderEnvVars(['NEXT_PUBLIC_SITE_DOMAIN'])} + + + Store in environment variable (seen in browser tab): + {renderEnvVars(['NEXT_PUBLIC_SITE_TITLE'])} + + + Store in environment variable (seen in nav, under title): + {renderEnvVars(['NEXT_PUBLIC_SITE_DESCRIPTION'])} + + + Store in environment variable (seen in grid sidebar): + {renderEnvVars(['NEXT_PUBLIC_SITE_ABOUT'])} + + + {!simplifiedView && <> + } + optional + > + + {aiError && renderError({ + connection: { provider: 'OpenAI', error: aiError}, + })} + Store your OpenAI secret key in order to add experimental support + for AI-generated text descriptions and enable an invisible field + called {'"Semantic Description"'} used to support CMD-K search: + {renderEnvVars(['OPENAI_SECRET_KEY'])} + + + {redisError && renderError({ + connection: { provider: 'Redis', error: redisError}, + })} + Create Upstash Redis store from storage tab + on Vercel dashboard and connect to this project + to enable rate limiting + + + Comma-separated fields to auto-generate when + uploading photos. Accepted values: title, caption, + tags, description, all, or none + {' '} + (default: {'"title, tags, semantic"'}): + {renderEnvVars(['AI_TEXT_AUTO_GENERATED_FIELDS'])} + + + } + optional + > + + Set environment variable to {'"1"'} to make site more responsive + by enabling static optimization + (i.e., rendering pages and images at build time): + {renderSubStatusWithEnvVar( + arePhotosStaticallyOptimized ? 'checked' : 'optional', + 'NEXT_PUBLIC_STATICALLY_OPTIMIZE_PHOTOS', + )} + {renderSubStatusWithEnvVar( + arePhotoOGImagesStaticallyOptimized ? 'checked' : 'optional', + 'NEXT_PUBLIC_STATICALLY_OPTIMIZE_PHOTO_OG_IMAGES', + )} + {renderSubStatusWithEnvVar( + arePhotoCategoriesStaticallyOptimized ? 'checked' : 'optional', + 'NEXT_PUBLIC_STATICALLY_OPTIMIZE_PHOTO_CATEGORIES', + )} + {renderSubStatusWithEnvVar( + // eslint-disable-next-line max-len + arePhotoCategoryOgImagesStaticallyOptimized ? 'checked' : 'optional', + 'NEXT_PUBLIC_STATICALLY_OPTIMIZE_PHOTO_CATEGORY_OG_IMAGES', + )} + + + Set environment variable to {'"1"'} to prevent + image uploads being compressed before storing: + {renderEnvVars(['NEXT_PUBLIC_PRESERVE_ORIGINAL_UPLOADS'])} + + + Set environment variable from {'"1-100"'} + {' '} + to control the quality of large photos + ({'"100"'} represents highest quality/largest size): + {renderEnvVars(['NEXT_PUBLIC_IMAGE_QUALITY'])} + + + Set environment variable to {'"1"'} to prevent + image blur data being stored and displayed: + {renderEnvVars(['NEXT_PUBLIC_BLUR_DISABLED'])} + + + } + optional + > + + {'Set environment variable to \'light\' or \'dark\''} + {' '} + to configure initial theme + {' '} + (defaults to {'\'system\''}): + {renderEnvVars(['NEXT_PUBLIC_DEFAULT_THEME'])} + + + Set environment variable to {'"1"'} to constrain the size + {' '} + of each photo, and display a surrounding border: + {renderEnvVars(['NEXT_PUBLIC_MATTE_PHOTOS'])} + + + } + optional + > + + Set environment variable to {'"1"'} to hide EXIF data: + {renderEnvVars(['NEXT_PUBLIC_HIDE_EXIF_DATA'])} + + + Set environment variable to {'"1"'} to hide + fullscreen photo zoom controls: + {renderEnvVars(['NEXT_PUBLIC_HIDE_ZOOM_CONTROLS'])} + + + Set environment variable to {'"1"'} to hide + taken at time from photo meta: + {renderEnvVars(['NEXT_PUBLIC_HIDE_TAKEN_AT_TIME'])} + + + Set environment variable to {'"1"'} to hide + {' '} + X (formerly Twitter) button from share modal: + {renderEnvVars(['NEXT_PUBLIC_HIDE_SOCIAL'])} + + + Set environment variable to {'"1"'} to prevent + simulations showing up in /grid sidebar and + CMD-K results: + {renderEnvVars(['NEXT_PUBLIC_HIDE_FILM_SIMULATIONS'])} + + + Set environment variable to {'"1"'} to hide footer link: + {renderEnvVars(['NEXT_PUBLIC_HIDE_REPO_LINK'])} + + + } + optional + > + + Set environment variable to {'"1"'} to show grid layout + on homepage: + {renderEnvVars(['NEXT_PUBLIC_GRID_HOMEPAGE'])} + + + Set environment variable to any number to enforce aspect ratio + {' '} + (default is {'"1"'}, i.e., square)—set to {'"0"'} to disable: + {renderEnvVars(['NEXT_PUBLIC_GRID_ASPECT_RATIO'])} + + + Set environment variable to {'"1"'} to ensure large thumbnails + on photo grid views (if not configured, density is based on + aspect ratio): + {renderEnvVars(['NEXT_PUBLIC_SHOW_LARGE_THUMBNAILS'])} + + + } + optional + > + + Set environment variable to {'"1"'} to disable + collection/display of location-based data: + {renderEnvVars(['NEXT_PUBLIC_GEO_PRIVACY'])} + + + Set environment variable to {'"1"'} to enable + public photo downloads for all visitors: + {renderEnvVars(['NEXT_PUBLIC_ALLOW_PUBLIC_DOWNLOADS'])} + + + Set environment variable to {'"1"'} to enable + a public API available at /api: + {renderEnvVars(['NEXT_PUBLIC_PUBLIC_API'])} + + + Set environment variable to {'"1"'} to prevent + priority order photo field affecting photo order: + {renderEnvVars(['NEXT_PUBLIC_IGNORE_PRIORITY_ORDER'])} + + + Set environment variable to {'"BOTTOM"'} to + keep OG image text bottom aligned (default is {'"top"'}): + {renderEnvVars(['NEXT_PUBLIC_OG_TEXT_ALIGNMENT'])} + + + {areInternalToolsEnabled && + } + optional + > + + Set environment variable to {'"1"'} to temporarily enable + features like photo matting, baseline grid, etc.: + {renderEnvVars(['ADMIN_DEBUG_TOOLS'])} Set environment variable to {'"1"'} to prevent - image uploads being compressed before storing: - {renderEnvVars(['NEXT_PUBLIC_PRESERVE_ORIGINAL_UPLOADS'])} + homepages from seeding infinite scroll on load: + {renderEnvVars(['ADMIN_DB_OPTIMIZE'])} - Set environment variable from {'"1-100"'} - {' '} - to control the quality of large photos - ({'"100"'} represents highest quality/largest size): - {renderEnvVars(['NEXT_PUBLIC_IMAGE_QUALITY'])} - - - Set environment variable to {'"1"'} to prevent - image blur data being stored and displayed: - {renderEnvVars(['NEXT_PUBLIC_BLUR_DISABLED'])} - - - } - optional - > - - {'Set environment variable to \'light\' or \'dark\''} - {' '} - to configure initial theme - {' '} - (defaults to {'\'system\''}): - {renderEnvVars(['NEXT_PUBLIC_DEFAULT_THEME'])} - - - Set environment variable to {'"1"'} to constrain the size - {' '} - of each photo, and display a surrounding border: - {renderEnvVars(['NEXT_PUBLIC_MATTE_PHOTOS'])} - - - } - optional - > - - Set environment variable to {'"1"'} to hide EXIF data: - {renderEnvVars(['NEXT_PUBLIC_HIDE_EXIF_DATA'])} - - - Set environment variable to {'"1"'} to hide - fullscreen photo zoom controls: - {renderEnvVars(['NEXT_PUBLIC_HIDE_ZOOM_CONTROLS'])} - - - Set environment variable to {'"1"'} to hide - taken at time from photo meta: - {renderEnvVars(['NEXT_PUBLIC_HIDE_TAKEN_AT_TIME'])} - - - Set environment variable to {'"1"'} to hide - {' '} - X (formerly Twitter) button from share modal: - {renderEnvVars(['NEXT_PUBLIC_HIDE_SOCIAL'])} - - - Set environment variable to {'"1"'} to prevent - simulations showing up in /grid sidebar and - CMD-K results: - {renderEnvVars(['NEXT_PUBLIC_HIDE_FILM_SIMULATIONS'])} - - - Set environment variable to {'"1"'} to hide footer link: - {renderEnvVars(['NEXT_PUBLIC_HIDE_REPO_LINK'])} - - - } - optional - > - - Set environment variable to {'"1"'} to show grid layout - on homepage: - {renderEnvVars(['NEXT_PUBLIC_GRID_HOMEPAGE'])} - - - Set environment variable to any number to enforce aspect ratio - {' '} - (default is {'"1"'}, i.e., square)—set to {'"0"'} to disable: - {renderEnvVars(['NEXT_PUBLIC_GRID_ASPECT_RATIO'])} - - - Set environment variable to {'"1"'} to ensure large thumbnails - on photo grid views (if not configured, density is based on - aspect ratio): - {renderEnvVars(['NEXT_PUBLIC_SHOW_LARGE_THUMBNAILS'])} - - - } - optional - > - - Set environment variable to {'"1"'} to disable - collection/display of location-based data: - {renderEnvVars(['NEXT_PUBLIC_GEO_PRIVACY'])} - - Set environment variable to {'"1"'} to enable - public photo downloads for all visitors: - {renderEnvVars(['NEXT_PUBLIC_ALLOW_PUBLIC_DOWNLOADS'])} + console output for all sql queries: + {renderEnvVars(['ADMIN_SQL_DEBUG'])} - - Set environment variable to {'"1"'} to enable - a public API available at /api: - {renderEnvVars(['NEXT_PUBLIC_PUBLIC_API'])} - - - Set environment variable to {'"1"'} to prevent - priority order photo field affecting photo order: - {renderEnvVars(['NEXT_PUBLIC_IGNORE_PRIORITY_ORDER'])} - - - Set environment variable to {'"BOTTOM"'} to - keep OG image text bottom aligned (default is {'"top"'}): - {renderEnvVars(['NEXT_PUBLIC_OG_TEXT_ALIGNMENT'])} - - - {areInternalToolsEnabled && - } - optional - > - - Set environment variable to {'"1"'} to temporarily enable - features like photo matting, baseline grid, etc.: - {renderEnvVars(['ADMIN_DEBUG_TOOLS'])} - - - Set environment variable to {'"1"'} to prevent - homepages from seeding infinite scroll on load: - {renderEnvVars(['ADMIN_DB_OPTIMIZE'])} - - - Set environment variable to {'"1"'} to enable - console output for all sql queries: - {renderEnvVars(['ADMIN_SQL_DEBUG'])} - - } - } -
+ } + }
Changes to environment variables require a redeploy diff --git a/src/admin/AdminInfoPage.tsx b/src/admin/AdminInfoPage.tsx index 3e046c22..d724b13e 100644 --- a/src/admin/AdminInfoPage.tsx +++ b/src/admin/AdminInfoPage.tsx @@ -1,5 +1,6 @@ import Container from '@/components/Container'; import SiteGrid from '@/components/SiteGrid'; +import clsx from 'clsx/lite'; import { ReactNode } from 'react'; export default function AdminInfoPage({ @@ -22,7 +23,10 @@ export default function AdminInfoPage({ {accessory}
-
+
{children}
diff --git a/src/admin/insights/AdminAppInsightsClient.tsx b/src/admin/insights/AdminAppInsightsClient.tsx index b318c1ba..3e5fc375 100644 --- a/src/admin/insights/AdminAppInsightsClient.tsx +++ b/src/admin/insights/AdminAppInsightsClient.tsx @@ -120,7 +120,7 @@ export default function AdminAppInsightsClient({ ; return ( -
+ <> {(codeMeta || debug) && <> {codeMeta?.didError @@ -396,6 +396,6 @@ export default function AdminAppInsightsClient({ content={descriptionWithSpaces} />} -
+ ); } diff --git a/src/app/paths.ts b/src/app/paths.ts index 50834d5e..22d49cde 100644 --- a/src/app/paths.ts +++ b/src/app/paths.ts @@ -41,6 +41,7 @@ export const PATH_ADMIN_TAGS = `${PATH_ADMIN}/tags`; export const PATH_ADMIN_CONFIGURATION = `${PATH_ADMIN}/configuration`; export const PATH_ADMIN_INSIGHTS = `${PATH_ADMIN}/insights`; export const PATH_ADMIN_BASELINE = `${PATH_ADMIN}/baseline`; +export const PATH_ADMIN_COMPONENTS = `${PATH_ADMIN}/components`; // Debug paths export const PATH_OG_ALL = `${PATH_OG}/all`; @@ -60,6 +61,8 @@ export const PATHS_ADMIN = [ PATH_ADMIN_UPLOADS, PATH_ADMIN_TAGS, PATH_ADMIN_CONFIGURATION, + PATH_ADMIN_BASELINE, + PATH_ADMIN_COMPONENTS, ]; export const PATHS_TO_CACHE = [ diff --git a/src/components/ChecklistGroup.tsx b/src/components/ChecklistGroup.tsx index 2cf3a751..ccb26d05 100644 --- a/src/components/ChecklistGroup.tsx +++ b/src/components/ChecklistGroup.tsx @@ -4,6 +4,7 @@ import ExperimentalBadge from './ExperimentalBadge'; import Badge from './Badge'; import ResponsiveText from './primitives/ResponsiveText'; import { parameterize } from '@/utility/string'; +import ScoreCard from './ScoreCard'; export default function ChecklistGroup({ title, @@ -23,37 +24,29 @@ export default function ChecklistGroup({ const slug = parameterize(title); return ( - + + {icon} + + + {title} + + {optional && + + Optional + } + {experimental && + } + + }> + {children} + ); } diff --git a/src/components/ChecklistRow.tsx b/src/components/ChecklistRow.tsx index 47341bbd..fe0ca90f 100644 --- a/src/components/ChecklistRow.tsx +++ b/src/components/ChecklistRow.tsx @@ -2,6 +2,7 @@ import { ReactNode } from 'react'; import { clsx } from 'clsx/lite'; import StatusIcon from './StatusIcon'; import ExperimentalBadge from './ExperimentalBadge'; +import ScoreCardRow from './ScoreCardRow'; export default function ChecklistRow({ title, @@ -21,31 +22,28 @@ export default function ChecklistRow({ children: ReactNode }) { return ( -
- -
+ />} + content={<>
{title} {experimental && }
-
+
{children}
-
-
+ } + /> ); } diff --git a/src/components/ScoreCard.tsx b/src/components/ScoreCard.tsx index c01d7840..de9b2763 100644 --- a/src/components/ScoreCard.tsx +++ b/src/components/ScoreCard.tsx @@ -6,15 +6,15 @@ export default function ScoreCard({ children, className, }: { - title?: string, + title?: ReactNode, children: ReactNode, className?: string, }) { return ( -
+
{title &&
diff --git a/src/components/ScoreCardRow.tsx b/src/components/ScoreCardRow.tsx index 0600e5dd..6eb06fd8 100644 --- a/src/components/ScoreCardRow.tsx +++ b/src/components/ScoreCardRow.tsx @@ -44,7 +44,7 @@ export default function ScoreCardRow({
{typeof content === 'function' ? content(isExpanded) diff --git a/src/components/StatusIcon.tsx b/src/components/StatusIcon.tsx index d7031f48..8e9c880e 100644 --- a/src/components/StatusIcon.tsx +++ b/src/components/StatusIcon.tsx @@ -4,13 +4,16 @@ import { BiSolidXSquare, } from 'react-icons/bi'; import Spinner from './Spinner'; +import clsx from 'clsx/lite'; export default function StatusIcon({ type, loading, + className, }: { type: 'checked' | 'missing' | 'warning' | 'optional' loading?: boolean + className?: string }) { const getIcon = () => { switch (type) { @@ -21,13 +24,13 @@ export default function StatusIcon({ />; case 'missing': return ; case 'warning': return ; case 'optional': return + {loading - ?
- -
- : getIcon()} -
+ ? + + + : + {getIcon()} + } + ); } diff --git a/src/components/cmdk/CommandKClient.tsx b/src/components/cmdk/CommandKClient.tsx index a8c01399..817ddc0f 100644 --- a/src/components/cmdk/CommandKClient.tsx +++ b/src/components/cmdk/CommandKClient.tsx @@ -13,6 +13,7 @@ import { } from 'react'; import { PATH_ADMIN_BASELINE, + PATH_ADMIN_COMPONENTS, PATH_ADMIN_CONFIGURATION, PATH_ADMIN_INSIGHTS, PATH_ADMIN_PHOTOS, @@ -376,6 +377,9 @@ export default function CommandKClient({ ? [{ label: 'Baseline Overview', path: PATH_ADMIN_BASELINE, + }, { + label: 'Components Overview', + path: PATH_ADMIN_COMPONENTS, }] : []) .concat({