0%
balqalam Logo
balqalam
FeaturesCommunityPricingDocumentation
Login
  • Introduction
  • Help center
  • Common questions
  • 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

Onboarding

PreviousNext

Multi-step wizard for school registration, configuration, and business setup. 16 steps in 3 groups, ~166 files.

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

StepPathRequiredDatabase
About school/about-schoolNostatic welcome
Title/titleYesSchool.name
Description/descriptionYesschoolType, schoolLevel, planType
Location/locationYesaddress, city, state, country, latitude, longitude
Stand out/stand-outNostatic

Group 2 — School setup

StepPathRequiredDatabase
Capacity/capacityYesmaxStudents, maxTeachers, maxClasses
Branding/brandingNoprimaryColor, borderRadius, shadow
Import/importNobulk insert (CSV / Excel)
Finish setup/finish-setupNostatic 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

StepPathRequiredDatabase
Join/joinYesUser
Visibility/visibilityNoSchool.isPublished
Price/priceYestuitionFee, registrationFee, applicationFee, currency
Discount/discountNoDiscount model
Legal/legalYesLegalConsent
Subdomain/subdomainNoSchool.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/country as before, plus latitude/longitude on School, Student, Teacher and Application.

Auth handshake

Four-stage flow:

  1. Start — Unauthenticated user visits /get-started or /onboarding. start-onboarding action persists intent in a cookie and redirects to /login.
  2. Authenticate — Standard login. Callback URL routes back to /onboarding.
  3. Provision — initializeSchoolSetup() calls ensureUserSchool() (src/lib/school-access.ts) — atomic db.$transaction creates the School row, SchoolMember (binds user as ADMIN), seeds default YearLevels. Idempotent.
  4. Refresh + redirect — _sessionRefreshRequired: true triggers client refetch of /api/auth/session; _redirect sends to next step.

Ownership enforced by requireSchoolOwnership(id) on every step's load. Cross-tenant access raises TenantError.

Multi-tenant integration

  • Subdomain routing — /onboarding and children live on a main domain (ed.databayt.org or balqalam.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 by proxy.ts, which then re-derives the locale from the NEXT_LOCALE cookie or Accept-Language header rather than the page the user was on — flipping Arabic users to English mid-wizard. FormFooter navigates correctly on its own; steps overriding onNext should save only, or prefix params.lang themselves.
  • Database scoping — every read uses requireSchoolOwnership(id) first, then queries scoped by schoolId.
  • Session schoolId — JWT carries schoolId after 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.

StepCreates
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():

PriorityStrategyExample
1International school type → USany country + international
2Country exact matchUS → US, SD → SD
3Unmapped country → USJP, KE, …

API routes

PathMethodPurpose
/api/onboarding/create-schoolPOSTIdempotent atomic school creation; handles P2002 unique violation race
/api/onboarding/validate-accessPOSTPre-flight access check; auto-provisions; returns redirect on mismatch
/api/onboarding/extractPOSTAI document extraction (10 MB max; JPEG / PNG / WebP / PDF)

Server actions

In src/components/onboarding/actions.ts:

ActionPurpose
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

PathPurpose
src/app/[lang]/onboarding/[id]/[step]/page.tsxEach step page (16 routes)
src/components/onboarding/actions.tsServer actions
src/components/onboarding/config.tsStep config (authoritative)
src/components/onboarding/types.tsOnboardingStep, OnboardingSchoolData
src/components/onboarding/validation.tsRoot Zod schemas
src/components/onboarding/use-listing.tsxListingProvider context
src/components/onboarding/use-onboarding.tsNavigation + validation hook
src/components/onboarding/auth-helpers.tsgetAuthContext, requireSchoolOwnership
src/components/form/footer.tsxFormFooter + ONBOARDING_CONFIG
src/lib/school-access.tsensureUserSchool() atomic transaction
src/components/catalog/setup.tsProvisioning chain

Troubleshooting

SymptomCauseFix
Build hangs at "Environments: .env"TypeScript errors in onboarding componentsRun pnpm tsc --noEmit
Session missing schoolIdSession not refreshed after atomic transactionFetch /api/auth/session after _sessionRefreshRequired: true
Cross-tenant access deniedUser accessing a school they don't ownrequireSchoolOwnership(id) throws TenantError
Subdomain already takenAnother school reserved itAction returns errorCode: "DOMAIN_TAKEN"
Form data not persistingMissing revalidatePath() after mutationAll update actions call revalidatePath("/onboarding")
Dictionary property not foundMissing translation keyUse optional chaining: dictionary?.onboarding?.title?.label

See also

  • Multi-tenancy — auth handshake, tenant context
  • Pattern — mirror pattern, file conventions
  • Authentication — login flow
OfflineOnboarding Videos

On This Page

StepsGroup 1 — Basic informationGroup 2 — School setupGroup 3 — Business and legalLocation pickerAuth handshakeMulti-tenant integrationValidationAuto-provisioningAPI routesServer actionsKey filesTroubleshootingSee also

Built by Databayt ·

Welcome to balqalam.

A great journey is about to begin.