- Get Started
- Localhost
- Architecture
- Structure
- Pattern
- Page
- Layout
- Content
- Types
- Config
- Actions
- Queries
- Authorization
- Validation
- Form
- Table
- Detail
- Card
- Util
- Hooks
- List Params
- Views
- README.md
- ISSUE.md
- Technology Stack
- Database
- File
- CDN Assets
- Entry Points
- Dashboard
- Authentication
- Credentials
- OAuth
- Flow Diagrams
- Multi-Tenancy
- Offline
- Onboarding
- Onboarding Videos
- Add Values
- Admission
- Application
- Attendance
- Compliance
- Profile
- Exams
- Exam Wizard
- Timetable
- Classrooms
- Notifications
- Conference
- LMS (Lumos)
- Finance
- Fee Management
- Invoice
- Wallet
- Salary
- Payroll
- Timesheet
- Expenses
- Budget
- Receipt
- Accounts
- Banking
- Reports
- Dashboard
- Permissions
- Messages
- Integration Flow
- Provision
- AI Document Processing
- Document Intelligence
- Internationalization
- Translation
- Translation Guide
- Icons
- Docs Factory
- Inspiration
- Listings
- Teachers
- Students
- Catalog
- Library
- Contributing
- Code of conduct
- GitHub Workflow
- Database Seeds
- Database Safety
- Test Accounts
- Bulk import
- Playwright
- Prettier
- Block Rebound
- Transportation
The onboarding system guides new schools through registration, configuration, and business setup. Mirror pattern, ~166 files, 16 active steps in 3 groups, driven by WizardLayout. Typical completion: 10–30 minutes.
Steps
Group 1 — Basic information
| Step | Path | Required | Database |
|---|---|---|---|
| About school | /about-school | No | static welcome |
| Title | /title | Yes | School.name |
| Description | /description | Yes | schoolType, schoolLevel, planType |
| Location | /location | Yes | address, city, state, country, latitude, longitude |
| Stand out | /stand-out | No | static |
Group 2 — School setup
| Step | Path | Required | Database |
|---|---|---|---|
| Capacity | /capacity | Yes | maxStudents, maxTeachers, maxClasses |
| Branding | /branding | No | primaryColor, borderRadius, shadow |
| Import | /import | No | bulk insert (CSV / Excel) |
| Finish setup | /finish-setup | No | static review |
The schedule step is defined in OnboardingStep with its own component directory but is not in active navigation. Lives at order: 5.5 in config.ts for future activation.
Group 3 — Business and legal
| Step | Path | Required | Database |
|---|---|---|---|
| Join | /join | Yes | User |
| Visibility | /visibility | No | School.isPublished |
| Price | /price | Yes | tuitionFee, registrationFee, applicationFee, currency |
| Discount | /discount | No | Discount model |
| Legal | /legal | Yes | LegalConsent |
| Subdomain | /subdomain | No | School.domain |
After all steps the user lands on /congratulations for a smart redirect (school subdomain dashboard or main domain depending on session state).
Active step order is ONBOARDING_CONFIG in src/components/form/footer.tsx. Authoritative metadata (labels, validation, group, order) is ONBOARDING_STEPS in src/components/onboarding/config.ts.
Location picker
One component, src/components/atom/mapbox-location-picker.tsx, sets every address in the app. That includes this step, school settings, the student and teacher add wizards, the public application, and transportation stops. It replaces separate country, city and district fields with a single search field, the user's current location, and a map.
- Search uses Mapbox Geocoding v6, scoped to the school's country and ranked around the school. Typing "الرياض" from a Khartoum school finds the Khartoum neighborhood, not Saudi Arabia. If the school's country has no match, the search retries worldwide. Results go down to blocks such as "كافوري مربع 9". Use the arrow keys and Enter to pick, Esc to close.
- Starting view: the saved pin, then the school, then the school's country, then Khartoum. It never opens at 0,0. Under a school, the school is drawn as a reference marker.
- Landing: picking a result, using the current location or tapping the map moves the map immediately. The address arrives when reverse geocoding answers. Drag the pin to adjust. The address line under the map is editable, so you can add a house number or nearest landmark.
- Arabic: map labels follow the page language. The RTL text plugin is armed before every map is created.
- Storage:
address/city/state/countryas before, pluslatitude/longitudeonSchool,Student,TeacherandApplication.
Auth handshake
Four-stage flow:
- Start — Unauthenticated user visits
/get-startedor/onboarding.start-onboardingaction persists intent in a cookie and redirects to/login. - Authenticate — Standard login. Callback URL routes back to
/onboarding. - Provision —
initializeSchoolSetup()callsensureUserSchool()(src/lib/school-access.ts) — atomicdb.$transactioncreates theSchoolrow,SchoolMember(binds user as ADMIN), seeds defaultYearLevels. Idempotent. - Refresh + redirect —
_sessionRefreshRequired: truetriggers client refetch of/api/auth/session;_redirectsends to next step.
Ownership enforced by requireSchoolOwnership(id) on every step's load. Cross-tenant access raises TenantError.
Multi-tenant integration
- Subdomain routing —
/onboardingand children live on a main domain (ed.databayt.orgorbalqalam.com), not on a subdomain. Subdomain assignment happens at the Subdomain step, and the suffix shown there follows the root the visitor actually arrived on (rootDomainFromLocation()), so a school signing up via balqalam.com is quoted a balqalam.com URL. - Locale in step URLs — every onboarding URL must carry its
[lang]segment. A bare/onboarding/...path is treated as locale-less byproxy.ts, which then re-derives the locale from theNEXT_LOCALEcookie orAccept-Languageheader rather than the page the user was on — flipping Arabic users to English mid-wizard.FormFooternavigates correctly on its own; steps overridingonNextshould save only, or prefixparams.langthemselves. - Database scoping — every read uses
requireSchoolOwnership(id)first, then queries scoped byschoolId. - Session schoolId — JWT carries
schoolIdafter step 1; subsequent steps trust it after re-verification.
Validation
Each step has a Zod schema co-located with its component. Schemas use the i18n factory pattern — same schema produces Arabic or English error messages depending on the dictionary slice.
import { createTitleSchema } from "@/components/onboarding/title/validation"
const schema = createTitleSchema(dictionary)
const result = schema.safeParse(input)Root validation.ts exports validateStep(step, data, dictionary) and getRequiredFieldsForStep(step) for shared step-aware validation.
Auto-provisioning
When completeOnboarding() runs at the legal step, the school is marked active and a 5-step provisioning chain creates academic infrastructure. Each step is wrapped in try/catch so one failure doesn't block the rest.
Source: src/components/onboarding/legal/actions.ts → src/components/catalog/setup.ts.
| Step | Creates |
|---|---|
setupDefaultsForSchool() | YearLevels (8 / 6 / 14 by school level), 6 Departments, 9 ScoreRanges |
setupCatalogForSchool() | Subject / chapter / lesson bridges from the global catalog |
setupTermsForSchool() | Default terms for the academic year |
setupRolesForSchool() | Permission templates for the 8 roles |
setupNotificationsForSchool() | Default notification preferences for ADMIN role |
setupCatalogForSchool resolves curriculum via inferCurriculum():
| Priority | Strategy | Example |
|---|---|---|
| 1 | International school type → US | any country + international |
| 2 | Country exact match | US → US, SD → SD |
| 3 | Unmapped country → US | JP, KE, … |
API routes
| Path | Method | Purpose |
|---|---|---|
/api/onboarding/create-school | POST | Idempotent atomic school creation; handles P2002 unique violation race |
/api/onboarding/validate-access | POST | Pre-flight access check; auto-provisions; returns redirect on mismatch |
/api/onboarding/extract | POST | AI document extraction (10 MB max; JPEG / PNG / WebP / PDF) |
Server actions
In src/components/onboarding/actions.ts:
| Action | Purpose |
|---|---|
initializeSchoolSetup() | Creates or retrieves school for current user; returns _redirect |
getListing(id) | Fetch school with ownership verification |
updateListing(id, data) | Update fields for a step |
getSchoolSetupStatus(schoolId) | Completion percentage (6 checks) |
reserveSubdomainForSchool(id, sub) | Reserve subdomain (delegates to src/lib/subdomain-actions.ts) |
getCurrentUserSchool() | Returns current user's schoolId |
getUserSchools() | Lists schools owned by or associated with current user |
proceedToTitle(schoolId) | Validates ownership and redirects to about-school step |
Key files
| Path | Purpose |
|---|---|
src/app/[lang]/onboarding/[id]/[step]/page.tsx | Each step page (16 routes) |
src/components/onboarding/actions.ts | Server actions |
src/components/onboarding/config.ts | Step config (authoritative) |
src/components/onboarding/types.ts | OnboardingStep, OnboardingSchoolData |
src/components/onboarding/validation.ts | Root Zod schemas |
src/components/onboarding/use-listing.tsx | ListingProvider context |
src/components/onboarding/use-onboarding.ts | Navigation + validation hook |
src/components/onboarding/auth-helpers.ts | getAuthContext, requireSchoolOwnership |
src/components/form/footer.tsx | FormFooter + ONBOARDING_CONFIG |
src/lib/school-access.ts | ensureUserSchool() atomic transaction |
src/components/catalog/setup.ts | Provisioning chain |
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Build hangs at "Environments: .env" | TypeScript errors in onboarding components | Run pnpm tsc --noEmit |
Session missing schoolId | Session not refreshed after atomic transaction | Fetch /api/auth/session after _sessionRefreshRequired: true |
| Cross-tenant access denied | User accessing a school they don't own | requireSchoolOwnership(id) throws TenantError |
| Subdomain already taken | Another school reserved it | Action returns errorCode: "DOMAIN_TAKEN" |
| Form data not persisting | Missing revalidatePath() after mutation | All update actions call revalidatePath("/onboarding") |
| Dictionary property not found | Missing translation key | Use optional chaining: dictionary?.onboarding?.title?.label |
See also
- Multi-tenancy — auth handshake, tenant context
- Pattern — mirror pattern, file conventions
- Authentication — login flow