'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, isProModeEnabled, isGridHomepageEnabled: isGridFirst, isStaticallyOptimized, arePagesStaticallyOptimized, areOGImagesStaticallyOptimized, 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 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 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 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'}
}
); }