'use client'; import { ComponentProps, ReactNode, } from 'react'; import { clsx } from 'clsx/lite'; import ChecklistRow from '../components/ChecklistRow'; import { FiExternalLink } from 'react-icons/fi'; import { BiCog, BiCopy, BiData, BiLockAlt, BiPencil, } from 'react-icons/bi'; import Container from '@/components/Container'; import Checklist from '@/components/Checklist'; import { toastSuccess } from '@/toast'; import { ConfigChecklistStatus } from './config'; import StatusIcon from '@/components/StatusIcon'; import { labelForStorage } from '@/services/storage'; import { HiSparkles } from 'react-icons/hi'; import LoaderButton from '@/components/primitives/LoaderButton'; import { testConnectionsAction } from '@/admin/actions'; import ErrorNote from '@/components/ErrorNote'; import Spinner from '@/components/Spinner'; import WarningNote from '@/components/WarningNote'; export default function SiteChecklistClient({ // Config checklist hasDatabase, isPostgresSslEnabled, hasVercelPostgres, hasVercelKv, hasStorageProvider, hasVercelBlobStorage, hasCloudflareR2Storage, hasAwsS3Storage, hasMultipleStorageProviders, currentStorage, hasAuthSecret, hasAdminUser, hasDomain, hasTitle, hasDescription, hasAbout, hasDefaultTheme, showRepoLink, showSocial, showFilmSimulations, showExifInfo, defaultTheme, areOriginalUploadsPreserved, isGridHomepageEnabled, isStaticallyOptimized, arePhotosStaticallyOptimized, arePhotoOGImagesStaticallyOptimized, arePhotoCategoriesStaticallyOptimized, arePhotosMatted, isBlurEnabled, isGeoPrivacyEnabled, isPriorityOrderEnabled, isAiTextGenerationEnabled, aiTextAutoGeneratedFields, hasAiTextAutoGeneratedFields, isPublicApiEnabled, arePublicDownloadsEnabled, isOgTextBottomAligned, gridAspectRatio, hasGridAspectRatio, gridDensity, hasGridDensityPreference, // Connection status databaseError, storageError, kvError, aiError, // Component props simplifiedView, isTestingConnections, secret, baseUrl, commitSha, commitMessage, }: ConfigChecklistStatus & Partial>> & { simplifiedView?: boolean isTestingConnections?: boolean secret?: string }) { const renderLink = (href: string, text: string, external = true) => <> {text} {external && <>   } ; const renderCopyButton = (label: string, text?: string, subtle?: boolean) => } className={clsx( 'translate-y-[2px]', subtle && 'text-gray-300 dark:text-gray-700', )} onClick={text ? () => { navigator.clipboard.writeText(text); toastSuccess(`${label} copied to clipboard`); } : undefined} styleAs="link" disabled={!text} />; const renderEnvVar = ( variable: string, minimal?: boolean, ) =>
`{variable}` {!minimal && renderCopyButton(variable, variable, true)}
; const renderEnvVars = (variables: string[]) =>
{variables.map(envVar => renderEnvVar(envVar))}
; const renderSubStatus = ( type: ComponentProps['type'], label: ReactNode, iconClassName?: string, ) =>
{label}
; const renderSubStatusWithEnvVar = ( type: ComponentProps['type'], variable: string, ) => renderSubStatus( type, renderEnvVars([variable]), 'translate-y-[5px]', ); const renderError = ({ connection, message, }: { connection?: { provider: string, error: string } message?: string }) => {connection && <> {connection.provider} connection error: {`"${connection.error}"`} } {message} ; const renderWarning = ({ connection, message, }: { connection?: { provider: string, error: string } message?: string }) => {connection && <> {connection.provider} connection error: {`"${connection.error}"`} } {message} ; return (
} > {databaseError && renderError({ connection: { provider: 'Database', error: databaseError}, })} {hasVercelPostgres ? renderSubStatus('checked', 'Vercel Postgres: connected') : renderSubStatus('optional', <> Vercel Postgres: {' '} {renderLink( // eslint-disable-next-line max-len 'https://vercel.com/docs/storage/vercel-postgres/quickstart#create-a-postgres-database', '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')}: {' '} {renderLink( // eslint-disable-next-line max-len 'https://vercel.com/docs/storage/vercel-blob/quickstart#create-a-blob-store', 'create store', )} {' '} and connect to project , )} {hasCloudflareR2Storage ? renderSubStatus('checked', 'Cloudflare R2: connected') : renderSubStatus('optional', <> {labelForStorage('cloudflare-r2')}: {' '} {renderLink( 'https://github.com/sambecker/exif-photo-blog#cloudflare-r2', 'create/configure bucket', )} )} {hasAwsS3Storage ? renderSubStatus('checked', 'AWS S3: connected') : renderSubStatus('optional', <> {labelForStorage('aws-s3')}: {' '} {renderLink( 'https://github.com/sambecker/exif-photo-blog#aws-s3', 'create/configure bucket', )} )} } > Store auth secret in environment variable: {!hasAuthSecret &&
{secret ? {secret} : }
{renderCopyButton('Secret', secret)}
} {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 && <> } experimental 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'])} {kvError && renderError({ connection: { provider: 'Vercel KV', error: kvError}, })} {renderLink( // eslint-disable-next-line max-len 'https://vercel.com/docs/storage/vercel-kv/quickstart#create-a-kv-database', 'Create Vercel KV store', )} {' '} and connect to project in order to enable rate limiting Comma-separated fields to auto-generate when uploading photos. Accepted values: title, caption, tags, description, all, or none (default is {'"all"'}): {renderEnvVars(['AI_TEXT_AUTO_GENERATED_FIELDS'])} } optional > Set environment variable to {'"1"'} to show grid layout on homepage: {renderEnvVars(['NEXT_PUBLIC_GRID_HOMEPAGE'])} {'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 prevent image uploads being optimized before storing: {renderEnvVars(['NEXT_PUBLIC_PRESERVE_ORIGINAL_UPLOADS'])} Set environment variable to {'"1"'} to enable static optimization, i.e., render 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', )} Set environment variable to {'"1"'} to constrain the size {' '} of each photo, and enable a surrounding border: {renderEnvVars(['NEXT_PUBLIC_MATTE_PHOTOS'])} Set environment variable to {'"1"'} to prevent image blur data being stored and displayed: {renderEnvVars(['NEXT_PUBLIC_BLUR_DISABLED'])} Set environment variable to {'"1"'} to disable collection/display of location-based data: {renderEnvVars(['NEXT_PUBLIC_GEO_PRIVACY'])} Set environment variable to {'"1"'} to hide footer link: {renderEnvVars(['NEXT_PUBLIC_HIDE_REPO_LINK'])} 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 {'"1"'} to hide {' '} X 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 EXIF data: {renderEnvVars(['NEXT_PUBLIC_HIDE_EXIF_DATA'])} 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 configuration): {renderEnvVars(['NEXT_PUBLIC_SHOW_LARGE_THUMBNAILS'])} Set environment variable to {'"BOTTOM"'} to keep OG image text bottom aligned (default is {'"top"'}): {renderEnvVars(['NEXT_PUBLIC_OG_TEXT_ALIGNMENT'])} }
Changes to environment variables require a redeploy or reboot of local dev server
{!simplifiedView &&
Domain    {baseUrl || 'Not Defined'}
Commit    {commitSha ? {commitSha} : 'Not Found'}
}
); }