feat: localStorage-backed student CRUD with zod schemas

Split the app into the three pages the navbar promises, each with one job:

- / (home)   -> read-only roster, no mutation controls
- /add       -> create only, redirects to / on success
- /edit      -> update + delete only (picker, plus /edit?id=<uuid> deep links)

Data layer (src/lib/students.ts) is the single source of truth:
- zod studentDraftSchema validates form input (trims name, lowercases email,
  coerces yearLevel to an int 1-5, restricts course to a known list)
- studentSchema = draft + id + createdAt, so types come from z.infer
- versioned key it3206.students.v1, validated on read *and* write; corrupt or
  missing data degrades to an empty list instead of throwing
- typed CRUD helpers; useSyncExternalStore hook with a stable snapshot cache,
  a window event and the storage event so every island and other tabs re-render

Footer "clear local storage" is now wired to clearStudents() (confirm dialog,
label feedback) instead of being inert, and the layout switched to min-h-svh +
an in-flow footer so it no longer overlaps long lists. Footer padding clears the
Astro dev toolbar, which was covering the link in dev.

Also adds zod, plus @astrojs/check + typescript@^6 as dev deps so astro check
runs (TS 7 does not expose the API astro check needs yet).
This commit is contained in:
2026-09-16 19:00:18 +08:00
parent 478cb360d2
commit a21c76f029
15 changed files with 950 additions and 19 deletions
+7 -2
View File
@@ -7,7 +7,7 @@
},
"scripts": {
"dev": "astro dev",
"build": "astro build",
"build": "astro check && astro build",
"preview": "astro preview",
"astro": "astro"
},
@@ -20,9 +20,14 @@
"astro": "^7.3.2",
"react": "^19.3.0",
"react-dom": "^19.3.0",
"tailwindcss": "^4.3.3"
"tailwindcss": "^4.3.3",
"zod": "^4.6.5"
},
"allowScripts": {
"esbuild": true
},
"devDependencies": {
"@astrojs/check": "^0.9.10",
"typescript": "^6"
}
}