|
| 1 | +# Copilot Instructions — Testownik Solvro Frontend |
| 2 | + |
| 3 | +## Project Overview |
| 4 | + |
| 5 | +A quiz/study platform for Wrocław University of Technology students, built by KN Solvro. Next.js 16 App Router + React |
| 6 | +19 + TanStack Query v5 + Tailwind CSS v4 + shadcn/ui (Radix). Backend is a separate Django REST API ( |
| 7 | +`Solvro/backend-testownik`). UI strings are in **Polish**; code identifiers are in **English**. |
| 8 | + |
| 9 | +## Architecture |
| 10 | + |
| 11 | +### Page Pattern (SSR ↔ Client split) |
| 12 | + |
| 13 | +Every route follows a two-file convention in `src/app/<route>/`: |
| 14 | + |
| 15 | +- **`page.tsx`** — Server Component. Reads cookies via `next/headers`, creates a `QueryClient`, prefetches data through |
| 16 | + service classes, wraps content in `<HydrationBoundary>`. Exports `metadata` (Polish titles). |
| 17 | +- **`client.tsx`** — Client Component (`"use client"`). Named export `XxxPageClient`. Consumes hydrated queries and |
| 18 | + accesses `AppContext` for auth/services. |
| 19 | + |
| 20 | +Reference: `src/app/quizzes/page.tsx` → `src/app/quizzes/client.tsx`. |
| 21 | + |
| 22 | +Some simpler pages (e.g. `grades`, `create-quiz`) skip SSR prefetching and render the client component directly. |
| 23 | + |
| 24 | +### Service Layer (`src/services/`) |
| 25 | + |
| 26 | +Class-based API services extending `BaseApiService`. Accessed via singleton `ServiceRegistry` exposed through React |
| 27 | +Context (`AppContext`). Three services: `QuizService`, `UserService`, `ImageService`. |
| 28 | + |
| 29 | +Access pattern in client components: |
| 30 | + |
| 31 | +```ts |
| 32 | +const { services, isGuest } = useContext(AppContext); |
| 33 | +const quizzes = await services.quiz.getQuizzes(); |
| 34 | +``` |
| 35 | + |
| 36 | +### Auth |
| 37 | + |
| 38 | +JWT (HS256) stored in cookies (`access_token`, `refresh_token`). Guest mode uses `is_guest` cookie. Client-side decoding |
| 39 | +via `jose.decodeJwt()` (no verification). Server-side verification via `jose.jwtVerify()` with `JWT_SECRET` env var. |
| 40 | +Auth context is provided by `AppContextProvider` in `src/app-context-provider.tsx`. |
| 41 | + |
| 42 | +### State Management & Data Fetching |
| 43 | + |
| 44 | +**TanStack Query v5** exclusively (no Redux/Zustand). Single `QueryClientProvider` at app root ( |
| 45 | +`src/app/providers.tsx`). |
| 46 | + |
| 47 | +**SSR-first pattern:** Prefer server-side data prefetch + hydration over client-only fetches. A typical page: |
| 48 | + |
| 49 | +1. Server (`page.tsx`) calls `queryClient.prefetchQuery()` with the service |
| 50 | +2. Dehydrates state: `<HydrationBoundary state={dehydrate(queryClient)}>` |
| 51 | +3. Client reads hydrated cache immediately (no duplicate fetch) |
| 52 | + |
| 53 | +Example: `src/app/quizzes/page.tsx` prefetches `["user-quizzes", isGuest]` — client hook in `useQuizzes()` uses the same |
| 54 | +key. |
| 55 | + |
| 56 | +**Query keys** include `isGuest` flag for cache segmentation. Default `staleTime` is 60s (`src/lib/query-client.ts`). |
| 57 | +Keep keys consistent between SSR prefetch and client hooks. |
| 58 | + |
| 59 | +### Middleware & Auth Guards |
| 60 | + |
| 61 | +`src/proxy.ts` (Next.js middleware) enforces auth on protected routes (`/profile`, `/quizzes`, `/grades`, |
| 62 | +`/create-quiz`, `/edit-quiz`, `/import-quiz`). Handles: |
| 63 | + |
| 64 | +- Token validation and refresh (expires with 30s buffer) |
| 65 | +- Automatic token refresh using `POST /token/refresh/` |
| 66 | +- Fallback to login redirect if no valid token |
| 67 | +- Cookie forwarding from backend responses |
| 68 | + |
| 69 | +### Env Variables |
| 70 | + |
| 71 | +Validated with `@t3-oss/env-nextjs` + Zod in `src/env.ts`. Required: `NEXT_PUBLIC_API_URL`. Optional: |
| 72 | +`NEXT_PUBLIC_TURN_USERNAME`, `NEXT_PUBLIC_TURN_CREDENTIAL` (TURN relay for P2P), `JWT_SECRET` (server), |
| 73 | +`INTERNAL_API_KEY` (server). |
| 74 | + |
| 75 | +## Key Conventions |
| 76 | + |
| 77 | +- **Path alias:** `@/` maps to `src/` (tsconfig paths) |
| 78 | +- **UI components:** shadcn/ui in `src/components/ui/`, project components alongside in `src/components/` |
| 79 | +- **Hooks:** `src/hooks/use-{feature}.ts` (kebab-case). Feature-specific hooks may be colocated (e.g. |
| 80 | + `src/components/quiz/hooks/`) |
| 81 | +- **Validation:** Zod v4 schemas in `src/lib/schemas/` |
| 82 | + |
| 83 | +### Commit Format |
| 84 | + |
| 85 | +Use **Conventional Commits** via `@solvro/config` commitlint enforcement. |
| 86 | + |
| 87 | +**Format:** `<type>(optional scope): present-tense description in English` |
| 88 | + |
| 89 | +**Types:** `feat`, `fix`, `refactor`, `chore`, `docs`, `ci`, `test`, `build`, `release` |
| 90 | + |
| 91 | +**Examples:** |
| 92 | + |
| 93 | +- `feat(quiz): add cross-device quiz continuity` |
| 94 | +- `fix(auth): correct token refresh timeout` |
| 95 | +- `refactor(editor): simplify question form` |
| 96 | +- `test(hooks): add useQuizzes integration tests` |
| 97 | + |
| 98 | +### Branch Naming |
| 99 | + |
| 100 | +**Format:** `<prefix>/<issue>-short-description` |
| 101 | + |
| 102 | +**Prefixes:** `feat/`, `fix/`, `hotfix/`, `design/`, `refactor/`, `test/`, `docs/` |
| 103 | + |
| 104 | +**Examples:** |
| 105 | + |
| 106 | +- `feat/123-add-solvro-auth` |
| 107 | +- `fix/87-fix-date-display` |
| 108 | +- `refactor/210-quiz-import-logic` |
| 109 | + |
| 110 | +### Code Style |
| 111 | + |
| 112 | +- **Formatting:** Prettier via `@solvro/config/prettier` — auto-run on commit via husky + lint-staged |
| 113 | +- **Linting:** ESLint flat config via `@solvro/config/eslint` (see `eslint.config.js`) |
| 114 | + |
| 115 | +## Commands |
| 116 | + |
| 117 | +| Task | Command | |
| 118 | +| ---------- | ----------------------------------------- | |
| 119 | +| Dev server | `npm run dev` | |
| 120 | +| Build | `npm run build` | |
| 121 | +| Lint | `npm run lint` / `npm run lint:fix` | |
| 122 | +| Format | `npm run format` / `npm run format:check` | |
| 123 | +| Type check | `npm run typecheck` | |
| 124 | +| Tests | `npm run test` (Vitest) | |
| 125 | + |
| 126 | +## Cross-Device Quiz Continuity (PeerJS) |
| 127 | + |
| 128 | +Users can sync quiz progress across devices (desktop, tablet, mobile) via WebRTC peer-to-peer. |
| 129 | + |
| 130 | +**Hook:** `src/components/quiz/hooks/use-quiz-continuity.ts` — manages `Peer` connections, device discovery, and message |
| 131 | +types (`initial_sync`, `question_update`, `answer_checked`, ping/pong for connection health). |
| 132 | + |
| 133 | +**UI:** `src/components/quiz/continuity-dialog.tsx` — displays connected peers by device type (icon per device), shows " |
| 134 | +host" badge for primary device. |
| 135 | + |
| 136 | +**RTC config** uses STUN/TURN servers from env vars `NEXT_PUBLIC_TURN_USERNAME` and `NEXT_PUBLIC_TURN_CREDENTIAL` for |
| 137 | +NAT traversal. Fallback to public Google STUN if TURN unavailable. |
| 138 | + |
| 139 | +## Testing |
| 140 | + |
| 141 | +- **Framework:** Vitest 4 + jsdom + React Testing Library + MSW 2 |
| 142 | +- **Test files:** `.spec.ts`/`.spec.tsx` — unit tests colocated with source in `src/lib/`, page tests in |
| 143 | + `src/__tests__/pages/` |
| 144 | +- **MSW handlers:** `src/test-utils/mocks/handlers.ts` — mocks backend API endpoints |
| 145 | +- **Test providers:** Wrap components in `<Providers>` from `src/test-utils/providers.tsx` (accepts `guest` flag, |
| 146 | + `accessToken`) |
| 147 | +- **Token generation:** Use `generateTestToken()` from `src/test-utils/token-factory.ts` for auth in tests |
| 148 | +- **Setup:** `src/test-utils/setup.ts` polyfills JSDOM gaps and configures MSW lifecycle (`beforeAll`/`afterEach`/ |
| 149 | + `afterAll`) |
| 150 | +- **Pattern:** Page tests use a `setup()` helper that renders in providers and returns action helpers |
| 151 | + |
| 152 | +## File Structure Quick Reference |
| 153 | + |
| 154 | +``` |
| 155 | +src/ |
| 156 | + app/ # Next.js App Router pages (page.tsx + client.tsx) |
| 157 | + services/ # API service classes (BaseApiService → Quiz/User/Image) |
| 158 | + components/ |
| 159 | + ui/ # shadcn/ui primitives (do not edit manually — use shadcn CLI) |
| 160 | + quiz/ # Quiz feature components, hooks, helpers |
| 161 | + navbar/ # Navigation components |
| 162 | + hooks/ # Global custom hooks |
| 163 | + lib/ |
| 164 | + auth/ # JWT utilities, constants, types |
| 165 | + schemas/ # Zod validation schemas |
| 166 | + cookies.ts # Cookie helpers (js-cookie wrapper) |
| 167 | + api.ts # API_URL export |
| 168 | + types/ # TypeScript interfaces (quiz.ts, user.ts, common.ts) |
| 169 | + test-utils/ # Test providers, MSW mocks, token factory |
| 170 | +``` |
0 commit comments