-
- Template onboarding
-
-
-
- Configure the template, then jump straight into the example dashboard.
-
-
- This setup wizard collects your local database and Auth.js seed values, generates
- a ready-to-use .env.local, and points you to the public example
- routes under /example/dashboard/*.
-
+
+ {groupChecks(checks).map(([area, areaChecks]) => (
+
+
{area}
+
+ {areaChecks.map((check) => (
+
+
+
+
+
+
{check.name}
+
{check.message}
+ {check.suggestedFix ? (
+
+ Suggested fix: {check.suggestedFix}
+
+ ) : null}
+
-
-
-
-
-
-
-
- Local app
-
- Reference URL used during local development.
-
-
- App URL
- setState({ ...state, appUrl: event.target.value })}
- placeholder='http://localhost:3000'
- />
-
-
-
-
-
-
-
- Auth secret
-
- Generated automatically for Auth.js credentials flow.
-
-
- setState({ ...state, authSecret: event.target.value })}
- />
- setState({ ...state, authSecret: generateSecret() })}
- >
-
- Regenerate secret
-
-
-
-
-
-
-
- Database configuration
-
- Fill in the PostgreSQL connection details. The wizard will build
- DATABASE_URL for you.
-
-
-
-
- Host
- setState({ ...state, dbHost: event.target.value })}
- />
-
-
- Port
- setState({ ...state, dbPort: event.target.value })}
- />
-
-
- Database name
- setState({ ...state, dbName: event.target.value })}
- />
-
-
- Database user
- setState({ ...state, dbUser: event.target.value })}
- />
-
-
- Database password
- setState({ ...state, dbPassword: event.target.value })}
- />
-
-
- DATABASE_URL preview
-
-
-
-
-
-
-
- Initial super admin seed
-
- These values feed the seed script used by npm run setup:db.
-
-
-
-
- Email
-
- setState({ ...state, superAdminEmail: event.target.value })
- }
- />
-
-
- Password
-
- setState({ ...state, superAdminPassword: event.target.value })
- }
- />
-
-
- Display name
-
- setState({ ...state, superAdminName: event.target.value })
- }
- />
-
-
-
Seed behavior
-
-
- setState({ ...state, resetPassword: event.target.checked })
- }
- className='mr-3'
- />
- Reset the seeded super admin password on reruns
-
-
-
-
Build mode
-
-
- setState({ ...state, buildStandalone: event.target.checked })
- }
- className='mr-3'
- />
- Enable standalone output for Docker-oriented builds
-
-
-
-
-
-
-
-
-
-
- .env.local preview
- {completedSteps}/{steps.length} steps ready
-
-
- Copy or download this file, then run npm run setup:db.
-
-
-
-
-
-
-
- {copied ? 'Copied' : 'Copy .env.local'}
-
- download('.env.local', envContent)}
- >
-
- Download file
-
-
-
-
- Open example dashboard
-
-
-
-
-
-
-
-
- Next steps
- Suggested flow after generating the env file.
-
-
- {steps.map((step, index) => (
-
-
- {index + 1}
-
-
-
{step}
-
- {index === 0 && 'Confirm the URL you will use locally while iterating.'}
- {index === 1 && 'Provide PostgreSQL connection details for local development.'}
- {index === 2 &&
- 'Set credentials and seed values for the first super admin account.'}
- {index === 3 &&
- 'Copy the generated file, run the seed command, then inspect /example/dashboard/*.'}
-
-
-
- ))}
-
- After saving .env.local, run npm run setup:db, then
- npm run dev. The app will continue to open on /setup,
- while all public showcase pages live under /example/dashboard/*.
-
-
-
+ ))}
+ ))}
+
+ );
+}
+
+function EmptyState({ message }: { message: string }) {
+ return (
+
+ {message}
+
+ );
+}
+
+function ErrorAlert({ title = 'Request failed', message }: { title?: string; message: string }) {
+ return (
+
+
+ {title}
+ {message}
+
+ );
+}
+
+function PermissionHint({ error }: { error: unknown }) {
+ const message = getErrorMessage(error);
+ if (!/unauthorized|forbidden|super_admin|access/i.test(message)) return null;
+
+ return (
+
+
+ Super admin access required
+
+ Setup APIs are restricted to super admins. Sign in with an authorized account to continue.
+
+
+ );
+}
+
+function TemplateList({ templates }: { templates: SetupTemplateMetadataItem[] }) {
+ if (templates.length === 0) return
;
+
+ return (
+
+
+ Order
+ File
+ Seed type
+ Status
+
+
+ {templates.map((template) => (
+
+
{template.importOrder}
+
+
{template.file}
+
{template.description}
+
+
{template.seedType}
+
+ {template.supported ? 'Supported' : 'Deferred'}
+
+
+ ))}
);
}
+
+function CsvErrors({ errors }: { errors: CsvPreviewError[] }) {
+ if (errors.length === 0) return null;
+
+ return (
+
+ {errors.slice(0, 8).map((error, index) => (
+
+
{error.code}
+
{error.message}
+
+ {error.file}, row {error.row}, column {error.column}. {error.suggestedFix}
+
+
+ ))}
+ {errors.length > 8 ? (
+
+ Showing first 8 of {errors.length} issues.
+
+ ) : null}
+
+ );
+}
+
+function PreviewFileTable({ files }: { files: CsvPreviewFileResult[] }) {
+ if (files.length === 0) return
;
+
+ return (
+
+ {files.map((file) => (
+
+
+
+
{file.fileName}
+
Template: {file.template}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))}
+
+ );
+}
+
+function CommitReportPanel({ report }: { report: ImportReport | null }) {
+ if (!report) return
;
+
+ return (
+
+
+
+
Commit report
+
+ {report.dryRun ? 'Dry run only' : 'Database commit'} generated {report.generatedAt}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+function VerificationPanel({ report }: { report: SetupVerificationReport | null }) {
+ if (!report) return
;
+
+ return (
+
+
+
+
{report.message}
+
Organization: {report.organizationId ?? 'Not selected'}
+
+
+
+
+
+
+ );
+}
+
+function Stepper({ currentStep }: { currentStep: WizardStepKey }) {
+ const currentIndex = WIZARD_STEPS.findIndex((step) => step.key === currentStep);
+
+ return (
+
+ {WIZARD_STEPS.map((step, index) => (
+
+
Step {index + 1}
+
{step.label}
+
+ ))}
+
+ );
+}
+
+export function SetupWizard() {
+ const [currentStep, setCurrentStep] = useState
('readiness');
+ const [files, setFiles] = useState([]);
+ const [preview, setPreview] = useState(null);
+ const [previewFingerprint, setPreviewFingerprint] = useState('');
+ const [commitReport, setCommitReport] = useState(null);
+ const [warningAcknowledged, setWarningAcknowledged] = useState(false);
+ const [dryRun, setDryRun] = useState(false);
+
+ const readinessQuery = useQuery(setupReadinessQueryOptions());
+ const templatesQuery = useQuery(setupTemplatesQueryOptions());
+ const previewMutation = useCsvPreview();
+ const commitMutation = useCsvCommit();
+ const verificationMutation = useSetupVerification();
+
+ const fileFingerprint = useMemo(() => getFileFingerprint(files), [files]);
+ const filesChangedAfterPreview = Boolean(preview) && fileFingerprint !== previewFingerprint;
+ const previewStatus = getPreviewStatus(preview);
+ const commitBlocked =
+ !preview ||
+ !preview.previewHash ||
+ filesChangedAfterPreview ||
+ previewStatus === 'FAIL' ||
+ (previewStatus === 'WARNING' && !warningAcknowledged) ||
+ files.length === 0;
+
+ const selectedTemplateNames = new Set(files.map((file) => file.name));
+ const missingRecommendedTemplates =
+ templatesQuery.data?.templates.filter(
+ (template) => template.supported && !selectedTemplateNames.has(template.file)
+ ) ?? [];
+
+ function handleFiles(nextFiles: FileList | null) {
+ const selectedFiles = Array.from(nextFiles ?? []);
+ setFiles(selectedFiles);
+ setPreview(null);
+ setPreviewFingerprint('');
+ setCommitReport(null);
+ setWarningAcknowledged(false);
+ }
+
+ async function runPreview() {
+ const result = await previewMutation.mutateAsync(files);
+ setPreview(result);
+ setPreviewFingerprint(fileFingerprint);
+ setCommitReport(null);
+ setWarningAcknowledged(false);
+ setCurrentStep('preview');
+ }
+
+ async function runCommit() {
+ if (!preview?.previewHash) return;
+ const report = await commitMutation.mutateAsync({
+ files,
+ previewHash: preview.previewHash,
+ dryRun
+ });
+ setCommitReport(report);
+ }
+
+ async function runVerification() {
+ const report = await verificationMutation.mutateAsync({});
+ setCurrentStep('verification');
+ return report;
+ }
+
+ return (
+
+
+
+
+
+ Session-only wizard
+
+ This setup wizard does not persist progress yet. Persistent resume and setup runs arrive in
+ D.7.8.
+
+
+
+
+
+
+
+ Step 1: System Readiness
+ Run non-destructive checks before importing setup CSV files.
+
+
readinessQuery.refetch()}
+ disabled={readinessQuery.isFetching}
+ >
+ {readinessQuery.isFetching ? (
+
+ ) : (
+
+ )}
+ Re-run
+
+
+
+
+ {readinessQuery.isError ? (
+ <>
+
+
+ >
+ ) : null}
+ {readinessQuery.isLoading ? : null}
+ {readinessQuery.data ? (
+ setCurrentStep('templates')} />
+ ) : null}
+
+
+
+
+
+ Step 2: Template Pack
+
+ Review the registered CSV templates. Local template files live under setup/templates.
+
+
+
+ {templatesQuery.isError ? (
+ <>
+
+
+ >
+ ) : null}
+ {templatesQuery.isLoading ? : null}
+ {templatesQuery.data ? : null}
+ setCurrentStep('upload')}>
+ Continue to upload
+
+
+
+
+
+
+
+ Step 3: CSV Upload
+ Select one or more CSV files. Files upload only when Preview is clicked.
+
+
+ handleFiles(event.target.files)}
+ />
+ {files.length > 0 ? (
+
+
Selected files
+
+ {files.map((file) => (
+
+
{file.name}
+
{file.size.toLocaleString()} bytes
+
+ ))}
+
+
+ ) : (
+
+ )}
+ {missingRecommendedTemplates.length > 0 ? (
+
+
+ Recommended templates not selected
+
+ Missing files are informational only: {missingRecommendedTemplates.slice(0, 6).map((template) => template.file).join(', ')}
+ {missingRecommendedTemplates.length > 6 ? '...' : ''}
+
+
+ ) : null}
+ {filesChangedAfterPreview ? (
+
+
+ Files changed after preview
+ Run Preview again before committing.
+
+ ) : null}
+
+ {previewMutation.isPending ? (
+
+ ) : (
+
+ )}
+ Preview CSV import
+
+ {previewMutation.isError ? : null}
+
+
+
+
+
+ Step 4: CSV Preview
+ Review grouped file and row validation before commit.
+
+
+ {preview ? (
+ <>
+
+
+
Preview hash
+
{preview.previewHash}
+
+ {previewStatus ?
: null}
+
+
+
+
+
+
+
+
+
+ {previewStatus === 'WARNING' ? (
+
+ setWarningAcknowledged(checked === true)}
+ />
+
+ I acknowledge preview warnings and want to allow commit.
+
+
+ ) : null}
+
+ >
+ ) : (
+
+ )}
+
+
+
+
+
+ Step 5: CSV Commit
+ Commit uses the latest previewHash and the currently selected files.
+
+
+
+ setDryRun(checked === true)}
+ />
+ Dry run only
+
+ {previewStatus === 'FAIL' ? (
+
+
+ Commit blocked
+ Preview contains FAIL results. Fix CSV files and preview again.
+
+ ) : null}
+ {filesChangedAfterPreview ? (
+
+
+ Re-preview required
+ The selected files changed after preview.
+
+ ) : null}
+
+ {commitMutation.isPending ? (
+
+ ) : (
+
+ )}
+ {dryRun ? 'Run commit dry run' : 'Commit import'}
+
+ {commitMutation.isError ? : null}
+
+
+
+
+
+
+ Step 6: Verification
+ Run setup verification checks after preview or commit.
+
+
+
+ {verificationMutation.isPending ? (
+
+ ) : (
+
+ )}
+ Run verification
+
+ {verificationMutation.isError ? (
+ <>
+
+
+ >
+ ) : null}
+
+
+
+
+
+
+ Step 7: Finish
+ Summary of this session. Progress is not persisted until D.7.8.
+
+
+
+
+
+
+
+
+ setCurrentStep('finish')}>
+ Mark current step complete
+
+
+
+
+ );
+}
+
+function ReadinessPanel({
+ report,
+ onContinue
+}: {
+ report: SetupReadinessReport;
+ onContinue: () => void;
+}) {
+ return (
+
+
+
+
{report.message}
+
Checked at {report.time}
+
+
+
+
+
+
0}>
+ Continue to templates
+
+
+
+ );
+}