Frontend Architecture
Frontend Architecture
Section titled “Frontend Architecture”KoAkademy delivers an interactive, single-page-application feel using Inertia 3 paired with React 19 and server-side rendering (SSR enabled in resources/js/ssr.tsx). Server-side Laravel controllers manage state, routing, and authorization, while React renders responsive components on the client without modern SPA routing complexity.
Directory Structure
Section titled “Directory Structure”All frontend pages reside in resources/js/pages/ and mirror the application audiences:
resources/js/├── actions/ # Generated Wayfinder controller helpers├── routes/ # Generated Wayfinder named route helpers├── components/│ ├── ui/ # Reusable shadcn/ui primitives (Radix UI)│ ├── administrators/ # Admin-specific UI blocks & dialogs│ ├── faculty/ # Faculty tools, gradebooks, attendance│ └── student/ # Student schedule, statement of account├── lib/ # Utilities (cn, formatting, api clients)├── pages/│ ├── administrators/ # Staff workspace (students, classes, finance, policies)│ ├── faculty/ # Faculty action center, grade submission, agendas│ ├── student/ # Student dashboard, digital ID, statements│ ├── enrollment/ # Public student registration wizard│ ├── setup/ # First-run institution initialization wizard│ ├── auth/ # Login, 2FA challenge, passkey flows│ └── docs/ # In-app documentation reader├── app.tsx # Inertia client entry point└── ssr.tsx # Inertia server-side renderer entry pointRouting: Laravel Wayfinder
Section titled “Routing: Laravel Wayfinder”Instead of hard-coding URLs or writing brittle route strings, frontend code imports type-safe route helpers generated by Laravel Wayfinder:
import { store, update } from "@/actions/App/Http/Controllers/AdministratorEnrollmentPolicyController";import { useForm } from "@inertiajs/react";
export function PolicyEditor({ policyId }: { policyId: number }) { const form = useForm({ name: "College Enrollment Blueprint", inherit: true, });
const submitCreate = () => { // Wayfinder provides typed action URLs form.post(store.url()); };
const submitUpdate = () => { form.put(update.url({ enrollmentPolicy: policyId })); };
return ( <form onSubmit={submitUpdate}> <button type="submit" disabled={form.processing}> Save Changes </button> </form> );}Component Architecture & Design System
Section titled “Component Architecture & Design System”KoAkademy uses shadcn/ui built on top of Radix UI primitives and styled with Tailwind CSS 4 (@tailwindcss/vite):
- Primitives: Radix UI components (Dialog, DropdownMenu, Popover, Tooltip, Select, Tabs, Accordion) guarantee full keyboard navigation, screen reader accessibility, and correct ARIA states.
- Icons: Standardized on
lucide-reactacross all portals. - Animations: Subtle interactions using
framer-motionand Tailwind CSS transitions. Always respectprefers-reduced-motion. - Forms & Validation: Inertia’s
useFormhook handles form state, submission lifecycle, and server-side validation error mapping.
Practical Component Sample: Modal Form with Validation
Section titled “Practical Component Sample: Modal Form with Validation”Here is the standard pattern for creating interactive modal dialogs across portals:
import { store } from "@/actions/App/Http/Controllers/AdministratorEnrollmentPolicyController";import { Button } from "@/components/ui/button";import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger,} from "@/components/ui/dialog";import { Input } from "@/components/ui/input";import { Label } from "@/components/ui/label";import { useForm } from "@inertiajs/react";import { Plus } from "lucide-react";import { useState } from "react";
export function CreateBlueprintDialog() { const [open, setOpen] = useState(false); const form = useForm({ name: "", school_year: "", });
const handleSubmit = (e: React.FormEvent) => { e.preventDefault();
form.post(store.url(), { preserveScroll: true, onSuccess: () => { form.reset(); setOpen(false); }, }); };
return ( <Dialog open={open} onOpenChange={setOpen}> <DialogTrigger asChild> <Button> <Plus className="mr-2 size-4" /> New Blueprint </Button> </DialogTrigger> <DialogContent className="sm:max-w-md"> <form onSubmit={handleSubmit}> <DialogHeader> <DialogTitle>Create Enrollment Blueprint</DialogTitle> <DialogDescription> Define a new policy scope for student admissions. </DialogDescription> </DialogHeader>
<div className="space-y-4 py-4"> <div className="space-y-2"> <Label htmlFor="name">Blueprint Name</Label> <Input id="name" value={form.data.name} onChange={(e) => form.setData("name", e.target.value)} placeholder="Senior High School 2026" /> {form.errors.name && ( <p className="text-destructive text-sm" role="alert"> {form.errors.name} </p> )} </div> </div>
<DialogFooter> <Button type="button" variant="outline" onClick={() => setOpen(false)} > Cancel </Button> <Button type="submit" disabled={form.processing}> {form.processing ? "Creating..." : "Create"} </Button> </DialogFooter> </form> </DialogContent> </Dialog> );}Key Libraries Overview
Section titled “Key Libraries Overview”| Library | Role |
|---|---|
@tanstack/react-table | Data tables with pagination, sorting, and multi-filter capabilities in administrative portals |
@tiptap/react | Rich text editors for class syllabus, announcements, and notes |
recharts / @visx/* | Institutional analytics, enrollment pipeline visualization, and finance graphs |
signature_pad | Digital student and registrar signature capture on admission forms |
sonner | Accessible toast notifications for background job dispatches and form confirmations |
laravel-echo / pusher-js | Real-time WebSocket event listeners for live notifications and status updates |
Frontend Review Conventions
Section titled “Frontend Review Conventions”When contributing frontend code:
- Strict TypeScript: Provide explicit prop interfaces; avoid
any. - Server-Side Validation: Rely on Laravel Form Requests as the source of truth for validation errors. Display error messages directly next to input fields using
form.errors. - Accessibility: Preserve accessible markup:
- Always associate
<Label htmlFor="...">with<Input id="...">. - Ensure all icon buttons have an
aria-label. - Never remove focus visible indicators (
focus-visible:ring-*).
- Always associate
- Formatting & Linting: Run
npm run lintandnpm run formatbefore submitting PRs.