'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 InfoBlock from '@/components/InfoBlock'; 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'; export default function SiteChecklistClient({ // Config checklist hasDatabase, isPostgresSslEnabled, hasVercelPostgres, hasVercelKv, hasStorageProvider, hasVercelBlobStorage, hasCloudflareR2Storage, hasAwsS3Storage, hasMultipleStorageProviders, currentStorage, hasAuthSecret, hasAdminUser, hasDomain, hasTitle, hasDescription, showRepoLink, showSocial, showFilmSimulations, showExifInfo, isProModeEnabled, isStaticallyOptimized, arePagesStaticallyOptimized, areOGImagesStaticallyOptimized, arePhotosMatted, isBlurEnabled, isGeoPrivacyEnabled, isPriorityOrderEnabled, isAiTextGenerationEnabled, aiTextAutoGeneratedFields, hasAiTextAutoGeneratedFields, isPublicApiEnabled, isOgTextBottomAligned, gridAspectRatio, hasGridAspectRatio, // Connection status databaseError, storageError, kvError, aiError, // Component props simplifiedView, isTestingConnections, secret, baseUrl, commitSha, }: 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 renderConnectionError = (provider: string, error: string) => {provider} connection error: {`"${error}"`} ; return (
} > {databaseError && renderConnectionError('Database', 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 && renderConnectionError('Storage', 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', ])}
} > Store in environment variable (displayed in top-right nav): {renderEnvVars(['NEXT_PUBLIC_SITE_DOMAIN'])} Store in environment variable (used in page titles): {renderEnvVars(['NEXT_PUBLIC_SITE_TITLE'])} Store in environment variable (mainly used for OG meta): {renderEnvVars(['NEXT_PUBLIC_SITE_DESCRIPTION'])} {!simplifiedView && <> } experimental optional > {aiError && renderConnectionError('OpenAI', 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 && renderConnectionError('Vercel KV', 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 enable higher quality image storage: {renderEnvVars(['NEXT_PUBLIC_PRO_MODE'])} Set environment variable to {'"1"'} to enable static optimization, i.e., rendering pages and images at build time: {renderSubStatus( arePagesStaticallyOptimized ? 'checked' : 'optional', renderEnvVars(['NEXT_PUBLIC_STATICALLY_OPTIMIZE_PAGES']), 'translate-y-[3.5px]', )} {renderSubStatus( areOGImagesStaticallyOptimized ? 'checked' : 'optional', renderEnvVars(['NEXT_PUBLIC_STATICALLY_OPTIMIZE_OG_IMAGES']), 'translate-y-[3.5px]', )} 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 prevent priority order photo field affecting photo order {renderEnvVars(['NEXT_PUBLIC_IGNORE_PRIORITY_ORDER'])} Set environment variable to {'"1"'} to enable a public API available at /api: {renderEnvVars(['NEXT_PUBLIC_PUBLIC_API'])} Set environment variable to {'"1"'} to hide footer link: {renderEnvVars(['NEXT_PUBLIC_HIDE_REPO_LINK'])} 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 search 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 {'"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 &&
Base Url    {baseUrl || 'Not Defined'}
Commit      {commitSha || 'Not Found'}
}
); }