Refactor site checklist, add secret generator
This commit is contained in:
parent
ed019be284
commit
33ec20d709
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -9,6 +9,7 @@
|
||||
"nextjs",
|
||||
"qaub",
|
||||
"skippable",
|
||||
"sonner",
|
||||
"thephotoblog",
|
||||
"trpc",
|
||||
"WRHGZC",
|
||||
|
||||
@ -33,6 +33,7 @@
|
||||
"react-icons": "^4.10.1",
|
||||
"react-spinners": "^0.13.8",
|
||||
"short-uuid": "^4.2.2",
|
||||
"sonner": "^0.7.0",
|
||||
"tailwindcss": "3.3.3",
|
||||
"ts-exif-parser": "^0.2.2",
|
||||
"typescript": "5.2.2"
|
||||
|
||||
12
pnpm-lock.yaml
generated
12
pnpm-lock.yaml
generated
@ -29,6 +29,7 @@ specifiers:
|
||||
react-icons: ^4.10.1
|
||||
react-spinners: ^0.13.8
|
||||
short-uuid: ^4.2.2
|
||||
sonner: ^0.7.0
|
||||
tailwindcss: 3.3.3
|
||||
ts-exif-parser: ^0.2.2
|
||||
typescript: 5.2.2
|
||||
@ -59,6 +60,7 @@ dependencies:
|
||||
react-icons: 4.10.1_react@18.2.0
|
||||
react-spinners: 0.13.8_biqbaboplfbrettd7655fr4n2y
|
||||
short-uuid: 4.2.2
|
||||
sonner: 0.7.0_biqbaboplfbrettd7655fr4n2y
|
||||
tailwindcss: 3.3.3
|
||||
ts-exif-parser: 0.2.2
|
||||
typescript: 5.2.2
|
||||
@ -3189,6 +3191,16 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
dev: false
|
||||
|
||||
/sonner/0.7.0_biqbaboplfbrettd7655fr4n2y:
|
||||
resolution: {integrity: sha512-vAlXCrE6/183yt64ktIUnPv85RmAPYiicl5z35fDDFhWRIUpg7N62TsiIbHjwGuxbVJu/5hYlh92HHImsS27dA==}
|
||||
peerDependencies:
|
||||
react: ^18.0.0
|
||||
react-dom: ^18.0.0
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
dev: false
|
||||
|
||||
/source-map-js/1.0.2:
|
||||
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
import InfoBlock from '@/components/InfoBlock';
|
||||
import SiteGrid from '@/components/SiteGrid';
|
||||
import { SITE_CHECKLIST_STATUS } from '@/site';
|
||||
import SiteChecklist from '@/site/SiteChecklist';
|
||||
|
||||
export default function ChecklistPage() {
|
||||
export default async function ChecklistPage() {
|
||||
return (
|
||||
<SiteGrid
|
||||
contentMain={<InfoBlock>
|
||||
<SiteChecklist {...SITE_CHECKLIST_STATUS} />
|
||||
<SiteChecklist />
|
||||
</InfoBlock>}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -6,6 +6,7 @@ import { BASE_URL, SITE_DESCRIPTION, SITE_TITLE } from '@/site/config';
|
||||
import StateProvider from '@/state/AppStateProvider';
|
||||
import ThemeProviderClient from '@/site/ThemeProviderClient';
|
||||
import Nav from '@/components/Nav';
|
||||
import ToasterClient from '@/components/ToasterClient';
|
||||
|
||||
import '../site/globals.css';
|
||||
|
||||
@ -76,6 +77,7 @@ export default function RootLayout({
|
||||
</StateProvider>
|
||||
<Analytics />
|
||||
</main>
|
||||
<ToasterClient />
|
||||
</ThemeProviderClient>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -10,37 +10,36 @@ export default function SignInForm() {
|
||||
const [password, setPassword] = useState('');
|
||||
|
||||
return (
|
||||
<InfoBlock
|
||||
className="space-y-8"
|
||||
padding="normal"
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<FieldSet
|
||||
id="email"
|
||||
label="Admin Email"
|
||||
value={email}
|
||||
onChange={setEmail}
|
||||
/>
|
||||
<FieldSet
|
||||
id="password"
|
||||
label="Admin Password"
|
||||
value={password}
|
||||
onChange={setPassword}
|
||||
type="password"
|
||||
/>
|
||||
<InfoBlock>
|
||||
<div className="space-y-8">
|
||||
<div className="space-y-4">
|
||||
<FieldSet
|
||||
id="email"
|
||||
label="Admin Email"
|
||||
value={email}
|
||||
onChange={setEmail}
|
||||
/>
|
||||
<FieldSet
|
||||
id="password"
|
||||
label="Admin Password"
|
||||
value={password}
|
||||
onChange={setPassword}
|
||||
type="password"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => signIn(
|
||||
'credentials',
|
||||
{
|
||||
email,
|
||||
password,
|
||||
callbackUrl: '/admin/photos',
|
||||
},
|
||||
)}
|
||||
>
|
||||
Sign in
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => signIn(
|
||||
'credentials',
|
||||
{
|
||||
email,
|
||||
password,
|
||||
callbackUrl: '/admin/photos',
|
||||
},
|
||||
)}
|
||||
>
|
||||
Sign in
|
||||
</button>
|
||||
</InfoBlock>
|
||||
);
|
||||
}
|
||||
|
||||
@ -55,3 +55,8 @@ export const {
|
||||
signIn: '/sign-in',
|
||||
},
|
||||
});
|
||||
|
||||
export const generateAuthSecret = () => fetch(
|
||||
'https://generate-secret.vercel.app/32',
|
||||
{ cache: 'no-cache' },
|
||||
).then(res => res.text());
|
||||
|
||||
@ -4,27 +4,33 @@ import { ReactNode } from 'react';
|
||||
export default function InfoBlock({
|
||||
children,
|
||||
className,
|
||||
padding = 'loose',
|
||||
padding = 'normal',
|
||||
}: {
|
||||
children: ReactNode
|
||||
className?: string
|
||||
padding?: 'loose' | 'normal';
|
||||
padding?: 'loose' | 'normal' | 'tight';
|
||||
} ) {
|
||||
const getPaddingClasses = () => {
|
||||
switch (padding) {
|
||||
case 'loose': return 'p-4 md:p-24';
|
||||
case 'normal': return 'p-4 md:p-8';
|
||||
case 'tight': return 'py-2 px-3';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cc(
|
||||
'flex flex-col items-center justify-center',
|
||||
'px-8 rounded-lg',
|
||||
padding === 'loose' ? 'py-24' : 'py-8',
|
||||
'border',
|
||||
'rounded-lg border',
|
||||
'bg-gray-50 border-gray-200',
|
||||
'dark:bg-gray-900/40 dark:border-gray-800',
|
||||
'text-center',
|
||||
getPaddingClasses(),
|
||||
className,
|
||||
)}>
|
||||
<div className={cc(
|
||||
'flex flex-col items-center justify-center',
|
||||
'space-y-4',
|
||||
'text-gray-500 dark:text-gray-400',
|
||||
className,
|
||||
)}>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
34
src/components/LoaderIcon.tsx
Normal file
34
src/components/LoaderIcon.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
'use client';
|
||||
|
||||
import { cc } from '@/utility/css';
|
||||
import Spinner from './Spinner';
|
||||
|
||||
export default function IconButton({
|
||||
children,
|
||||
onClick,
|
||||
isLoading,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
onClick?: () => void
|
||||
isLoading?: boolean
|
||||
}) {
|
||||
return (
|
||||
<span className="min-w-[1.1rem]">
|
||||
{!isLoading
|
||||
? <span
|
||||
onClick={onClick}
|
||||
className={cc(
|
||||
onClick !== undefined && 'cursor-pointer',
|
||||
'active:opacity-50',
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
: <span className={cc(
|
||||
'inline-block translate-x-[2px] translate-y-[1px]',
|
||||
)}>
|
||||
<Spinner size={12} />
|
||||
</span>}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
11
src/components/ToasterClient.tsx
Normal file
11
src/components/ToasterClient.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import { useTheme } from 'next-themes';
|
||||
import { Toaster } from 'sonner';
|
||||
|
||||
export default function ToasterClient() {
|
||||
const { theme } = useTheme();
|
||||
return (
|
||||
<Toaster theme={theme as 'system' | 'light' | 'dark'} />
|
||||
);
|
||||
}
|
||||
@ -1,13 +1,13 @@
|
||||
import InfoBlock from '@/components/InfoBlock';
|
||||
import SiteGrid from '@/components/SiteGrid';
|
||||
import { SITE_CHECKLIST_STATUS } from '@/site';
|
||||
import { CONFIG_CHECKLIST_STATUS } from '@/site/config';
|
||||
import SiteChecklist from '@/site/SiteChecklist';
|
||||
import { cc } from '@/utility/css';
|
||||
import Link from 'next/link';
|
||||
import { HiOutlinePhotograph } from 'react-icons/hi';
|
||||
|
||||
export default function PhotosEmptyState() {
|
||||
const showChecklist = Object.values(SITE_CHECKLIST_STATUS).some(v => !v);
|
||||
const showChecklist = Object.values(CONFIG_CHECKLIST_STATUS).some(v => !v);
|
||||
|
||||
return (
|
||||
<SiteGrid
|
||||
@ -21,13 +21,11 @@ export default function PhotosEmptyState() {
|
||||
'font-bold text-2xl',
|
||||
'text-gray-700 dark:text-gray-200',
|
||||
)}>
|
||||
{showChecklist
|
||||
? 'Finish Setup'
|
||||
: 'Welcome!'}
|
||||
{showChecklist ? 'Finish Setup' : 'Welcome!'}
|
||||
</div>
|
||||
{showChecklist
|
||||
? <SiteChecklist {...SITE_CHECKLIST_STATUS} />
|
||||
: <div className="max-w-md leading-[1.7]">
|
||||
? <SiteChecklist />
|
||||
: <div className="max-w-md leading-[1.7] text-center">
|
||||
<div className="mb-2">
|
||||
1. Visit
|
||||
{' '}
|
||||
|
||||
@ -1,144 +1,13 @@
|
||||
'use client';
|
||||
|
||||
import { useTransition } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { cc } from '@/utility/css';
|
||||
import SiteChecklistRow from './SiteChecklistRow';
|
||||
import { FiExternalLink } from 'react-icons/fi';
|
||||
|
||||
export default function SiteChecklist({
|
||||
hasTitle,
|
||||
hasDomain,
|
||||
hasPostgres,
|
||||
hasBlob,
|
||||
hasAuth,
|
||||
showRefreshButton,
|
||||
}: {
|
||||
hasTitle: boolean
|
||||
hasDomain: boolean
|
||||
hasPostgres: boolean
|
||||
hasBlob: boolean
|
||||
hasAuth: boolean
|
||||
showRefreshButton?: boolean
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const refreshSetupStatus = () => {
|
||||
startTransition(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 &&
|
||||
<>
|
||||
|
||||
<FiExternalLink
|
||||
size={14}
|
||||
className='inline translate-y-[-1.5px]'
|
||||
/>
|
||||
</>}
|
||||
</>;
|
||||
|
||||
const renderEnvVar = (variables: string[]) =>
|
||||
<div className="py-1 space-y-1">
|
||||
{variables.map(variable =>
|
||||
<div key={variable}>
|
||||
<span className={cc(
|
||||
'rounded-sm',
|
||||
'bg-gray-100 text-gray-500',
|
||||
'dark:bg-gray-800 dark:text-gray-400',
|
||||
)}>
|
||||
`{variable}`
|
||||
</span>
|
||||
</div>)}
|
||||
</div>;
|
||||
import { generateAuthSecret } from '@/auth';
|
||||
import SiteChecklistClient from './SiteChecklistClient';
|
||||
import { CONFIG_CHECKLIST_STATUS } from '@/site/config';
|
||||
|
||||
export default async function SiteChecklist() {
|
||||
const secret = await generateAuthSecret();
|
||||
return (
|
||||
<div className={cc(
|
||||
'text-sm',
|
||||
'max-w-xl',
|
||||
'bg-white dark:bg-black',
|
||||
'dark:text-gray-400',
|
||||
'border border-gray-200 dark:border-gray-800 rounded-md',
|
||||
'divide-y divide-gray-200 dark:divide-gray-800',
|
||||
)}>
|
||||
<SiteChecklistRow
|
||||
title="Add title"
|
||||
status={hasTitle}
|
||||
isPending={isPending}
|
||||
>
|
||||
Store in environment variable:
|
||||
{renderEnvVar(['NEXT_PUBLIC_SITE_TITLE'])}
|
||||
</SiteChecklistRow>
|
||||
<SiteChecklistRow
|
||||
title="Add domain"
|
||||
status={hasDomain}
|
||||
isPending={isPending}
|
||||
>
|
||||
Store in environment variable:
|
||||
{renderEnvVar(['NEXT_PUBLIC_SITE_DOMAIN'])}
|
||||
</SiteChecklistRow>
|
||||
<SiteChecklistRow
|
||||
title="Setup database"
|
||||
status={hasPostgres}
|
||||
isPending={isPending}
|
||||
>
|
||||
{renderLink(
|
||||
'https://vercel.com/docs/storage/vercel-postgres/quickstart',
|
||||
'Create Vercel Postgres store',
|
||||
)}
|
||||
{' '}
|
||||
and connect to project
|
||||
</SiteChecklistRow>
|
||||
<SiteChecklistRow
|
||||
title="Setup blob store"
|
||||
status={hasBlob}
|
||||
isPending={isPending}
|
||||
>
|
||||
{renderLink(
|
||||
'https://vercel.com/docs/storage/vercel-blob/quickstart',
|
||||
'Create Vercel Blob store',
|
||||
)}
|
||||
{' '}
|
||||
and connect to project
|
||||
</SiteChecklistRow>
|
||||
<SiteChecklistRow
|
||||
title="Setup auth"
|
||||
status={hasAuth}
|
||||
isPending={isPending}
|
||||
>
|
||||
{renderLink(
|
||||
'https://clerk.com/docs/quickstarts/setup-clerk',
|
||||
'Create Clerk account',
|
||||
)}
|
||||
{' '}
|
||||
and add environment variables:
|
||||
{renderEnvVar([
|
||||
'NEXT_PUBLIC_CLERK_SIGN_IN_URL',
|
||||
'NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY',
|
||||
'CLERK_SECRET_KEY',
|
||||
'CLERK_ADMIN_USER_ID',
|
||||
])}
|
||||
</SiteChecklistRow>
|
||||
<div className="py-4 space-y-4">
|
||||
<div className="px-8 text-gray-400">
|
||||
Changes to environment variables require a redeploy
|
||||
or reboot of local dev server
|
||||
</div>
|
||||
{showRefreshButton &&
|
||||
<button onClick={refreshSetupStatus}>
|
||||
Check
|
||||
</button>}
|
||||
</div>
|
||||
</div>
|
||||
<SiteChecklistClient {...{
|
||||
...CONFIG_CHECKLIST_STATUS,
|
||||
secret,
|
||||
}} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
186
src/site/SiteChecklistClient.tsx
Normal file
186
src/site/SiteChecklistClient.tsx
Normal file
@ -0,0 +1,186 @@
|
||||
'use client';
|
||||
|
||||
import { useTransition } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { cc } from '@/utility/css';
|
||||
import SiteChecklistRow from './SiteChecklistRow';
|
||||
import { FiExternalLink } from 'react-icons/fi';
|
||||
import { BiCopy, BiRefresh } from 'react-icons/bi';
|
||||
import IconButton from '@/components/LoaderIcon';
|
||||
import { toast } from 'sonner';
|
||||
import InfoBlock from '@/components/InfoBlock';
|
||||
|
||||
export default function SiteChecklistClient({
|
||||
hasTitle,
|
||||
hasDomain,
|
||||
hasPostgres,
|
||||
hasBlob,
|
||||
hasAuth,
|
||||
hasAdminUser,
|
||||
showRefreshButton,
|
||||
secret,
|
||||
}: {
|
||||
hasTitle: boolean
|
||||
hasDomain: boolean
|
||||
hasPostgres: boolean
|
||||
hasBlob: boolean
|
||||
hasAuth: boolean
|
||||
hasAdminUser: 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 &&
|
||||
<>
|
||||
|
||||
<FiExternalLink
|
||||
size={14}
|
||||
className='inline translate-y-[-1.5px]'
|
||||
/>
|
||||
</>}
|
||||
</>;
|
||||
|
||||
const renderEnvVar = (variable: string) =>
|
||||
<div key={variable}>
|
||||
<span className={cc(
|
||||
'rounded-sm',
|
||||
'bg-gray-100 text-gray-500',
|
||||
'dark:bg-gray-800 dark:text-gray-400',
|
||||
)}>
|
||||
`{variable}`
|
||||
</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-4">
|
||||
<div className={cc(
|
||||
'bg-white dark:bg-black',
|
||||
'dark:text-gray-400',
|
||||
'border border-gray-200 dark:border-gray-800 rounded-md',
|
||||
'divide-y divide-gray-200 dark:divide-gray-800',
|
||||
)}>
|
||||
<SiteChecklistRow
|
||||
title="Add title"
|
||||
status={hasTitle}
|
||||
isPending={isPendingPage}
|
||||
>
|
||||
Store in environment variable:
|
||||
{renderEnvVars(['NEXT_PUBLIC_SITE_TITLE'])}
|
||||
</SiteChecklistRow>
|
||||
<SiteChecklistRow
|
||||
title="Add domain"
|
||||
status={hasDomain}
|
||||
isPending={isPendingPage}
|
||||
>
|
||||
Store in environment variable:
|
||||
{renderEnvVars(['NEXT_PUBLIC_SITE_DOMAIN'])}
|
||||
</SiteChecklistRow>
|
||||
<SiteChecklistRow
|
||||
title="Setup database"
|
||||
status={hasPostgres}
|
||||
isPending={isPendingPage}
|
||||
>
|
||||
{renderLink(
|
||||
'https://vercel.com/docs/storage/vercel-postgres/quickstart',
|
||||
'Create Vercel Postgres store',
|
||||
)}
|
||||
{' '}
|
||||
and connect to project
|
||||
</SiteChecklistRow>
|
||||
<SiteChecklistRow
|
||||
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
|
||||
</SiteChecklistRow>
|
||||
<SiteChecklistRow
|
||||
title="Setup auth"
|
||||
status={hasAuth}
|
||||
isPending={isPendingPage}
|
||||
>
|
||||
Store auth secret in environment variable:
|
||||
<InfoBlock className="my-1.5" padding="tight">
|
||||
<div className="flex items-center gap-4">
|
||||
<span>{secret}</span>
|
||||
<div className="flex items-center gap-1">
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(secret);
|
||||
toast('Secret copied to clipboard', {
|
||||
duration: 4000,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<BiCopy size={16} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={refreshSecret}
|
||||
isLoading={isPendingSecret}
|
||||
>
|
||||
<BiRefresh size={18} />
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
</InfoBlock>
|
||||
{renderEnvVars(['AUTH_SECRET'])}
|
||||
</SiteChecklistRow>
|
||||
<SiteChecklistRow
|
||||
title="Setup admin user"
|
||||
status={hasAdminUser}
|
||||
isPending={isPendingPage}
|
||||
>
|
||||
Store admin email/password
|
||||
{' '}
|
||||
in environment variables:
|
||||
{renderEnvVars([
|
||||
'ADMIN_EMAIL',
|
||||
'ADMIN_PASSWORD',
|
||||
])}
|
||||
</SiteChecklistRow>
|
||||
{showRefreshButton &&
|
||||
<div className="py-4 space-y-4">
|
||||
<button onClick={refreshPage}>
|
||||
Check
|
||||
</button>
|
||||
</div>}
|
||||
</div>
|
||||
<div className="px-10 text-gray-500">
|
||||
Changes to environment variables require a redeploy
|
||||
or reboot of local dev server
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -11,3 +11,15 @@ export const SITE_DESCRIPTION = process.env.NEXT_PUBLIC_SITE_DESCRIPTION
|
||||
export const BASE_URL = process.env.NODE_ENV === 'production'
|
||||
? `https://${SITE_DOMAIN}`
|
||||
: 'http://localhost:3000';
|
||||
|
||||
export const CONFIG_CHECKLIST_STATUS = {
|
||||
hasTitle: (process.env.NEXT_PUBLIC_SITE_TITLE ?? '').length > 0,
|
||||
hasDomain: (process.env.NEXT_PUBLIC_SITE_DOMAIN ?? '').length > 0,
|
||||
hasPostgres: (process.env.POSTGRES_HOST ?? '').length > 0,
|
||||
hasBlob: (process.env.BLOB_READ_WRITE_TOKEN ?? '').length > 0,
|
||||
hasAuth: (process.env.AUTH_SECRET ?? '').length > 0,
|
||||
hasAdminUser: (
|
||||
(process.env.ADMIN_EMAIL ?? '').length > 0 &&
|
||||
(process.env.ADMIN_PASSWORD ?? '').length > 0
|
||||
),
|
||||
};
|
||||
|
||||
@ -23,16 +23,3 @@ const STORE_ID = process.env.BLOB_READ_WRITE_TOKEN?.match(
|
||||
|
||||
export const BLOB_BASE_URL =
|
||||
`https://${STORE_ID}.public.blob.vercel-storage.com`;
|
||||
|
||||
export const SITE_CHECKLIST_STATUS = {
|
||||
hasTitle: (process.env.NEXT_PUBLIC_SITE_TITLE ?? '').length > 0,
|
||||
hasDomain: (process.env.NEXT_PUBLIC_SITE_DOMAIN ?? '').length > 0,
|
||||
hasPostgres: (process.env.POSTGRES_HOST ?? '').length > 0,
|
||||
hasBlob: (process.env.BLOB_READ_WRITE_TOKEN ?? '').length > 0,
|
||||
hasAuth: (
|
||||
(process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY ?? '').length > 0 &&
|
||||
(process.env.NEXT_PUBLIC_CLERK_SIGN_IN_URL ?? '').length > 0 &&
|
||||
(process.env.CLERK_SECRET_KEY ?? '').length > 0 &&
|
||||
(process.env.CLERK_ADMIN_USER_ID ?? '').length > 0
|
||||
),
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user