diff --git a/src/admin/actions.ts b/src/admin/actions.ts index dbd6f86c..8fe7665a 100644 --- a/src/admin/actions.ts +++ b/src/admin/actions.ts @@ -1,6 +1,7 @@ 'use server'; import { runAuthenticatedAdminServerAction } from '@/auth'; +import { testKvConnection } from '@/services/kv'; import { testOpenAiConnection } from '@/services/openai'; import { testDatabaseConnection } from '@/services/postgres'; import { testStorageConnection } from '@/services/storage'; @@ -11,7 +12,9 @@ const scanForError = ( promise: () => Promise ): Promise => shouldCheck - ? promise().then(() => '').catch(error => error.message) + ? promise() + .then(() => '') + .catch(error => error.message) : Promise.resolve(''); export const testConnectionsAction = async () => @@ -19,22 +22,26 @@ export const testConnectionsAction = async () => const { hasDatabase, hasStorageProvider, + hasVercelKv, isAiTextGenerationEnabled, } = CONFIG_CHECKLIST_STATUS; const [ databaseError, storageError, + kvError, aiError, ] = await Promise.all([ scanForError(hasDatabase, testDatabaseConnection), scanForError(hasStorageProvider, testStorageConnection), + scanForError(hasVercelKv, testKvConnection), scanForError(isAiTextGenerationEnabled, testOpenAiConnection), ]); return { databaseError, storageError, + kvError, aiError, }; }); diff --git a/src/components/ChecklistRow.tsx b/src/components/ChecklistRow.tsx index 9680d3cf..b491a0e1 100644 --- a/src/components/ChecklistRow.tsx +++ b/src/components/ChecklistRow.tsx @@ -13,7 +13,7 @@ export default function ChecklistRow({ }: { title: string status: boolean - isPending: boolean + isPending?: boolean optional?: boolean experimental?: boolean children: ReactNode diff --git a/src/services/kv.ts b/src/services/kv.ts new file mode 100644 index 00000000..4e5fd7a6 --- /dev/null +++ b/src/services/kv.ts @@ -0,0 +1,3 @@ +import { kv } from '@vercel/kv'; + +export const testKvConnection = () => kv.get('test'); diff --git a/src/site/SiteChecklist.tsx b/src/site/SiteChecklist.tsx index 891a74b5..c118e769 100644 --- a/src/site/SiteChecklist.tsx +++ b/src/site/SiteChecklist.tsx @@ -1,21 +1,20 @@ -import { generateAuthSecret } from '@/auth'; -import SiteChecklistClient from './SiteChecklistClient'; +import { Suspense } from 'react'; import { CONFIG_CHECKLIST_STATUS } from '@/site/config'; -import { testConnectionsAction } from '@/admin/actions'; +import SiteChecklistServer from './SiteChecklistServer'; +import SiteChecklistClient from './SiteChecklistClient'; -export default async function SiteChecklist({ +export default function SiteChecklist({ simplifiedView, }: { simplifiedView?: boolean }) { - const secret = await generateAuthSecret(); - const connectionErrors = await testConnectionsAction().catch(() => ({})); return ( - + }} /> }> + + ); } diff --git a/src/site/SiteChecklistClient.tsx b/src/site/SiteChecklistClient.tsx index ae19e82d..9358a108 100644 --- a/src/site/SiteChecklistClient.tsx +++ b/src/site/SiteChecklistClient.tsx @@ -1,11 +1,9 @@ 'use client'; -import { +import { ComponentProps, ReactNode, - useTransition, } from 'react'; -import { useRouter } from 'next/navigation'; import { clsx } from 'clsx/lite'; import ChecklistRow from '../components/ChecklistRow'; import { FiExternalLink } from 'react-icons/fi'; @@ -15,7 +13,6 @@ import { BiData, BiLockAlt, BiPencil, - BiRefresh, } from 'react-icons/bi'; import InfoBlock from '@/components/InfoBlock'; import Checklist from '@/components/Checklist'; @@ -27,13 +24,14 @@ 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, + isPostgresSslEnabled, hasVercelPostgres, - hasVercelKV, + hasVercelKv, hasStorageProvider, hasVercelBlobStorage, hasCloudflareR2Storage, @@ -66,29 +64,18 @@ export default function SiteChecklistClient({ // Connection status databaseError, storageError, + kvError, aiError, // Component props simplifiedView, - showRefreshButton, + isTestingConnections, secret, }: ConfigChecklistStatus & Partial>> & { simplifiedView?: boolean - showRefreshButton?: boolean - secret: string + isTestingConnections?: boolean + secret?: string }) { - const router = useRouter(); - - const [isPendingPage, startTransitionPage] = useTransition(); - const [isPendingSecret, startTransitionSecret] = useTransition(); - - const refreshPage = () => { - startTransitionPage(router.refresh); - }; - const refreshSecret = () => { - startTransitionSecret(router.refresh); - }; - const renderLink = (href: string, text: string, external = true) => <> } ; - const renderCopyButton = (label: string, text: string, subtle?: boolean) => + const renderCopyButton = (label: string, text?: string, subtle?: boolean) => } className={clsx( 'translate-y-[2px]', subtle && 'text-gray-300 dark:text-gray-700', )} - onClick={() => { - navigator.clipboard.writeText(text); - toastSuccess(`${label} copied to clipboard`); - }} + onClick={text + ? () => { + navigator.clipboard.writeText(text); + toastSuccess(`${label} copied to clipboard`); + } + : undefined} styleAs="link" + disabled={!text} />; const renderEnvVar = ( @@ -179,9 +169,11 @@ export default function SiteChecklistClient({ icon={} > {databaseError && renderConnectionError('Database', databaseError)} @@ -202,18 +194,21 @@ export default function SiteChecklistClient({ renderSubStatus('checked', <> Postgres-compatible: connected {' '} - (SSL {isPostgresSSLEnabled ? 'enabled' : 'disabled'}) + (SSL {isPostgresSslEnabled ? 'enabled' : 'disabled'}) )} {storageError && renderConnectionError('Storage', storageError)} @@ -258,9 +253,11 @@ export default function SiteChecklistClient({ icon={} > Store auth secret in environment variable: {!hasAuthSecret && @@ -269,16 +266,9 @@ export default function SiteChecklistClient({
- {secret} + {secret ? {secret} : }
{renderCopyButton('Secret', secret)} - } - onClick={refreshSecret} - isLoading={isPendingSecret} - spinnerColor="text" - styleAs="link" - />
@@ -288,7 +278,6 @@ export default function SiteChecklistClient({ Store admin email/password {' '} @@ -307,7 +296,6 @@ export default function SiteChecklistClient({ Store in environment variable (used in page titles): @@ -316,7 +304,6 @@ export default function SiteChecklistClient({ Store in environment variable (displayed in top-right nav): @@ -331,9 +318,11 @@ export default function SiteChecklistClient({ optional > {aiError && @@ -344,11 +333,15 @@ export default function SiteChecklistClient({ {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', @@ -361,7 +354,6 @@ export default function SiteChecklistClient({ // eslint-disable-next-line max-len title={`Auto-generated fields: ${aiTextAutoGeneratedFields.join(', ')}`} status={hasAiTextAutoGeneratedFields} - isPending={isPendingPage} optional > Comma-separated fields to auto-generate when @@ -378,7 +370,6 @@ export default function SiteChecklistClient({ Set environment variable to {'"1"'} to enable @@ -388,7 +379,6 @@ export default function SiteChecklistClient({ @@ -408,7 +398,6 @@ export default function SiteChecklistClient({ Set environment variable to {'"1"'} to constrain the size @@ -419,7 +408,6 @@ export default function SiteChecklistClient({ Set environment variable to {'"1"'} to prevent @@ -429,7 +417,6 @@ export default function SiteChecklistClient({ Set environment variable to {'"1"'} to disable @@ -439,7 +426,6 @@ export default function SiteChecklistClient({ Set environment variable to {'"1"'} to prevent @@ -449,7 +435,6 @@ export default function SiteChecklistClient({ Set environment variable to {'"1"'} to enable @@ -459,7 +444,6 @@ export default function SiteChecklistClient({ Set environment variable to {'"1"'} to hide footer link: @@ -468,7 +452,6 @@ export default function SiteChecklistClient({ Set environment variable to {'"1"'} to hide @@ -479,7 +462,6 @@ export default function SiteChecklistClient({ Set environment variable to {'"1"'} to prevent @@ -490,7 +472,6 @@ export default function SiteChecklistClient({ Set environment variable to {'"1"'} to hide EXIF data: @@ -499,7 +480,6 @@ export default function SiteChecklistClient({ Set environment variable to any number to enforce aspect ratio @@ -510,7 +490,6 @@ export default function SiteChecklistClient({ Set environment variable to {'"BOTTOM"'} to @@ -519,12 +498,6 @@ export default function SiteChecklistClient({ } - {showRefreshButton && -
- -
}
Changes to environment variables require a redeploy or reboot of local dev server diff --git a/src/site/SiteChecklistServer.tsx b/src/site/SiteChecklistServer.tsx new file mode 100644 index 00000000..8078124e --- /dev/null +++ b/src/site/SiteChecklistServer.tsx @@ -0,0 +1,21 @@ +import { generateAuthSecret } from '@/auth'; +import SiteChecklistClient from './SiteChecklistClient'; +import { CONFIG_CHECKLIST_STATUS } from '@/site/config'; +import { testConnectionsAction } from '@/admin/actions'; + +export default async function SiteChecklistServer({ + simplifiedView, +}: { + simplifiedView?: boolean +}) { + const secret = await generateAuthSecret(); + const connectionErrors = await testConnectionsAction().catch(() => ({})); + return ( + + ); +} diff --git a/src/site/config.ts b/src/site/config.ts index bde78d8c..e0c05956 100644 --- a/src/site/config.ts +++ b/src/site/config.ts @@ -148,12 +148,12 @@ export const HIGH_DENSITY_GRID = GRID_ASPECT_RATIO <= 1; export const CONFIG_CHECKLIST_STATUS = { hasDatabase: HAS_DATABASE, - isPostgresSSLEnabled: POSTGRES_SSL_ENABLED, + isPostgresSslEnabled: POSTGRES_SSL_ENABLED, hasVercelPostgres: ( /\/verceldb\?/.test(process.env.POSTGRES_URL ?? '') || /\.vercel-storage\.com\//.test(process.env.POSTGRES_URL ?? '') ), - hasVercelKV: HAS_VERCEL_KV, + hasVercelKv: HAS_VERCEL_KV, hasVercelBlobStorage: HAS_VERCEL_BLOB_STORAGE, hasCloudflareR2Storage: HAS_CLOUDFLARE_R2_STORAGE, hasAwsS3Storage: HAS_AWS_S3_STORAGE,