diff --git a/docs/implementation/task-d76-dashboard-setup-wizard-ui.md b/docs/implementation/task-d76-dashboard-setup-wizard-ui.md new file mode 100644 index 0000000..795635f --- /dev/null +++ b/docs/implementation/task-d76-dashboard-setup-wizard-ui.md @@ -0,0 +1,71 @@ +# Task D.7.6 Dashboard Setup Wizard UI + +Date: 2026-07-03 +Status: implemented + +## Scope Delivered + +Added dashboard-native setup wizard route: + +- `GET /dashboard/administration/setup` +- Server-side page access restricted to `super_admin` +- Admin navigation item for `Setup Wizard` + +Added setup UI orchestration: + +- `src/features/setup/components/setup-wizard.tsx` +- `src/app/dashboard/administration/setup/page.tsx` + +Extended setup API client surface: + +- `src/features/setup/api/types.ts` +- `src/features/setup/api/service.ts` +- `src/features/setup/api/queries.ts` +- `src/features/setup/api/use-csv-commit.ts` +- `src/features/setup/api/use-setup-verification.ts` + +## Behavior + +- Step 1 calls setup readiness and displays overall status, PASS / WARNING / FAIL counts, grouped checks, suggested fixes, loading, error, and permission states. +- Step 2 calls template metadata and displays registered setup templates from the existing setup template API. +- Step 3 accepts one or more CSV files and does not upload them until the user clicks preview. +- Step 4 calls CSV preview, stores `previewHash` in client state only, displays per-file summaries, row/error/warning counts, and grouped safe error messages. +- Step 5 calls CSV commit with the latest `previewHash`. +- Commit is disabled when preview has `FAIL`. +- Commit requires warning acknowledgement when preview has `WARNING`. +- File changes after preview clear commit eligibility and require re-preview, using file name + size + lastModified fingerprint. +- Step 6 calls setup verification and displays grouped checks plus summary counts. +- Step 7 summarizes readiness, preview, commit, and verification results for the current browser session. + +## Security And API Notes + +- Setup page uses `auth()` and `PageContainer` access fallback for non-super-admin users. +- Existing setup APIs remain the enforcement boundary and still require `super_admin`. +- Upload requests use `FormData` directly so browser-managed multipart boundaries are preserved. +- UI reads safe API error messages and does not render raw stack traces. +- CSV files are uploaded only to the preview and commit endpoints. +- The wizard does not log CSV data. + +## Out Of Scope Preserved + +- No setup state persistence. +- No seed manifest persistence. +- No destructive reset or reseed behavior. +- No seed execution. +- No database migrations. +- No new CRM behavior. +- No file download or ZIP template generation. + +Persistent setup runs and resume behavior remain deferred to D.7.8. + +## Validation + +- `npm run typecheck -- --pretty false` passed. +- `npx oxlint src/features/setup/api/types.ts src/features/setup/api/service.ts src/features/setup/api/queries.ts src/features/setup/api/use-csv-commit.ts src/features/setup/api/use-setup-verification.ts src/features/setup/components/setup-wizard.tsx src/app/dashboard/administration/setup/page.tsx src/config/nav-config.ts` passed. +- Existing dev server was already running at `http://localhost:3000`. +- `curl.exe -I -L --max-time 20 http://localhost:3000/dashboard/administration/setup` returned `200 OK`. + +## Residual Notes + +- `plans/task-d.7.6.md` was already untracked during implementation and was not modified as part of this delivery. +- The wizard is intentionally session-only until the D.7.8 setup-state persistence slice. diff --git a/plans/task-d.7.6.md b/plans/task-d.7.6.md new file mode 100644 index 0000000..ec69937 --- /dev/null +++ b/plans/task-d.7.6.md @@ -0,0 +1,486 @@ +# Task D.7.6 – Dashboard Setup Wizard UI + +## Objective + +Build a dashboard-native Setup Wizard UI for ALLA OS Administration. + +The wizard must connect the previously implemented setup APIs into one guided workflow: + +- D.7.2 Readiness and Verification APIs +- D.7.3 CSV Template Pack and Template Metadata API +- D.7.4 CSV Preview Validation Engine +- D.7.5 CSV Commit Import Engine + +This task focuses on UI and orchestration only. + +Do not implement seed execution, destructive reset, setup state persistence, seed manifest persistence, or new migrations in this task. + +--- + +## Review Before Implementation + +Review: + +- AGENTS.md +- plans/task-d.7.md +- plans/task-d.7.1.md +- plans/task-d.7.2.md +- plans/task-d.7.3.md +- plans/task-d.7.4.md +- plans/task-d.7.5.md +- docs/setup/seed-matrix.md +- docs/setup/setup-wizard-blueprint.md +- docs/setup/setup-state-machine.md +- docs/setup/verification-matrix.md +- docs/setup/csv-template-spec.md +- docs/setup/import-mapping.md +- setup/templates/README.md +- setup/templates/templates.json +- src/features/setup/api/types.ts +- src/features/setup/api/service.ts +- src/features/setup/api/queries.ts +- src/app/api/setup/readiness/route.ts +- src/app/api/setup/verify/route.ts +- src/app/api/setup/templates/route.ts +- src/app/api/setup/import/preview/route.ts +- src/app/api/setup/import/commit/route.ts +- existing dashboard pages and PageContainer usage +- src/config/nav-config.ts + +--- + +## Implementation Scope + +Create dashboard page: + +src/app/dashboard/administration/setup/page.tsx + +Create UI components under: + +src/features/setup/components/ + +Recommended components: + +- setup-wizard.tsx +- setup-wizard-stepper.tsx +- setup-status-badge.tsx +- setup-check-list.tsx +- setup-summary-cards.tsx +- setup-template-list.tsx +- csv-upload-dropzone.tsx +- csv-preview-table.tsx +- csv-import-report.tsx +- setup-verification-summary.tsx +- setup-finish-panel.tsx + +Optional hooks: + +src/features/setup/api/use-setup-readiness.ts +src/features/setup/api/use-setup-templates.ts +src/features/setup/api/use-csv-preview.ts +src/features/setup/api/use-csv-commit.ts +src/features/setup/api/use-setup-verification.ts + +--- + +## Route and Navigation + +Add menu item under Administration: + +Administration + + Setup Wizard + +Target route: + +/dashboard/administration/setup + +The route must be protected by existing dashboard/auth rules. + +If setup APIs require `super_admin`, the UI should show a friendly permission message when unauthorized. + +--- + +## Wizard Steps + +Implement these UI steps. + +### Step 1 – System Readiness + +Call: + +GET /api/setup/readiness + +Show: + +- Overall status +- PASS / WARNING / FAIL counts +- Check list grouped by area +- Suggested fixes +- Re-run button + +Blocking behavior: + +- FAIL should block moving forward unless step is marked dry-run / review-only. +- WARNING can continue with acknowledgement. + +--- + +### Step 2 – Template Pack + +Call: + +GET /api/setup/templates + +Show: + +- available CSV templates +- import order +- seed type +- required columns +- sample file name +- unsupported/deferred templates from README or metadata if available + +Optional: + +- link to local setup/templates path in documentation text only +- do not implement file download unless already available + +--- + +### Step 3 – CSV Upload + +Allow upload of multiple CSV files. + +Rules: + +- accept only `.csv` +- show selected files +- allow remove/replace +- show detected file count +- show missing recommended templates as info only +- do not upload automatically until Preview button is clicked + +--- + +### Step 4 – CSV Preview + +Call: + +POST /api/setup/import/preview + +Input: + +multipart/form-data files + +Show: + +- previewHash +- overall status +- per-file status +- row count +- create/update/error/warning counts +- row-level errors +- row-level warnings +- dependency warnings +- duplicate warnings + +Blocking behavior: + +- FAIL blocks commit. +- WARNING requires acknowledgement before commit. +- Store previewHash in client state only for this task. + +--- + +### Step 5 – CSV Commit + +Call: + +POST /api/setup/import/commit + +Input: + +same files + previewHash + +Support: + +- dryRun toggle +- commit button +- show import report + +Rules: + +- Commit button disabled when preview has blocking errors. +- If files changed after preview, force re-preview. +- If commit returns previewHash mismatch, show clear message and require re-preview. + +--- + +### Step 6 – Verification + +Call: + +POST /api/setup/verify + +Input: + +optional organizationId / active org context + +Show: + +- overall status +- PASS / WARNING / FAIL counts +- grouped checks +- suggested fixes +- re-run button + +--- + +### Step 7 – Finish + +Show: + +- readiness summary +- template summary +- preview summary +- commit summary +- verification summary +- next recommended task + +Because setup state persistence is out of scope, do not permanently lock or complete setup. + +Show clear message: + +This wizard session is not persisted yet. Persistent setup runs will be implemented in D.7.8. + +--- + +## UI Requirements + +Use: + +- shadcn/ui +- PageContainer +- Card +- Button +- Badge +- Alert +- Table +- Tabs or Accordion where helpful +- Progress / Stepper +- ScrollArea for long reports +- responsive layout +- dark mode compatible styles + +Avoid: + +- hardcoded colors outside design tokens +- raw JSON dumps as main UI +- giant ungrouped error lists +- hidden errors + +--- + +## UX Requirements + +The wizard should feel like an enterprise setup console. + +Must include: + +- clear progress +- clear blocking vs non-blocking issues +- grouped diagnostics +- concise summary cards +- actionable suggested fixes +- retry/re-run buttons +- empty states +- loading states +- error states +- permission denied state + +Recommended status language: + +PASS + +WARNING + +FAIL + +Do not translate status enum values in API handling. + +--- + +## State Management + +D.7.6 uses client-side state only. + +Track: + +- current step +- readiness report +- template metadata +- selected files +- preview report +- previewHash +- commit report +- verification report +- warning acknowledgement + +Do not implement DB persistence. + +If browser refreshes, state can reset. + +Add note that persistent resume will arrive in D.7.8. + +--- + +## API Integration Rules + +- Reuse existing setup API types. +- Do not duplicate response shapes manually if types exist. +- Handle non-200 responses safely. +- Show safe error message from API contract. +- Do not display raw stack traces. +- Do not log sensitive CSV data. +- Do not upload files except to preview/commit endpoints. + +--- + +## CSV File Change Rules + +After successful preview: + +- if selected files are removed, added, renamed, or content changed +- clear previewHash +- clear commit report +- require preview again + +Implementation can use file name + size + lastModified fingerprint for this task. + +--- + +## Out Of Scope + +- Persistent setup state +- setup_runs / setup_run_steps migrations +- seed_manifests persistence +- seed execution +- scoped reset/reseed UI +- CSV template ZIP download +- production setup lock +- installation profile engine +- plugin runtime +- real file attachment import +- email settings persistence +- new CRM behavior + +--- + +## Deliverables + +Required: + +src/app/dashboard/administration/setup/page.tsx + +src/features/setup/components/setup-wizard.tsx +src/features/setup/components/setup-wizard-stepper.tsx +src/features/setup/components/setup-status-badge.tsx +src/features/setup/components/setup-check-list.tsx +src/features/setup/components/setup-summary-cards.tsx +src/features/setup/components/setup-template-list.tsx +src/features/setup/components/csv-upload-dropzone.tsx +src/features/setup/components/csv-preview-table.tsx +src/features/setup/components/csv-import-report.tsx +src/features/setup/components/setup-verification-summary.tsx +src/features/setup/components/setup-finish-panel.tsx + +Navigation update: + +src/config/nav-config.ts + +Optional: + +src/features/setup/api/use-setup-readiness.ts +src/features/setup/api/use-setup-templates.ts +src/features/setup/api/use-csv-preview.ts +src/features/setup/api/use-csv-commit.ts +src/features/setup/api/use-setup-verification.ts + +--- + +## Acceptance Criteria + +✓ Dashboard route exists at /dashboard/administration/setup. + +✓ Navigation exposes Setup Wizard under Administration. + +✓ Stepper wizard renders with all required steps. + +✓ Readiness step calls GET /api/setup/readiness. + +✓ Template step calls GET /api/setup/templates. + +✓ CSV upload accepts multiple CSV files. + +✓ Preview step calls POST /api/setup/import/preview. + +✓ Preview report displays file and row-level validation results. + +✓ Commit step calls POST /api/setup/import/commit with previewHash. + +✓ Commit is disabled when preview has FAIL. + +✓ File changes after preview clear previewHash and require re-preview. + +✓ Verification step calls POST /api/setup/verify. + +✓ Finish step summarizes all prior outputs. + +✓ UI handles loading, empty, error, unauthorized, warning, and success states. + +✓ Uses shadcn/ui and existing dashboard layout conventions. + +✓ No setup data persistence or reset logic is introduced. + +✓ Typecheck passes or unrelated failures are documented. + +--- + +## Suggested Verification + +Run: + +npm run typecheck + +Run scoped lint: + +npx oxlint src/features/setup src/app/dashboard/administration/setup src/config/nav-config.ts + +Manual test: + +1. Open /dashboard/administration/setup as super_admin. +2. Run readiness. +3. View template pack. +4. Upload valid CSV batch. +5. Run preview. +6. Confirm previewHash appears. +7. Run dryRun commit. +8. Run commit. +9. Re-run verification. +10. Confirm finish summary. +11. Modify selected file after preview. +12. Confirm previewHash clears. +13. Login as non-super_admin and confirm friendly unauthorized behavior. + +--- + +## Follow-up + +After D.7.6: + +D.7.8 – Seed Manifest and Setup State Persistence + +D.7.7 – Scoped Reset and Reseed Engine + +D.7.9 – Installation Profile and Plugin Runtime Enhancement diff --git a/src/app/dashboard/administration/setup/page.tsx b/src/app/dashboard/administration/setup/page.tsx new file mode 100644 index 0000000..cacf3cc --- /dev/null +++ b/src/app/dashboard/administration/setup/page.tsx @@ -0,0 +1,27 @@ +import { auth } from '@/auth'; +import PageContainer from '@/components/layout/page-container'; +import { SetupWizard } from '@/features/setup/components/setup-wizard'; + +export const metadata = { + title: 'Dashboard: Administration Setup' +}; + +export default async function AdministrationSetupPage() { + const session = await auth(); + const canAccessSetup = session?.user?.systemRole === 'super_admin'; + + return ( + + Setup administration requires a super admin account. + + } + > + + + ); +} diff --git a/src/config/nav-config.ts b/src/config/nav-config.ts index 078ee96..dcc8578 100644 --- a/src/config/nav-config.ts +++ b/src/config/nav-config.ts @@ -170,17 +170,25 @@ import { NavGroup } from "@/types"; // shortcut: ["d", "d"], // items: [], // }, - { - title: "Workspaces", - url: "/dashboard/workspaces", - icon: "workspace", - isActive: false, + { + title: "Workspaces", + url: "/dashboard/workspaces", + icon: "workspace", + isActive: false, items: [], - access: { systemRole: "super_admin" }, - }, - { - title: "Teams", - url: "/dashboard/workspaces/team", + access: { systemRole: "super_admin" }, + }, + { + title: "Setup Wizard", + url: "/dashboard/administration/setup", + icon: "settings", + isActive: false, + items: [], + access: { systemRole: "super_admin" }, + }, + { + title: "Teams", + url: "/dashboard/workspaces/team", icon: "teams", isActive: false, items: [], diff --git a/src/features/setup/api/queries.ts b/src/features/setup/api/queries.ts index 2757816..dbdefd4 100644 --- a/src/features/setup/api/queries.ts +++ b/src/features/setup/api/queries.ts @@ -1,10 +1,11 @@ import { queryOptions } from '@tanstack/react-query'; -import { getSetupReadiness, verifySetup } from './service'; +import { getSetupReadiness, getSetupTemplates, verifySetup } from './service'; import type { SetupVerificationPayload } from './types'; export const setupKeys = { all: ['setup'] as const, readiness: () => [...setupKeys.all, 'readiness'] as const, + templates: () => [...setupKeys.all, 'templates'] as const, verification: (payload: SetupVerificationPayload = {}) => [...setupKeys.all, 'verification', payload] as const }; @@ -15,6 +16,12 @@ export const setupReadinessQueryOptions = () => queryFn: () => getSetupReadiness() }); +export const setupTemplatesQueryOptions = () => + queryOptions({ + queryKey: setupKeys.templates(), + queryFn: () => getSetupTemplates() + }); + export const setupVerificationQueryOptions = (payload: SetupVerificationPayload = {}) => queryOptions({ queryKey: setupKeys.verification(payload), diff --git a/src/features/setup/api/service.ts b/src/features/setup/api/service.ts index 6083833..b650019 100644 --- a/src/features/setup/api/service.ts +++ b/src/features/setup/api/service.ts @@ -1,14 +1,26 @@ -import { apiClient } from '@/lib/api-client'; +import { ApiClientError, apiClient } from '@/lib/api-client'; import type { + CsvPreviewResult, + ImportReport, SetupReadinessReport, + SetupTemplatesResponse, SetupVerificationPayload, SetupVerificationReport } from './types'; +interface ApiErrorPayload { + message?: string; + error?: string; +} + export async function getSetupReadiness(): Promise { return apiClient('/setup/readiness'); } +export async function getSetupTemplates(): Promise { + return apiClient('/setup/templates'); +} + export async function verifySetup( payload: SetupVerificationPayload = {} ): Promise { @@ -17,3 +29,50 @@ export async function verifySetup( body: JSON.stringify(payload) }); } + +async function readUploadResponse(response: Response): Promise { + if (response.ok) { + return response.json() as Promise; + } + + const payload = (await response.json().catch(() => null)) as ApiErrorPayload | null; + const message = payload?.message ?? payload?.error ?? `API error: ${response.status}`; + + throw new ApiClientError(message, { + status: response.status, + statusText: response.statusText, + payload + }); +} + +function buildCsvFormData(files: File[]): FormData { + const formData = new FormData(); + files.forEach((file) => formData.append('files', file, file.name)); + return formData; +} + +export async function previewSetupCsvFiles(files: File[]): Promise { + return readUploadResponse( + await fetch('/api/setup/import/preview', { + method: 'POST', + body: buildCsvFormData(files) + }) + ); +} + +export async function commitSetupCsvFiles(input: { + files: File[]; + previewHash: string; + dryRun?: boolean; +}): Promise { + const formData = buildCsvFormData(input.files); + formData.set('previewHash', input.previewHash); + formData.set('dryRun', String(Boolean(input.dryRun))); + + return readUploadResponse( + await fetch('/api/setup/import/commit', { + method: 'POST', + body: formData + }) + ); +} diff --git a/src/features/setup/api/types.ts b/src/features/setup/api/types.ts index 9c2b3f8..c648c76 100644 --- a/src/features/setup/api/types.ts +++ b/src/features/setup/api/types.ts @@ -37,3 +37,36 @@ export interface SetupVerificationPayload { export interface SetupVerificationReport extends SetupReadinessReport { organizationId: string | null; } + +export interface SetupTemplateMetadataItem { + file: string; + seedType: string; + importOrder: number; + supported: boolean; + requiredColumns: string[]; + description: string; +} + +export interface SetupTemplatesResponse { + success: boolean; + time: string; + version: string; + generatedFrom: string; + templates: SetupTemplateMetadataItem[]; +} + +export type { + CsvPreviewError, + CsvPreviewFileResult, + CsvPreviewRow, + CsvPreviewResult, + CsvPreviewStatus, + CsvPreviewWarning +} from '@/features/setup/server/csv/types'; + +export type { + ImportFileReport, + ImportReport, + ImportRowReport, + ImportStatus +} from '@/features/setup/server/csv/import-report'; diff --git a/src/features/setup/api/use-csv-commit.ts b/src/features/setup/api/use-csv-commit.ts new file mode 100644 index 0000000..c4bb128 --- /dev/null +++ b/src/features/setup/api/use-csv-commit.ts @@ -0,0 +1,17 @@ +'use client'; + +import { useMutation } from '@tanstack/react-query'; +import { commitSetupCsvFiles, previewSetupCsvFiles } from './service'; + +export function useCsvPreview() { + return useMutation({ + mutationFn: (files: File[]) => previewSetupCsvFiles(files) + }); +} + +export function useCsvCommit() { + return useMutation({ + mutationFn: (input: { files: File[]; previewHash: string; dryRun?: boolean }) => + commitSetupCsvFiles(input) + }); +} diff --git a/src/features/setup/api/use-setup-verification.ts b/src/features/setup/api/use-setup-verification.ts new file mode 100644 index 0000000..4f5a019 --- /dev/null +++ b/src/features/setup/api/use-setup-verification.ts @@ -0,0 +1,11 @@ +'use client'; + +import { useMutation } from '@tanstack/react-query'; +import { verifySetup } from './service'; +import type { SetupVerificationPayload } from './types'; + +export function useSetupVerification() { + return useMutation({ + mutationFn: (payload: SetupVerificationPayload = {}) => verifySetup(payload) + }); +} diff --git a/src/features/setup/components/setup-wizard.tsx b/src/features/setup/components/setup-wizard.tsx index 42e252e..76fe77a 100644 --- a/src/features/setup/components/setup-wizard.tsx +++ b/src/features/setup/components/setup-wizard.tsx @@ -1,410 +1,675 @@ 'use client'; -import Link from 'next/link'; -import { useEffect, useMemo, useState } from 'react'; +import { useMemo, useState } from 'react'; +import { useQuery } from '@tanstack/react-query'; import { Icons } from '@/components/icons'; +import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Checkbox } from '@/components/ui/checkbox'; import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; -import { Textarea } from '@/components/ui/textarea'; import { cn } from '@/lib/utils'; +import { useCsvCommit, useCsvPreview } from '../api/use-csv-commit'; +import { useSetupVerification } from '../api/use-setup-verification'; +import { setupReadinessQueryOptions, setupTemplatesQueryOptions } from '../api/queries'; +import type { + CsvPreviewError, + CsvPreviewFileResult, + CsvPreviewResult, + ImportReport, + SetupCheckResult, + SetupCheckStatus, + SetupReadinessReport, + SetupReportSummary, + SetupTemplateMetadataItem, + SetupVerificationReport +} from '../api/types'; -type WizardState = { - appUrl: string; - authSecret: string; - dbHost: string; - dbPort: string; - dbName: string; - dbUser: string; - dbPassword: string; - superAdminEmail: string; - superAdminPassword: string; - superAdminName: string; - resetPassword: boolean; - buildStandalone: boolean; -}; +type WizardStepKey = + | 'readiness' + | 'templates' + | 'upload' + | 'preview' + | 'commit' + | 'verification' + | 'finish'; -const steps = [ - 'Project URL', - 'Database', - 'Auth + seed', - 'Preview .env.local' -] as const; +const WIZARD_STEPS: Array<{ key: WizardStepKey; label: string }> = [ + { key: 'readiness', label: 'Readiness' }, + { key: 'templates', label: 'Templates' }, + { key: 'upload', label: 'CSV Upload' }, + { key: 'preview', label: 'Preview' }, + { key: 'commit', label: 'Commit' }, + { key: 'verification', label: 'Verification' }, + { key: 'finish', label: 'Finish' } +]; -function generateSecret() { - const bytes = new Uint8Array(32); - crypto.getRandomValues(bytes); - return Array.from(bytes, (value) => value.toString(16).padStart(2, '0')).join(''); +function getErrorMessage(error: unknown): string { + if (error instanceof Error) return error.message; + return 'Unable to complete setup request.'; } -function download(filename: string, content: string) { - const blob = new Blob([content], { type: 'text/plain;charset=utf-8' }); - const url = URL.createObjectURL(blob); - const anchor = document.createElement('a'); - anchor.href = url; - anchor.download = filename; - anchor.click(); - URL.revokeObjectURL(url); +function getFileFingerprint(files: File[]): string { + return files + .map((file) => `${file.name}:${file.size}:${file.lastModified}`) + .toSorted() + .join('|'); } -export function SetupWizard() { - const [copied, setCopied] = useState(false); - const [state, setState] = useState({ - appUrl: 'http://localhost:3000', - authSecret: '', - dbHost: 'localhost', - dbPort: '5432', - dbName: 'training_system', - dbUser: 'postgres', - dbPassword: 'postgres', - superAdminEmail: 'admin@example.com', - superAdminPassword: 'change-this-password', - superAdminName: 'Super Admin', - resetPassword: false, - buildStandalone: false +function getStatusVariant(status: SetupCheckStatus): 'default' | 'secondary' | 'destructive' { + if (status === 'FAIL') return 'destructive'; + if (status === 'WARNING') return 'secondary'; + return 'default'; +} + +function getPreviewStatus(preview: CsvPreviewResult | null): SetupCheckStatus | null { + if (!preview) return null; + if (preview.summary.error > 0 || preview.files.some((file) => file.status === 'FAIL')) return 'FAIL'; + if (preview.summary.warning > 0 || preview.files.some((file) => file.status === 'WARNING')) { + return 'WARNING'; + } + return 'PASS'; +} + +function groupChecks(checks: SetupCheckResult[]): Array<[string, SetupCheckResult[]]> { + const groups = new Map(); + checks.forEach((check) => { + const current = groups.get(check.area) ?? []; + current.push(check); + groups.set(check.area, current); }); + return Array.from(groups.entries()); +} - useEffect(() => { - setState((current) => - current.authSecret ? current : { ...current, authSecret: generateSecret() } - ); - }, []); +function StatusBadge({ status }: { status: SetupCheckStatus }) { + return {status}; +} - const databaseUrl = useMemo(() => { - const user = encodeURIComponent(state.dbUser); - const password = encodeURIComponent(state.dbPassword); - return `postgres://${user}:${password}@${state.dbHost}:${state.dbPort}/${state.dbName}`; - }, [state.dbHost, state.dbName, state.dbPassword, state.dbPort, state.dbUser]); - - const envContent = useMemo( - () => - [ - '# Generated by /setup', - '', - '# Authentication (Auth.js)', - `AUTH_SECRET=${state.authSecret}`, - '', - '# Optional local app URL reference', - `APP_URL=${state.appUrl}`, - '', - '# Database (PostgreSQL)', - `DATABASE_URL=${databaseUrl}`, - '', - '# Initial Super Admin Seed', - `SUPER_ADMIN_EMAIL=${state.superAdminEmail}`, - `SUPER_ADMIN_PASSWORD=${state.superAdminPassword}`, - `SUPER_ADMIN_NAME=${state.superAdminName}`, - `SUPER_ADMIN_RESET_PASSWORD=${state.resetPassword ? 'true' : 'false'}`, - '', - '# Build Configuration', - `BUILD_STANDALONE=${state.buildStandalone ? 'true' : ''}`, - '', - '# Sentry (optional)', - 'NEXT_PUBLIC_SENTRY_DSN=', - 'NEXT_PUBLIC_SENTRY_ORG=', - 'NEXT_PUBLIC_SENTRY_PROJECT=', - 'SENTRY_AUTH_TOKEN=', - 'NEXT_PUBLIC_SENTRY_DISABLED="false"' - ].join('\n'), - [databaseUrl, state] +function SummaryPills({ summary }: { summary: SetupReportSummary }) { + return ( +
+ + + + +
); +} - async function copyToClipboard() { - await navigator.clipboard.writeText(envContent); - setCopied(true); - window.setTimeout(() => setCopied(false), 2000); +function Metric({ label, value }: { label: string; value: number | string }) { + return ( +
+
{label}
+
{value}
+
+ ); +} + +function ReportCheckList({ checks }: { checks: SetupCheckResult[] }) { + if (checks.length === 0) { + return ; } - const completedSteps = [ - !!state.appUrl.trim(), - !!state.dbHost.trim() && !!state.dbPort.trim() && !!state.dbName.trim(), - !!state.authSecret.trim() && !!state.superAdminEmail.trim() && !!state.superAdminPassword.trim(), - true - ].filter(Boolean).length; - return ( -
-
-
-
-
- - 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. - - - - 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 })} - /> - - - -
- - - - Database configuration - - Fill in the PostgreSQL connection details. The wizard will build - DATABASE_URL for you. - - - -
- - setState({ ...state, dbHost: event.target.value })} - /> -
-
- - setState({ ...state, dbPort: event.target.value })} - /> -
-
- - setState({ ...state, dbName: event.target.value })} - /> -
-
- - setState({ ...state, dbUser: event.target.value })} - /> -
-
- - setState({ ...state, dbPassword: event.target.value })} - /> -
-
- - -
-
-
- - - - Initial super admin seed - - These values feed the seed script used by npm run setup:db. - - - -
- - - setState({ ...state, superAdminEmail: event.target.value }) - } - /> -
-
- - - setState({ ...state, superAdminPassword: event.target.value }) - } - /> -
-
- - - setState({ ...state, superAdminName: event.target.value }) - } - /> -
-
- -
- - setState({ ...state, resetPassword: event.target.checked }) - } - className='mr-3' - /> - Reset the seeded super admin password on reruns -
-
-
- -
- - 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. - - - -