diff --git a/AGENTS.md b/AGENTS.md index 4f0f467..e83c178 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -24,9 +24,16 @@ Yarn workspaces monorepo, two independent apps talking only over HTTP: - `apps/api` — Express + Prisma/Postgres, layered `router/ → controller/ → service/ → repository/` per resource (see `task.*.ts` for the pattern). Validation is zod in the controller; no DTO layer. Every response is `{ success, message, data }`, thrown via `common/errors/app-error.ts` typed errors and centralized in `common/middleware/error-handler.middleware.ts`. - `apps/web` — Next.js App Router. Server Components fetch data (`lib/api-client.ts`, which unwraps the `{ success, data }` envelope); anything interactive needs its own `"use client"` component, and mutations should call `router.refresh()` afterward since there's no client cache. + - `src/features//` — business-specific components grouped by feature (`auth`, `profile`, `wallet`); e.g. `features/auth/login-panel.tsx` wires the presentational form to the API and router. + - `src/components/ui/` — base/design-system primitives (button, input, select, ...), documented in `DESIGN.md`. + - `src/components/guards/auth-guard.tsx` (`AuthGuard`) — the single client-side route guard, mounted once in `app/layout.tsx`. Matches the path against a `ROUTE_RULES` table (guest-only / profile-completion-required / role-restricted per prefix) and redirects before rendering — add a route there instead of a new `layout.tsx`. + - `src/components/Navbar.tsx`, `Footer.tsx` — global app shell, not tied to one feature. + - `src/contexts/auth-context.tsx` — `AuthProvider` + `useAuth()`, the single source of truth for the signed-in user's profile (`GET /users/me`). `AuthGuard`, `Navbar`, and profile/wallet features read from it instead of each fetching independently; call `refresh()` after a mutation that can change role/profile-completion/wallet state. ## Conventions - New API resource: add one file each to `router/`, `controller/`, `service/`, `repository/`, then register in `apps/api/src/routes.ts`. - New shared frontend type: one file per resource under `apps/web/src/types/`. +- New frontend feature UI goes under `apps/web/src/features//`, not `components/`; `components/` is reserved for the design system (`ui/`), route guards (`guards/`), and app-shell pieces (`Navbar`, `Footer`). +- New protected/guest page: add a prefix to `ROUTE_RULES` in `apps/web/src/components/guards/auth-guard.tsx`. - Formatting is enforced by Prettier (100 cols, double quotes, semicolons) + ESLint, run automatically on commit via husky/lint-staged — don't bypass with `--no-verify`. diff --git a/README.md b/README.md index 90a0b2d..8918d53 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,13 @@ Monorepo: Next.js frontend + Express API, Prisma/Postgres, Swagger docs, Storybo ## Stack - **apps/web** — Next.js, Tailwind CSS, shadcn/ui, lucide-react, Storybook + - `src/features//` — feature UI grouped by business domain (`auth`, + `profile`, `wallet`) + - `src/components/ui/` — base design-system primitives, see `DESIGN.md` + - `src/components/guards/auth-guard.tsx` — the single route guard mounted in + `app/layout.tsx`; redirects per-route based on sign-in/profile-completion/role + - `src/contexts/auth-context.tsx` — `AuthProvider`/`useAuth()`, the shared + signed-in user profile consumed by the guard, navbar, and feature components - **apps/api** — Express, Prisma, PostgreSQL, Swagger (`swagger-jsdoc` + `swagger-ui-express`) - Layers: `router/` → `controller/` → `service/` → `repository/`. Validation (zod) lives in the controller; no separate DTO layer. diff --git a/apps/web/src/app/balance/top-up/page.tsx b/apps/web/src/app/balance/top-up/page.tsx index 8c3425b..d397380 100644 --- a/apps/web/src/app/balance/top-up/page.tsx +++ b/apps/web/src/app/balance/top-up/page.tsx @@ -1,5 +1,5 @@ import type { Metadata } from "next"; -import { TopUpForm } from "@/components/wallet/top-up-form"; +import { TopUpForm } from "@/features/wallet/top-up-form"; export const metadata: Metadata = { title: "Top up balance | Tutorist" }; diff --git a/apps/web/src/app/complete-profile/page.tsx b/apps/web/src/app/complete-profile/page.tsx index ff1da77..263d71b 100644 --- a/apps/web/src/app/complete-profile/page.tsx +++ b/apps/web/src/app/complete-profile/page.tsx @@ -1,25 +1,17 @@ import type { Metadata } from "next"; -import { ProfileCompletionForm } from "@/components/profile/profile-completion-form"; +import { ProfileCompletionForm } from "@/features/profile/profile-completion-form"; export const metadata: Metadata = { title: "Complete your profile | Tutorist", description: "Finish setting up your Tutorist profile.", }; -type CompleteProfilePageProps = { - searchParams: Promise<{ role?: string }>; -}; - -export default async function CompleteProfilePage({ searchParams }: CompleteProfilePageProps) { - const { role } = await searchParams; - const normalizedRole = role?.trim().toLowerCase(); - const profileRole = normalizedRole === "tutor" ? "tutor" : "student"; - +export default function CompleteProfilePage() { return (
- +
); diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index 6710bd0..bb56d49 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -1,8 +1,9 @@ import type { Metadata } from "next"; import "./globals.css"; -import { AuthBoundary } from "@/components/auth/auth-boundary"; +import { AuthGuard } from "@/components/guards/auth-guard"; import { Footer } from "@/components/Footer"; import { Navbar } from "@/components/Navbar"; +import { AuthProvider } from "@/contexts/auth-context"; export const metadata: Metadata = { title: "SE Project", @@ -13,8 +14,10 @@ export default function RootLayout({ children }: { children: React.ReactNode }) return ( - - {children} + + + {children} +