Vercel/src/site/SiteChecklistClient.tsx
2023-10-18 14:16:18 -05:00

271 lines
7.4 KiB
TypeScript

'use client';
import { useTransition } from 'react';
import { useRouter } from 'next/navigation';
import { cc } from '@/utility/css';
import ChecklistRow from '../components/ChecklistRow';
import { FiCheckSquare, FiExternalLink } from 'react-icons/fi';
import {
BiCog,
BiCopy,
BiData,
BiLockAlt,
BiPencil,
BiRefresh,
} from 'react-icons/bi';
import IconButton from '@/components/IconButton';
import { toast } from 'sonner';
import InfoBlock from '@/components/InfoBlock';
import Checklist from '@/components/Checklist';
export default function SiteChecklistClient({
hasPostgres,
hasBlob,
hasAuth,
hasAdminUser,
hasTitle,
hasDomain,
showRepoLink,
isProModeEnabled,
isPublicApiEnabled,
isOgTextBottomAligned,
showRefreshButton,
secret,
}: {
hasPostgres: boolean
hasBlob: boolean
hasAuth: boolean
hasAdminUser: boolean
hasTitle: boolean
hasDomain: boolean
showRepoLink: boolean
isProModeEnabled: boolean
isPublicApiEnabled: boolean
isOgTextBottomAligned: boolean
showRefreshButton?: 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) =>
<>
<a {...{
href,
...external && { target: '_blank', rel: 'noopener noreferrer' },
className: cc(
'underline hover:no-underline',
),
}}>
{text}
</a>
{external &&
<>
&nbsp;
<FiExternalLink
size={14}
className='inline translate-y-[-1.5px]'
/>
</>}
</>;
const renderCopyButton = (label: string, text: string, subtle?: boolean) =>
<IconButton
icon={<BiCopy size={15} />}
className={cc(subtle && 'text-gray-300 dark:text-gray-700')}
onClick={() => {
navigator.clipboard.writeText(text);
toast(
`${label} copied to clipboard`, {
icon: <FiCheckSquare size={16} />,
duration: 4000,
},
);
}}
/>;
const renderEnvVar = (variable: string) =>
<div
key={variable}
className="overflow-x-scroll"
>
<span className="inline-flex items-center gap-1">
<span className={cc(
'rounded-sm',
'bg-gray-100 text-gray-500',
'dark:bg-gray-800 dark:text-gray-400',
)}>
`{variable}`
</span>
{renderCopyButton(variable, variable, true)}
</span>
</div>;
const renderEnvVars = (variables: string[]) =>
<div className="py-1 space-y-1">
{variables.map(renderEnvVar)}
</div>;
return (
<div className="text-sm max-w-xl space-y-6 w-full">
<Checklist
title="Storage"
icon={<BiData size={16} />}
>
<ChecklistRow
title="Setup database"
status={hasPostgres}
isPending={isPendingPage}
>
{renderLink(
'https://vercel.com/docs/storage/vercel-postgres/quickstart',
'Create Vercel Postgres store',
)}
{' '}
and connect to project
</ChecklistRow>
<ChecklistRow
title="Setup blob store"
status={hasBlob}
isPending={isPendingPage}
>
{renderLink(
'https://vercel.com/docs/storage/vercel-blob/quickstart',
'Create Vercel Blob store',
)}
{' '}
and connect to project
</ChecklistRow>
</Checklist>
<Checklist
title="Authentication"
icon={<BiLockAlt size={16} />}
>
<ChecklistRow
title="Setup auth"
status={hasAuth}
isPending={isPendingPage}
>
Store auth secret in environment variable:
<div className="overflow-x-auto">
<InfoBlock className="my-1.5 inline-flex" padding="tight">
<div className="flex flex-nowrap items-center gap-4">
<span>{secret}</span>
<div className="flex items-center gap-0.5">
{renderCopyButton('Secret', secret)}
<IconButton
icon={<BiRefresh size={18} />}
onClick={refreshSecret}
isLoading={isPendingSecret}
spinnerColor="text"
/>
</div>
</div>
</InfoBlock>
</div>
{renderEnvVars(['AUTH_SECRET'])}
</ChecklistRow>
<ChecklistRow
title="Setup admin user"
status={hasAdminUser}
isPending={isPendingPage}
>
Store admin email/password
{' '}
in environment variables:
{renderEnvVars([
'ADMIN_EMAIL',
'ADMIN_PASSWORD',
])}
</ChecklistRow>
</Checklist>
<Checklist
title="Content"
icon={<BiPencil size={16} />}
>
<ChecklistRow
title="Add title"
status={hasTitle}
isPending={isPendingPage}
optional
>
Store in environment variable (used in page titles):
{renderEnvVars(['NEXT_PUBLIC_SITE_TITLE'])}
</ChecklistRow>
<ChecklistRow
title="Add custom domain"
status={hasDomain}
isPending={isPendingPage}
optional
>
Store in environment variable (displayed in top-right nav):
{renderEnvVars(['NEXT_PUBLIC_SITE_DOMAIN'])}
</ChecklistRow>
</Checklist>
<Checklist
title="Settings"
icon={<BiCog size={16} />}
>
<ChecklistRow
title="Show Repo Link"
status={showRepoLink}
isPending={isPendingPage}
optional
>
Set environment variable to {'"1"'} to hide footer link:
{renderEnvVars(['NEXT_PUBLIC_HIDE_REPO_LINK'])}
</ChecklistRow>
<ChecklistRow
title="Pro Mode"
status={isProModeEnabled}
isPending={isPendingPage}
optional
>
Set environment variable to {'"1"'} to enable
higher quality image storage:
{renderEnvVars(['NEXT_PUBLIC_PRO_MODE'])}
</ChecklistRow>
<ChecklistRow
title="Public API"
status={isPublicApiEnabled}
isPending={isPendingPage}
optional
>
Set environment variable to {'"1"'} to enable
a public API available at <code>/api</code>:
{renderEnvVars(['NEXT_PUBLIC_PUBLIC_API'])}
</ChecklistRow>
<ChecklistRow
title="Legacy OG Text Alignment"
status={isOgTextBottomAligned}
isPending={isPendingPage}
optional
>
Set environment variable to {'"BOTTOM"'} to
keep OG image text bottom aligned (default is top):
{renderEnvVars(['NEXT_PUBLIC_OG_TEXT_ALIGNMENT'])}
</ChecklistRow>
</Checklist>
{showRefreshButton &&
<div className="py-4 space-y-4">
<button onClick={refreshPage}>
Check
</button>
</div>}
<div className="px-11 text-gray-500">
Changes to environment variables require a redeploy
or reboot of local dev server
</div>
</div>
);
}