- Introduction
- Pitch
- Hogwarts
- Live Demo
- MVP
- Roadmap
- Launch Sprint
- PRD
- 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
- Playwright
- Prettier
- Block Rebound
Students enter the system through six entry points, all unified under a single validation schema and wizard infrastructure. Incomplete records are clearly marked for completion.
Entry points
1. Admin Wizard → 7 steps, 35+ fields, draft flow
2. CSV Bulk Import → ~12 fields, marked incomplete
3. Internal Onboarding → ~21 fields, marked incomplete
4. Application Enroll → ~15 fields from Application
5. Profile Self-Edit → Contact, emergency contacts
6. Admin Detail Edit → Link to wizard at any step
Wizard steps
7-step wizard creates a draft record on entry and progressively fills in fields. Each step saves independently. A "Skip & Create" button appears once firstName + lastName are filled — saves current step, marks complete, redirects to list.
| # | Step | Route | Fields | Required |
|---|---|---|---|---|
| 1 | Photo | /photo | profilePhotoUrl | None |
| 2 | Personal | /personal | firstName, middleName, lastName, dateOfBirth, gender, nationality | firstName, lastName |
| 3 | Enrollment | /enrollment | enrollmentDate, admissionNumber, status, studentType, academicGradeId, sectionId | None (defaults) |
| 4 | Contact | /contact | email, mobileNumber, alternatePhone, emergencyContactName, emergencyContactPhone, emergencyContactRelation | None |
| 5 | Location | /location | currentAddress, city, state, postalCode, country | None |
| 6 | Health | /health | medicalConditions, allergies, medicationRequired, bloodGroup, doctorName, doctorContact, insuranceProvider, insuranceNumber | None |
| 7 | Previous Education | /previous-education | previousSchoolName, previousSchoolAddress, previousGrade, transferCertificateNo, transferDate, previousAcademicRecord | None |
| Group | Label | Steps |
|---|---|---|
| 1 | Essentials | photo, personal, enrollment |
| 2 | Contact Details | contact, location |
| 3 | Health & History | health, previous-education |
Routes
src/app/[lang]/s/[subdomain]/(school-dashboard)/(listings)/students/
add/[id]/
layout.tsx # Wizard layout with step navigation
photo/page.tsx
personal/page.tsx
enrollment/page.tsx
contact/page.tsx
location/page.tsx
health/page.tsx
previous-education/page.tsx
Draft lifecycle
1. Admin clicks "Add Student"
2. Draft created: { firstName: "", dateOfBirth: new Date(), wizardStep: "photo" }
3. Each step saves fields + advances wizardStep
4. Two completion paths:
a. FAST: firstName + lastName → "Skip & Create" → wizardStep: null
b. FULL: All 7 steps → last step sets wizardStep: null
5. Complete students appear in main list (wizardStep: null filter)
6. Abandoned drafts deletable via deleteDraftStudent()
Component structure
src/components/school-dashboard/listings/students/
├── content.tsx # Server — list page
├── table.tsx # Client — DataTable
├── columns.tsx # Client — incomplete badge
├── actions.ts # createStudent, updateStudent, deleteStudent
├── queries.ts # buildStudentWhere (supports includeDrafts)
├── validation.ts # Composed from wizard step schemas
├── config.ts # Enum constants (GENDER_OPTIONS)
├── authorization.ts # RBAC
├── types.ts # StudentDTO, StudentRow, form step types
├── detail/content.tsx # Profile view with edit link to wizard
└── wizard/
├── actions.ts # createDraftStudent, completeStudentWizard, updateStudentWizardStep
├── config.ts # Wizard step config
├── use-student-wizard.ts
├── photo/ # Step 1
├── personal/ # Step 2 — required: firstName, lastName
├── enrollment/ # Step 3
├── contact/ # Step 4 — emergency tab
├── location/ # Step 5 — Mapbox or manual
├── health/ # Step 6 — doctor / insurance tab
└── previous-education/ # Step 7 — final, triggers complete
Validation
validation.ts composes from wizard step schemas — single source of truth for wizard, server actions, CSV import, and onboarding.
import { contactSchema } from "./wizard/contact/validation"
import { emergencySchema } from "./wizard/emergency/validation"
import { enrollmentSchema } from "./wizard/enrollment/validation"
import { healthSchema } from "./wizard/health/validation"
import { personalSchema } from "./wizard/personal/validation"
import { previousEducationSchema } from "./wizard/previous-education/validation"
export const studentCreateSchema = personalSchema
.merge(contactSchema)
.merge(emergencySchema)
.merge(enrollmentSchema)
.merge(healthSchema)
.merge(previousEducationSchema)
.extend({
userId: z.string().optional(),
})Only personal is required (requiredSteps: ["personal"]). All other steps are optional.
Entry point details
CSV bulk import
// csv-import.ts
await tx.student.createMany({
data: chunk.map((r) => ({
schoolId,
firstName: parts[0],
lastName: parts.slice(1).join(" "),
email: r.validated.email,
wizardStep: "emergency", // CSV covers personal + contact only
})),
})CSV-imported students appear in the "Incomplete" tab. Click to open wizard at the emergency step.
Internal onboarding
When a student joins through the school's join page, onboarding creates:
- Student with personal + contact + enrollment +
wizardStep: "health" - Guardian records (if guardian info provided)
- Section assignment (if selected)
The sectionId set here (or in the wizard's Enrollment step) is the canonical coupling: Student.sectionId → Section → Section.classroomId (homeroom). Classrooms does not own students — placing a student in a grade + section is done here and in the other entry points above. See Classrooms › Integration.
Application enrollment
confirmEnrollment() in admission creates:
await tx.student.create({
data: {
schoolId,
firstName: application.firstName,
lastName: application.lastName,
dateOfBirth: application.dateOfBirth,
gender: application.gender,
email: application.email,
emergencyContactName: application.guardianName || application.fatherName,
emergencyContactPhone: application.guardianPhone || application.fatherPhone,
emergencyContactRelation: application.guardianRelation || "Parent",
wizardStep: "health", // Application doesn't collect health data
},
})Enrolled students appear in the "Incomplete" tab. Admins finish health and previous education via the wizard.
Profile self-edit
Students can edit their own contact and emergency contacts. They cannot edit personal info, enrollment details, health, or previous education (admin only). Self-edit uses the same wizard step forms in a profile dialog.
Admin detail edit
Detail view's "Edit" links to /{lang}/students/add/{studentId}/personal — wizard pre-populated with existing data.
Incomplete records
| Source | wizardStep | Fields covered |
|---|---|---|
| Wizard (in progress) | Current step | All steps up to current |
| CSV import | "emergency" | Name, email, phone, grade |
| Internal onboarding | "health" | Name, email, phone, enrollment, emergency |
| Application enroll | "health" | Name, DOB, gender, email, emergency from guardian |
Incomplete records show an amber "Incomplete" badge on the name column. Status faceted filter includes "Incomplete". Action dropdown shows "Edit (Incomplete)" linking to the wizard at the incomplete step.
// Include incomplete records in queries
buildStudentWhere(schoolId, { includeDrafts: true })Config constants
| Constant | Values |
|---|---|
GENDER_OPTIONS | Male, Female |
Additional enums (blood groups, enrollment statuses) live in wizard step validation schemas.
Key files
| File | Purpose |
|---|---|
students/add/[id]/layout.tsx | Wizard layout |
students/wizard/actions.ts | createDraftStudent, completeStudentWizard, deleteDraftStudent |
students/wizard/config.ts | Step config |
students/validation.ts | Composed schema |
students/queries.ts | buildStudentWhere with includeDrafts |
students/columns.tsx | Incomplete badge + "Complete Profile" action |
internal-onboarding/actions.ts | Onboarding student creation with wizardStep |
admission/actions.ts | confirmEnrollment with emergency + wizardStep |
file/import/csv-import.ts | Bulk import |
profile/edit-role-data.tsx | Self-edit dialog |
profile/edit-role-actions.ts | Permission-checked self-edit server actions |