An internal library management tool for Onja team members to browse, borrow, and return books — replacing the previous spreadsheet and Google Form workflow.
- General users can view and filter books, borrow/return books, favourite books, and leave reviews.
- Admins can do all of the above, plus add/edit/delete books and manage users (roles and removal).
The app is a single page. General users see the books view only. Admins see two tabs: Books and Admin.
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router), TypeScript |
| Database & Auth | Supabase (Postgres, RLS, Google OAuth) |
| Styling | Tailwind CSS v4 — brand tokens in globals.css via @theme |
| Deployment | Vercel |
src/types/index.ts— TypeScript interfaces for every entity. Import from here, never redefine types elsewhere.src/lib/supabase.ts— Typed Supabase client. Use for all DB queries.src/hooks/useAuth.ts— Returns the current user, their role, andsignOut.src/middleware.ts— Redirects unauthenticated users to/login.SCHEMA.md— Full database schema. Read this before every session.
Brand tokens are defined in src/app/globals.css via Tailwind v4's @theme block:
--color-onja-navy: #1B3A6B /* headers, nav, primary buttons */
--color-onja-red: #E8401C /* CTA buttons, active states */
--color-onja-blue: #2D5FA6 /* hover states, links, focus rings */
--color-onja-offwhite:#F5F5F0 /* page background */
--color-onja-black: #1A1A1A /* body text */Use bg-onja-navy, text-onja-red, border-onja-blue, etc. No generic Tailwind blues or grays.
Font: Inter (loaded via next/font). Headings weight 600–700 in Onja Navy. Body weight 400 in Near Black.
npm install
npm run devOpen http://localhost:3000.
Copy .env.example to .env.local and fill in your Supabase URL and anon/service keys.
| Entity | Purpose |
|---|---|
books |
Core book records with genre, library, English level lookups |
profiles |
User profiles linked to Supabase Auth; stores name, avatar, email, role |
libraries |
Lookup table of physical library locations |
genres |
Lookup table to keep genre values consistent |
english_levels |
Lookup table (A1–C2) |
borrows |
Append-only borrow/return history |
reviews |
One review per user per book (rating and/or comment) |
favourites |
Join table between users and books |
See SCHEMA.md for full column definitions, indexes, and trigger logic.
| Wave | Tasks | Notes |
|---|---|---|
| 1 | Task 1 | Types + Supabase client — push first, everything depends on it |
| 2 | Tasks 2, 4, 6 | Auth, books API, BookCard — no cross-dependencies |
| 3 | Tasks 3, 5, 7–11, 13–15 | Feature components and API routes |
| 4 | Task 12 | BookModal (composes BorrowButton, FavouriteButton, Reviews) |
| 5 | Task 16 | Main page — final integration, must be last |
Each developer works on feature/task-XX. Import types from src/types/index.ts.
- Branch from
mainusing the naming conventionfeature/task-XX(e.g.feature/task-07). - Open a pull request against
mainwhen your task is ready for review. - Keep PRs scoped to a single task — don't bundle unrelated changes.
- Read
SCHEMA.md— every query and type must be grounded in the real database schema. - Check
src/types/index.ts— import types from here, never redefine them inline. - Read the relevant guide in
node_modules/next/dist/docs/— this project uses Next.js 16, which has breaking changes from earlier versions.
- Supabase client: use
src/lib/supabase/client.tsin client components andsrc/lib/supabase/server.tsin Server Components and Route Handlers. - Auth: use
src/hooks/useAuth.tsto get the current user and role — don't queryprofilesdirectly in components. - Styling: Onja brand tokens only (
bg-onja-navy,text-onja-red, etc.). No generic Tailwind blues or grays. See the Branding section above. - Write operations (add/edit/delete books, user role changes) must be admin-only and use the service role key.
- Borrow records are append-only — set
returned_aton return, never delete rows. favourites_countis maintained by a database trigger — do not update it from the app layer.
Genres, English levels, and libraries come from lookup tables. Always use the IDs from these tables — never store free text for these fields.
Run npm run dev and verify your feature end-to-end in the browser before opening a PR. TypeScript errors (npm run build) should be zero.