- 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
- 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
Sales & GTM
The platform has four entry points, each serving a different audience with its own access controls. Understanding the surfaces is critical for routing, RBAC, and user-experience design.
Overview
| # | Entry | URL | Audience | Auth |
|---|---|---|---|---|
| 1 | SaaS marketing | ed.databayt.org | Visitors, school owners, principals | None for browsing |
| 2 | Operator dashboard | ed.databayt.org/en/dashboard | Platform operators | DEVELOPER only |
| 3 | School site | {school}.databayt.org | School visitors, applicants | None |
| 4 | School platform | {school}.databayt.org/en/dashboard | Authenticated school users | Role-based |
1. SaaS marketing
Public landing for everyone — visitors, school owners, principals, teachers, students, parents, guests, investors, contributors.
| Component | Location | Purpose |
|---|---|---|
SiteHeader | components/template/marketing-header/ | Nav with login |
Hero | components/marketing/hero/ | "Get Started" CTA |
UserButton | components/auth/user-button.tsx | Login/avatar (variant="marketing") |
CommandMenu | components/template/marketing-header/ | Quick search |
| CTA | Destination | Auth |
|---|---|---|
| Login | /login | No |
| Get Started | /onboarding | Yes (redirect through login) |
| Join | /join | No |
Login redirect:
// src/components/auth/login/action.ts
if (existingUser.role === "DEVELOPER") {
finalRedirectUrl = `/${locale}/dashboard`
} else {
finalRedirectUrl = `/${locale}` // Homepage
}2. Operator dashboard
DEVELOPER only — platform-wide controls.
src/app/[lang]/(saas-dashboard)/
├── layout.tsx # SaaS auth guard (DEVELOPER only)
├── dashboard/page.tsx # Operator home
├── analytics/page.tsx
├── billing/page.tsx
├── domains/page.tsx
├── tenants/page.tsx # School / tenant management
├── product/page.tsx
├── sales/page.tsx
├── profile/page.tsx
├── kanban/page.tsx
├── observability/page.tsx
└── (catalog)/ # Catalog management
Access guard:
const session = await auth()
if (!session) redirect(`/${lang}/login?callbackUrl=/${lang}/dashboard`)
if (session.user?.role !== "DEVELOPER") redirect(`/${lang}/access-denied`)3. School site
Public school-branded site. The edge proxy detects the subdomain and rewrites:
hogwarts.databayt.org
→ middleware extracts "hogwarts"
→ rewrite to /[lang]/s/hogwarts/(site)/*
→ SchoolProvider loads school context
| Environment | URL pattern | Internal route |
|---|---|---|
| Production | school.databayt.org | /[lang]/s/school/(site)/* |
| Preview | school---branch.vercel.app | /[lang]/s/school/(site)/* |
| Development | school.localhost:3000 | /[lang]/s/school/(site)/* |
src/app/[lang]/s/[subdomain]/(site)/
├── page.tsx # School homepage
├── layout.tsx # SchoolProvider wrapper
├── about/page.tsx
├── academic/page.tsx
├── admissions/page.tsx
├── apply/page.tsx
├── tour/page.tsx
├── contact/page.tsx
└── events/page.tsx
| CTA | Destination | Auth |
|---|---|---|
| Apply Now | /{subdomain}/application | Yes (to save) |
| Schedule Tour | /{subdomain}/tour | No |
| Contact Us | /{subdomain}/contact | No |
| Go to Platform | /{subdomain}/dashboard | Yes |
All (site) routes are public (publicRoutes in src/routes.ts).
4. School platform
Authenticated school dashboard with role-based routing.
src/app/[lang]/s/[subdomain]/(school-dashboard)/
├── layout.tsx # PlatformLayout with sidebar
├── dashboard/page.tsx # Role-specific dashboard
├── admission/
├── attendance/ # ADMIN, TEACHER, STAFF
├── billing/
├── exams/ # ADMIN, TEACHER
├── finance/ # All authenticated (role-specific views)
├── library/ # All authenticated
├── messages/
├── notifications/
├── profile/
├── settings/ # ADMIN, DEVELOPER
├── timetable/ # ADMIN, TEACHER
└── (listings)/
├── announcements/ # ADMIN, TEACHER, STAFF
├── assignments/ # ADMIN, TEACHER
├── classes/ # ADMIN, TEACHER
├── events/ # All authenticated
├── grades/ # ADMIN, TEACHER
├── lessons/ # ADMIN, TEACHER
├── parents/ # ADMIN, TEACHER, STAFF
├── students/ # ADMIN, TEACHER, STAFF
├── subjects/ # ADMIN, TEACHER
└── teachers/ # ADMIN only
roleRoutes matrix
export const roleRoutes: Record<string, Role[]> = {
"/admin": ["ADMIN", "DEVELOPER"],
"/admin/*": ["ADMIN", "DEVELOPER"],
"/settings/*": ["ADMIN", "DEVELOPER"],
"/teachers": ["ADMIN", "DEVELOPER"],
"/subjects/*": ["ADMIN", "TEACHER", "DEVELOPER"],
"/grades/*": ["ADMIN", "TEACHER", "DEVELOPER"],
"/students/*": ["ADMIN", "TEACHER", "STAFF", "DEVELOPER"],
"/parents/*": ["ADMIN", "TEACHER", "STAFF", "DEVELOPER"],
"/attendance/*": ["ADMIN", "TEACHER", "STAFF", "DEVELOPER"],
"/finance/*": [
"ADMIN",
"TEACHER",
"STUDENT",
"GUARDIAN",
"ACCOUNTANT",
"STAFF",
"DEVELOPER",
],
"/my-grades": ["STUDENT", "GUARDIAN", "DEVELOPER"],
"/my-attendance": ["STUDENT", "GUARDIAN", "DEVELOPER"],
"/my-fees": ["STUDENT", "GUARDIAN", "DEVELOPER"],
}export function isRouteAllowedForRole(pathname: string, role: Role): boolean {
if (role === "DEVELOPER") return true
if (roleRoutes[pathname]) return roleRoutes[pathname].includes(role)
for (const [pattern, allowedRoles] of Object.entries(roleRoutes)) {
if (pattern.endsWith("/*")) {
const base = pattern.slice(0, -2)
if (pathname.startsWith(base + "/")) return allowedRoles.includes(role)
}
}
return true // default: allow routes not in matrix
}Module status
| Module | Status |
|---|---|
| Dashboard | Working — role-specific widgets |
| Students CRUD | Working — full DataTable + forms |
| Teachers CRUD | Working — full DataTable + forms |
| Parents CRUD | Working — guardian linking partial |
| Attendance | Working — daily/weekly views |
| Grades | Partial — needs grade calculation |
| Finance | Partial — basic CRUD, needs Stripe |
| Timetable | Partial — display works, editor needed |
| Announcements | Working — CRUD with targeting |
| Events | Working — calendar integration |
Cross-entry navigation
| From | Action | Auth state | Destination |
|---|---|---|---|
| SaaS marketing | Get Started | Guest | Login → onboarding |
| SaaS marketing | Get Started | Has school | School dashboard |
| SaaS marketing | Get Started | No school | Onboarding wizard |
| SaaS marketing | Live Demo | Any | demo.databayt.org (new tab) |
| SaaS marketing | Login | Guest | Login page |
| SaaS marketing | Login | DEVELOPER | Operator dashboard |
| School site | Platform | Guest | Login → dashboard |
| School site | Platform | Correct user | Role-based dashboard |
| School site | Platform | Wrong school | Access denied |
| School site | Apply | Any | Application form |
| Operator dashboard | View School | DEVELOPER | School dashboard (impersonate) |
UserButton variants
| Variant | Entry | Menu items |
|---|---|---|
marketing | SaaS marketing | Dashboard, Schools (operators), Settings |
saas | Operator dashboard | Profile, Billing, Schools, Settings |
site | School site | Go to Platform, Profile |
platform | School platform | Profile, My Account, School Settings, Help |
Edge proxy
// src/proxy.ts (simplified)
export async function middleware(request: NextRequest) {
const hostname = request.headers.get("host")
const subdomain = extractSubdomain(hostname)
if (subdomain && subdomain !== "ed") {
return NextResponse.rewrite(
new URL(`/${locale}/s/${subdomain}${pathname}`, request.url)
)
}
if (isProtectedRoute(pathname)) {
const session = await auth()
if (!session) return NextResponse.redirect(new URL("/login", request.url))
}
return NextResponse.next()
}Session
session.user = {
id: string
email: string
role: UserRole
schoolId: string | null // null for new users / DEVELOPER
isPlatformAdmin: boolean
}SchoolProvider
// src/components/school/school-provider.tsx
export function SchoolProvider({ subdomain, children }) {
const [school, setSchool] = useState(null)
useEffect(() => {
getSchoolBySubdomain(subdomain).then(setSchool)
}, [subdomain])
return (
<SchoolContext.Provider value={school}>{children}</SchoolContext.Provider>
)
}E2E coverage
// e2e/entry-points.spec.ts
test("SaaS marketing loads without auth", async ({ page }) => {
await page.goto("https://ed.databayt.org/en")
await expect(page.locator("h1")).toContainText("School Automation")
})
test("Operator dashboard requires DEVELOPER", async ({ page }) => {
await page.goto("https://ed.databayt.org/en/dashboard")
await expect(page).toHaveURL(/\/login/)
})
test("School site loads with subdomain", async ({ page }) => {
await page.goto("https://hogwarts.databayt.org/en")
await expect(page.locator("h1")).toContainText("Hogwarts")
})
test("School platform requires auth", async ({ page }) => {
await page.goto("https://hogwarts.databayt.org/en/dashboard")
await expect(page).toHaveURL(/\/login/)
})