- ENCOACH_UNIFIED_SRS.md v2.0: updated header, added 8 new Part VIII-B sections (student leave, fees, lessons, gradebook, student progress, library, activities, facilities), extended permissions with roles CRUD and authority matrix, updated tech specs (93 pages, ~377 API routes, 41 modules), added implementation traceability to all sections - ENCOACH_ODOO19_BACKEND_SRS.md v3.0: updated status to implemented, added beyond-SRS features section, updated module count to 41, endpoint count to ~377 - ODOO_DEVELOPER_HANDOFF.md: rewritten with current repo references and implementation status - ENCOACH_SYSTEM_FEATURES_GUIDE.md: added system features guide - Superseded notices added to ODOO_BACKEND_SRS_v3.md, ODOO_MIGRATION_SRS_v2.md, ODOO_MIGRATION_SRS.md, ODOO_MIGRATION_FRONTEND_SRS.md, MATH_IT_ADAPTIVE_LEARNING_SRS.md Made-with: Cursor
37 KiB
EnCoach Frontend API Migration SRS
SUPERSEDED -- This document has been replaced by
ENCOACH_UNIFIED_SRS.md(v2.0). The migration is complete. The oldielts-uiNext.js frontend has been replaced byencoach_frontend_new_v2(React 18 + Vite + TypeScript), deployed athttp://5.189.151.117:3000.
ielts-ui Migration Guide: Next.js API Routes to Odoo 19
Version: 1.0
Date: March 11, 2026
Status: Active SUPERSEDED
Audience: Frontend developer(s) maintaining ielts-ui
Companion document: ODOO_MIGRATION_SRS.md (backend Odoo specification)
Table of Contents
- Migration Overview
- Authentication Migration
- API Base URL Change
- Endpoint-by-Endpoint Migration Map
- SSR Migration Strategy
- Files to Delete
- Migration Checklist
1. Migration Overview
1.1 What Is Changing
The ielts-ui Next.js application currently serves two roles:
- Browser UI -- React pages, components, Zustand stores, hooks
- BFF (Backend-for-Frontend) -- 108 API route files in
src/pages/api/that query MongoDB directly, manage iron-session cookies, and proxy AI requests
After migration, ielts-ui serves only the Browser UI. All backend logic moves to Odoo 19. The src/pages/api/ directory and all server-side database utilities are eliminated entirely.
1.2 Current Architecture (what the frontend sees)
Browser (React)
│
├── axios/SWR/fetch calls to /api/* (same origin)
│
└── Next.js API Routes (src/pages/api/)
├── iron-session cookie auth
├── MongoDB queries (via src/utils/*.be.ts)
└── Proxy to AI backend (via BACKEND_URL)
1.3 Target Architecture (what the frontend sees)
Browser (React)
│
├── axios/SWR/fetch calls to ODOO_URL/api/* (cross-origin)
│ └── Authorization: Bearer <JWT>
│
└── Odoo 19 (separate service)
├── JWT auth
├── PostgreSQL (replaces MongoDB)
└── All AI/ML handled internally by Odoo
1.4 What Does NOT Change
- All React page components, layouts, and UI rendering
- Zustand stores (
src/stores/exam/,src/stores/preferencesStore.ts, etc.) - All components in
src/components/(they still call the same API shapes -- only the base URL and auth mechanism change) - Styling (Tailwind, DaisyUI)
- TypeScript interfaces in
src/interfaces/ - Client-side routing
- Strapi CMS and the landing page
1.5 Guiding Principle
Odoo's REST API is designed to return the same JSON shapes the frontend currently expects. If the frontend calls GET /api/users/list and expects { users: [...], total: N }, Odoo's GET /api/users/list returns the same shape. The migration is primarily a transport change (base URL + auth header), not a data format change.
2. Authentication Migration
This is the most significant structural change.
2.1 Current Auth Pattern
| Aspect | Current Implementation |
|---|---|
| Identity provider | Firebase Auth (signInWithEmailAndPassword) |
| Session | iron-session (encrypted httpOnly cookie named eCrop/ielts) |
| Client auth | Automatic -- browser sends cookie on same-origin requests |
| SSR auth | withIronSessionSsr + getServerSideProps reads req.session.user |
| User hook | useUser() calls GET /api/user via SWR; cookie sent automatically |
| Config | src/lib/session.ts (SECRET_COOKIE_PASSWORD, cookieName) |
2.2 Target Auth Pattern
| Aspect | Target Implementation |
|---|---|
| Identity provider | Odoo 19 native auth (res.users) |
| Session | JWT token issued by Odoo |
| Client auth | Authorization: Bearer <token> header on every request |
| SSR auth | JWT stored in httpOnly cookie; getServerSideProps reads cookie and calls Odoo |
| User hook | useUser() calls GET ODOO_URL/api/user with JWT header |
| Config | NEXT_PUBLIC_API_URL env var |
2.3 New Auth Infrastructure
Create a new file src/lib/api.ts:
import axios from "axios";
const api = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL,
withCredentials: true,
});
api.interceptors.request.use((config) => {
const token = getToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
api.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
clearToken();
if (typeof window !== "undefined") {
window.location.href = "/login";
}
}
return Promise.reject(error);
}
);
export default api;
JWT storage helpers (also in src/lib/api.ts or a separate src/lib/auth.ts):
import Cookies from "js-cookie";
const TOKEN_KEY = "encoach_token";
export function setToken(token: string) {
Cookies.set(TOKEN_KEY, token, { secure: true, sameSite: "lax" });
}
export function getToken(): string | undefined {
return Cookies.get(TOKEN_KEY);
}
export function clearToken() {
Cookies.remove(TOKEN_KEY);
}
Note: Using a cookie for JWT storage (rather than localStorage) is recommended because getServerSideProps can read cookies from the request headers, enabling SSR auth. The cookie is NOT httpOnly in this approach (so JS can read it for the axios interceptor). If httpOnly is required, use a thin proxy or Next.js middleware to inject the header.
2.4 Files That Change for Auth
| File | Current | Target |
|---|---|---|
src/pages/login.tsx |
axios.post("/api/login", { email, password }) |
api.post("/api/login", { email, password }) -- store JWT from response |
src/pages/register.tsx |
fetch to /api/register (SSR) + code lookup |
api.post("/api/register", payload) -- store JWT from response |
src/pages/official-exam.tsx |
axios.post("/api/logout") |
api.post("/api/logout") + clearToken() |
src/components/Sidebar.tsx |
axios.post("/api/logout") |
api.post("/api/logout") + clearToken() |
src/components/MobileMenu.tsx |
axios.post("/api/logout") |
api.post("/api/logout") + clearToken() |
src/utils/email.ts |
axios.post("/api/reset/sendVerification") |
api.post("/api/reset/sendVerification") |
src/hooks/useUser.tsx |
SWR fetcher uses axios.get("/api/user") |
SWR fetcher uses api.get("/api/user") |
src/lib/session.ts |
iron-session config | DELETE -- replaced by src/lib/api.ts |
2.5 Login Flow Change
Current:
- User submits email + password
POST /api/login(same origin)- API route calls Firebase Auth, looks up user in MongoDB, saves to iron-session
- Response: user JSON +
Set-Cookieheader mutateUser(response.data)updates SWR cache
Target:
- User submits email + password
POST ODOO_URL/api/login(cross-origin)- Odoo validates credentials, returns
{ user: {...}, token: "jwt..." } - Frontend stores JWT via
setToken(response.data.token) mutateUser(response.data.user)updates SWR cache
Login page change (src/pages/login.tsx):
// BEFORE
const response = await axios.post("/api/login", { email, password });
mutateUser(response.data);
// AFTER
import api, { setToken } from "@/lib/api";
const response = await api.post("/api/login", { email, password });
setToken(response.data.token);
mutateUser(response.data.user);
2.6 Logout Flow Change
Current: POST /api/logout destroys iron-session.
Target: POST ODOO_URL/api/logout (optional server-side invalidation) + clearToken() client-side.
// BEFORE
await axios.post("/api/logout");
mutateUser(null);
router.push("/login");
// AFTER
import api, { clearToken } from "@/lib/api";
await api.post("/api/logout").catch(() => {});
clearToken();
mutateUser(null);
router.push("/login");
3. API Base URL Change
3.1 Environment Variable
Add to .env.local:
NEXT_PUBLIC_API_URL=https://api.encoach.com
Development:
NEXT_PUBLIC_API_URL=http://localhost:8069
3.2 Shared Axios Instance
Replace all direct axios imports with the shared api instance from src/lib/api.ts (defined in Section 2.3).
Search and replace pattern across the codebase:
// BEFORE (scattered across ~40 files)
import axios from "axios";
axios.get("/api/...");
axios.post("/api/...");
axios.patch("/api/...");
axios.delete("/api/...");
// AFTER
import api from "@/lib/api";
api.get("/api/...");
api.post("/api/...");
api.patch("/api/...");
api.delete("/api/...");
The /api/... path stays the same -- the baseURL on the axios instance prepends the Odoo host.
3.3 SWR Fetcher Update
Update src/hooks/useUser.tsx:
// BEFORE
const fetcher = (url: string) => axios.get(url).then((res) => res.data);
// AFTER
import api from "@/lib/api";
const fetcher = (url: string) => api.get(url).then((res) => res.data);
3.4 fetch() Calls
A few files use fetch() instead of axios (primarily src/components/ExamEditor/ImportExam/WordUploader.tsx). These also need the base URL and auth header:
// BEFORE
const res = await fetch(`/api/exam/${module}/import/`, { method: "POST", body: formData });
// AFTER
import { getToken } from "@/lib/api";
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/exam/${module}/import/`, {
method: "POST",
body: formData,
headers: { Authorization: `Bearer ${getToken()}` },
});
Note: fetch() calls to blob URLs (e.g., fetch(blobUrl) for audio/video previews) do NOT change. Only calls to /api/* endpoints change.
4. Endpoint-by-Endpoint Migration Map
For each API call the frontend makes, this section specifies the current URL, new Odoo URL, HTTP method, and every file that makes the call.
Convention: Where the Odoo URL is the same path as the current URL (just different host), only the base URL changes. These are marked "Path unchanged." Where the Odoo URL differs, the new path is shown.
4.1 Auth
| # | Method | Current URL | Odoo URL | Files |
|---|---|---|---|---|
| 1 | POST | /api/login |
Path unchanged | src/pages/login.tsx |
| 2 | POST | /api/logout |
Path unchanged | src/pages/official-exam.tsx, src/components/Sidebar.tsx, src/components/MobileMenu.tsx |
| 3 | POST | /api/reset |
Path unchanged | src/pages/login.tsx |
| 4 | POST | /api/reset/sendVerification |
Path unchanged | src/utils/email.ts |
Response change for login: Odoo returns { user: {...}, token: "jwt..." } instead of just the user object. Frontend must extract and store the token.
4.2 User
| # | Method | Current URL | Odoo URL | Files |
|---|---|---|---|---|
| 5 | GET | /api/user |
Path unchanged | src/hooks/useUser.tsx (SWR) |
| 6 | DELETE | /api/user |
Path unchanged | (called from useUser context) |
| 7 | POST | /api/users/update |
PATCH /api/users/update |
src/pages/profile.tsx, src/components/UserCard.tsx |
| 8 | GET | /api/users/list |
Path unchanged | src/utils/groups.ts |
| 9 | GET | /api/users/{id} |
Path unchanged | src/utils/groups.ts |
| 10 | POST | /api/users/controller |
Path unchanged | src/components/Imports/StudentClassroomTransfer.tsx |
| 11 | GET | /api/users/balance |
Path unchanged | src/hooks/useUserBalance.tsx (if used) |
Note on #7: The current frontend uses POST for user updates. Odoo convention is PATCH. Either Odoo can accept both, or the frontend changes the method.
4.3 Registration / Codes
| # | Method | Current URL | Odoo URL | Files |
|---|---|---|---|---|
| 12 | POST | /api/register |
Path unchanged | src/pages/register.tsx (SSR currently; move to client-side) |
| 13 | GET | /api/code/{code} |
Path unchanged | src/pages/register.tsx |
Note on #12: Registration is currently handled in getServerSideProps. With Odoo, move registration to a client-side form submission. The register page should call api.post("/api/register", payload) and store the returned JWT.
4.4 Exams
| # | Method | Current URL | Odoo URL | Files |
|---|---|---|---|---|
| 14 | GET | /api/exam |
Path unchanged | src/hooks/useExams.tsx |
| 15 | GET | /api/exam/{module}?{params} |
Path unchanged | src/utils/exams.ts (getExam) |
| 16 | GET | /api/exam/{module}/{id} |
Path unchanged | src/utils/exams.ts (getExamById) |
| 17 | POST | /api/exam/{module} |
Path unchanged | src/components/ExamEditor/SettingsEditor/reading/index.tsx, writing/index.tsx, listening/index.tsx, speaking/index.tsx, level.tsx |
| 18 | PATCH | /api/exam/{module}/{id} |
Path unchanged | src/pages/(admin)/Lists/ExamList.tsx, src/pages/approval-workflows/[id]/index.tsx |
| 19 | DELETE | /api/exam/{module}/{id} |
Path unchanged | src/pages/(admin)/Lists/ExamList.tsx |
| 20 | GET | /api/exam/avatars |
Path unchanged | src/pages/generation.tsx |
| 21 | GET/POST | /api/exam/generate/{module}/{sectionId} |
Path unchanged | src/components/ExamEditor/SettingsEditor/Shared/Generate.ts |
| 22 | POST | /api/exam/media/speaking |
Path unchanged | src/components/ExamEditor/SettingsEditor/Shared/generateVideos.ts |
| 23 | POST | /api/exam/media/listening |
Path unchanged | src/components/ExamEditor/SettingsEditor/listening/components.tsx |
| 24 | POST | /api/exam/media/instructions |
Path unchanged | src/components/ExamEditor/Standalone/ListeningInstructions/index.tsx |
| 25 | POST | /api/exam/{module}/import/ |
Path unchanged | src/components/ExamEditor/ImportExam/WordUploader.tsx (uses fetch) |
4.5 Evaluation / Grading
| # | Method | Current URL | Odoo URL | Files |
|---|---|---|---|---|
| 26 | POST | /api/evaluate/writing |
Path unchanged | src/utils/evaluation.ts |
| 27 | POST | /api/evaluate/speaking |
Path unchanged | src/utils/evaluation.ts |
| 28 | POST | /api/evaluate/interactiveSpeaking |
Path unchanged | src/utils/evaluation.ts |
| 29 | GET | /api/evaluate/status?... |
Path unchanged | src/utils/evaluation.ts (polling) |
| 30 | GET | /api/evaluate/fetchSolutions?... |
Path unchanged | src/utils/evaluation.ts |
| 31 | POST | /api/grading |
Path unchanged | src/pages/api/grading/index.ts (if called by frontend) |
| 32 | POST | /api/grading/multiple |
Path unchanged | (if called by frontend) |
4.6 Storage
| # | Method | Current URL | Odoo URL | Files |
|---|---|---|---|---|
| 33 | POST | /api/storage |
Path unchanged | src/components/ExamEditor/SettingsEditor/writing/index.tsx, speaking/index.tsx, listening/index.tsx, level.tsx |
| 34 | POST | /api/storage/delete |
DELETE /api/storage/{id} (or keep POST) |
src/utils/evaluation.ts |
Note: Odoo will use ir.attachment for file storage. The response must return a publicly accessible URL for the uploaded file, matching the current behavior.
4.7 Speaking Audio
| # | Method | Current URL | Odoo URL | Files |
|---|---|---|---|---|
| 35 | POST | /api/speaking |
Path unchanged | src/components/Solutions/Speaking.tsx, src/components/Solutions/InteractiveSpeaking.tsx |
4.8 Sessions
| # | Method | Current URL | Odoo URL | Files |
|---|---|---|---|---|
| 36 | POST | /api/sessions |
Path unchanged | src/stores/exam/index.ts (Zustand saveSession) |
| 37 | DELETE | /api/sessions/{id} |
Path unchanged | src/components/Medium/SessionCard.tsx |
4.9 Stats
| # | Method | Current URL | Odoo URL | Files |
|---|---|---|---|---|
| 38 | POST | /api/stats |
Path unchanged | src/stores/exam/index.ts (Zustand saveStats) |
| 39 | GET | /api/stats/{id} |
Path unchanged | src/pages/training/[id]/index.tsx |
| 40 | POST | /api/statistical |
Path unchanged | src/pages/statistical.tsx |
4.10 Training
| # | Method | Current URL | Odoo URL | Files |
|---|---|---|---|---|
| 41 | POST | /api/training |
Path unchanged | src/pages/training/index.tsx |
| 42 | GET | /api/training/{id} |
Path unchanged | src/pages/training/[id]/index.tsx |
| 43 | GET | /api/training/walkthrough |
Path unchanged | src/pages/training/[id]/index.tsx |
4.11 Groups
| # | Method | Current URL | Odoo URL | Files |
|---|---|---|---|---|
| 44 | GET | /api/groups?participant={userId} |
Path unchanged | src/utils/groups.ts |
| 45 | POST | /api/groups |
Path unchanged | src/pages/classrooms/create.tsx |
| 46 | PATCH | /api/groups/{id} |
Path unchanged | src/pages/classrooms/[id].tsx |
| 47 | DELETE | /api/groups/{id} |
Path unchanged | src/pages/classrooms/[id].tsx |
4.12 Entities
| # | Method | Current URL | Odoo URL | Files |
|---|---|---|---|---|
| 48 | POST | /api/entities |
Path unchanged | src/pages/entities/create.tsx |
| 49 | PATCH | /api/entities/{id} |
Path unchanged | src/pages/entities/[id]/index.tsx |
| 50 | DELETE | /api/entities/{id} |
Path unchanged | src/pages/entities/[id]/index.tsx |
| 51 | PATCH | /api/entities/{id}/users |
Path unchanged | src/pages/entities/[id]/index.tsx |
4.13 Roles
| # | Method | Current URL | Odoo URL | Files |
|---|---|---|---|---|
| 52 | POST | /api/roles |
Path unchanged | src/pages/entities/[id]/roles/index.tsx |
| 53 | PATCH | /api/roles/{id} |
Path unchanged | src/pages/entities/[id]/roles/[role].tsx |
| 54 | DELETE | /api/roles/{id} |
Path unchanged | src/pages/entities/[id]/roles/[role].tsx |
| 55 | POST | /api/roles/{id}/users |
Path unchanged | src/pages/entities/[id]/index.tsx |
4.14 Permissions
| # | Method | Current URL | Odoo URL | Files |
|---|---|---|---|---|
| 56 | PATCH | /api/permissions/{id} |
Path unchanged | src/pages/permissions/[id].tsx |
4.15 Payments
| # | Method | Current URL | Odoo URL | Files |
|---|---|---|---|---|
| 57 | POST | /api/payments |
Path unchanged | src/pages/payment-record.tsx |
| 58 | PATCH | /api/payments/{id} |
Path unchanged | src/pages/payment-record.tsx |
| 59 | DELETE | /api/payments/{id} |
Path unchanged | src/pages/payment-record.tsx |
| 60 | GET/POST/DELETE | /api/payments/files/{type}/{paymentId} |
Path unchanged | src/components/PaymentAssetManager.tsx |
| 61 | POST | /api/paymob |
Path unchanged | src/components/PaymobPayment.tsx |
Bug fix opportunity: In src/pages/payment-record.tsx, the PATCH call uses api/payments/${id} without a leading /. When switching to the shared axios instance with a baseURL, this will work correctly. But verify during testing.
4.16 Assignments
| # | Method | Current URL | Odoo URL | Files |
|---|---|---|---|---|
| 62 | POST | /api/assignments |
Path unchanged | src/pages/assignments/creator/index.tsx |
| 63 | PATCH | /api/assignments/{id} |
Path unchanged | src/pages/assignments/creator/[id].tsx, src/pages/assignments/[id].tsx |
| 64 | DELETE | /api/assignments/{id} |
Path unchanged | src/pages/assignments/creator/[id].tsx, src/pages/assignments/[id].tsx |
| 65 | POST | /api/assignments/{id}/start |
Path unchanged | src/pages/assignments/creator/[id].tsx, src/pages/assignments/[id].tsx |
4.17 Invites
| # | Method | Current URL | Odoo URL | Files |
|---|---|---|---|---|
| 66 | GET | /api/invites/{decision}/{id} |
POST /api/invites/{decision}/{id} |
src/components/Medium/InviteCard.tsx, src/components/Medium/InviteWithUserCard.tsx |
Note: Currently uses GET for accept/decline actions (side-effectful GET). Odoo should accept POST. Consider updating the frontend to use POST -- it's a more correct HTTP method for state-changing operations.
4.18 Approval Workflows
| # | Method | Current URL | Odoo URL | Files |
|---|---|---|---|---|
| 67 | POST | /api/approval-workflows/create |
Path unchanged | src/pages/approval-workflows/create.tsx |
| 68 | PUT | /api/approval-workflows/{id} |
Path unchanged | src/pages/approval-workflows/[id]/index.tsx |
| 69 | PUT | /api/approval-workflows/{id}/edit |
Path unchanged | src/pages/approval-workflows/[id]/edit.tsx |
| 70 | DELETE | /api/approval-workflows/{id} |
Path unchanged | src/pages/approval-workflows/index.tsx |
4.19 Transcription
| # | Method | Current URL | Odoo URL | Files |
|---|---|---|---|---|
| 71 | POST | /api/transcribe |
Path unchanged | (called from evaluation utils if needed) |
4.20 Batch Users
| # | Method | Current URL | Odoo URL | Files |
|---|---|---|---|---|
| 72 | POST | /api/batch_users |
Path unchanged | (admin import page) |
4.21 Make User (Admin)
| # | Method | Current URL | Odoo URL | Files |
|---|---|---|---|---|
| 73 | POST | /api/make_user |
Path unchanged | (admin user creation) |
5. SSR Migration Strategy
5.1 Current SSR Auth Pattern
~35 pages use this pattern:
import { withIronSessionSsr } from "iron-session/next";
import { sessionOptions } from "@/lib/session";
export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
const user = await requestUser(req, res);
if (!user || !user.isVerified) return redirect("/login");
return { props: { user } };
}, sessionOptions);
5.2 Recommended: JWT Cookie + Server-Side Validation (Option A)
Store the JWT in a regular cookie (set by js-cookie on login). In getServerSideProps, read the cookie from the request and call Odoo to validate.
New helper src/lib/ssr-auth.ts:
import type { GetServerSidePropsContext, GetServerSidePropsResult } from "next";
const API_URL = process.env.NEXT_PUBLIC_API_URL;
const TOKEN_COOKIE = "encoach_token";
export function withAuth<P extends Record<string, unknown>>(
handler: (ctx: GetServerSidePropsContext, user: User) => Promise<GetServerSidePropsResult<P>>
) {
return async (ctx: GetServerSidePropsContext): Promise<GetServerSidePropsResult<P>> => {
const token = ctx.req.cookies[TOKEN_COOKIE];
if (!token) {
return { redirect: { destination: "/login", permanent: false } };
}
try {
const res = await fetch(`${API_URL}/api/user`, {
headers: { Authorization: `Bearer ${token}` },
});
if (!res.ok) throw new Error("Unauthorized");
const user = await res.json();
if (!user.isVerified) {
return { redirect: { destination: "/login", permanent: false } };
}
return handler(ctx, user);
} catch {
return { redirect: { destination: "/login", permanent: false } };
}
};
}
Page migration example:
// BEFORE
export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
const user = await requestUser(req, res);
if (!user) return redirect("/login");
// ... fetch additional data using user ...
return { props: { user, someData } };
}, sessionOptions);
// AFTER
export const getServerSideProps = withAuth(async (ctx, user) => {
// ... fetch additional data using user ...
return { props: { user, someData } };
});
5.3 Alternative: Client-Only Auth (Option B)
Remove all getServerSideProps auth checks. Every page uses useUser({ redirectTo: "/login" }) to protect itself.
Pros: Simpler; no SSR auth at all. Cons: Brief flash of unauthenticated content; pages that fetch data server-side need to move data fetching to client-side.
5.4 Pages Requiring SSR Migration
Every page below currently uses withIronSessionSsr and needs to switch to withAuth (Option A) or remove SSR auth (Option B):
| Page | SSR Data Fetching |
|---|---|
src/pages/index.tsx |
User only (redirects to dashboard) |
src/pages/login.tsx |
Reverse guard (redirect if already logged in) |
src/pages/dashboard/index.tsx |
User only |
src/pages/dashboard/teacher.tsx |
User + groups + assignments + stats |
src/pages/dashboard/student.tsx |
User + assignments + stats |
src/pages/dashboard/corporate.tsx |
User + entity data |
src/pages/dashboard/mastercorporate.tsx |
User + multi-entity data |
src/pages/dashboard/admin.tsx |
User + platform stats |
src/pages/dashboard/developer.tsx |
User |
src/pages/users/index.tsx |
User + user list |
src/pages/users/performance.tsx |
User + performance data |
src/pages/training/index.tsx |
User |
src/pages/training/[id]/index.tsx |
User + training data |
src/pages/tickets.tsx |
User + tickets |
src/pages/stats.tsx |
User + stats |
src/pages/statistical.tsx |
User |
src/pages/settings.tsx |
User |
src/pages/record.tsx |
User |
src/pages/profile.tsx |
User |
src/pages/popout.tsx |
User (session-based) |
src/pages/permissions/index.tsx |
User + permissions |
src/pages/permissions/[id].tsx |
User + permission detail |
src/pages/payment.tsx |
User |
src/pages/payment-record.tsx |
User + payments |
src/pages/official-exam.tsx |
User |
src/pages/generation.tsx |
User |
src/pages/exercises.tsx |
User + exam data |
src/pages/exam.tsx |
User + exam + session data |
src/pages/entities/index.tsx |
User + entities |
src/pages/entities/create.tsx |
User |
src/pages/entities/[id]/index.tsx |
User + entity detail |
src/pages/entities/[id]/roles/index.tsx |
User + roles |
src/pages/entities/[id]/roles/[role].tsx |
User + role detail |
src/pages/classrooms/*.tsx |
User + groups |
src/pages/assignments/*.tsx |
User + assignments |
src/pages/approval-workflows/*.tsx |
User + workflows |
For pages that only need user auth (no server-side data): Replace getServerSideProps with client-side useUser({ redirectTo: "/login" }).
For pages that fetch additional data server-side: Use withAuth and make the data-fetching calls to Odoo using the JWT token from the cookie.
6. Files to Delete
After migration, the following files are no longer needed. They represent the BFF layer being replaced by Odoo.
6.1 API Route Files (108 files)
Delete the entire src/pages/api/ directory:
Auth & User:
src/pages/api/login.tssrc/pages/api/logout.tssrc/pages/api/register.tssrc/pages/api/user.tssrc/pages/api/make_user.tssrc/pages/api/batch_users.tssrc/pages/api/reset/index.tssrc/pages/api/reset/confirm.tssrc/pages/api/reset/sendVerification.tssrc/pages/api/reset/verify.tssrc/pages/api/users/update.tssrc/pages/api/users/search.tssrc/pages/api/users/list.tssrc/pages/api/users/controller.tssrc/pages/api/users/balance.tssrc/pages/api/users/agents/index.tssrc/pages/api/users/agents/[code].tssrc/pages/api/users/[id].ts
Exams:
src/pages/api/exam/index.tssrc/pages/api/exam/upload.tssrc/pages/api/exam/avatars.tssrc/pages/api/exam/generate/[...module].tssrc/pages/api/exam/media/[...module].tssrc/pages/api/exam/media/poll.tssrc/pages/api/exam/media/instructions.tssrc/pages/api/exam/[module]/index.tssrc/pages/api/exam/[module]/import.tssrc/pages/api/exam/[module]/[id].ts
Evaluation:
src/pages/api/evaluate/writing.tssrc/pages/api/evaluate/speaking.tssrc/pages/api/evaluate/interactiveSpeaking.tssrc/pages/api/evaluate/status.tssrc/pages/api/evaluate/fetchSolutions.ts
Grading:
src/pages/api/grading/index.tssrc/pages/api/grading/multiple.ts
Stats & Sessions:
src/pages/api/stats/index.tssrc/pages/api/stats/update.tssrc/pages/api/stats/disabled.tssrc/pages/api/stats/user/[user].tssrc/pages/api/stats/session/[session].tssrc/pages/api/stats/[id]/index.tssrc/pages/api/stats/[id]/[export]/pdf.tsxsrc/pages/api/statistical.tssrc/pages/api/sessions/index.tssrc/pages/api/sessions/[id].ts
Training:
src/pages/api/training/index.tssrc/pages/api/training/[id].tssrc/pages/api/training/user/[user].tssrc/pages/api/training/walkthrough/index.ts
Entities & Groups:
src/pages/api/entities/index.tssrc/pages/api/entities/users.tssrc/pages/api/entities/groups.tssrc/pages/api/entities/[id]/index.tssrc/pages/api/entities/[id]/users.tssrc/pages/api/groups/index.tssrc/pages/api/groups/controller.tssrc/pages/api/groups/[id].ts
Roles & Permissions:
src/pages/api/roles/index.tssrc/pages/api/roles/[id]/index.tssrc/pages/api/roles/[id]/users.tssrc/pages/api/permissions/index.tssrc/pages/api/permissions/bootstrap.tssrc/pages/api/permissions/[id].ts
Invites & Codes:
src/pages/api/invites/index.tssrc/pages/api/invites/[id].tssrc/pages/api/invites/accept/[id].tssrc/pages/api/invites/decline/[id].tssrc/pages/api/code/index.tssrc/pages/api/code/entities.tssrc/pages/api/code/[id].ts
Payments:
src/pages/api/stripe.tssrc/pages/api/paypal/index.tssrc/pages/api/paypal/approve.tssrc/pages/api/paypal/raas.tssrc/pages/api/paymob/index.tssrc/pages/api/paymob/webhook.tssrc/pages/api/payments/index.tssrc/pages/api/payments/[id].tssrc/pages/api/payments/assigned.tssrc/pages/api/payments/paypal.tssrc/pages/api/payments/files/[type]/[paymentId].tssrc/pages/api/packages/index.tssrc/pages/api/packages/[id].tssrc/pages/api/discounts/index.tssrc/pages/api/discounts/[id].ts
Assignments:
src/pages/api/assignments/index.tssrc/pages/api/assignments/corporate/index.tssrc/pages/api/assignments/corporate/[id].tssrc/pages/api/assignments/[id]/index.tssrc/pages/api/assignments/[id]/start.tssrc/pages/api/assignments/[id]/release.tssrc/pages/api/assignments/[id]/archive.tsxsrc/pages/api/assignments/[id]/unarchive.tsxsrc/pages/api/assignments/[id]/[export]/pdf.tsxsrc/pages/api/assignments/[id]/[export]/excel.ts
Approval Workflows:
src/pages/api/approval-workflows/index.tssrc/pages/api/approval-workflows/create.tssrc/pages/api/approval-workflows/[id]/index.tssrc/pages/api/approval-workflows/[id]/edit.ts
Other:
src/pages/api/storage/index.tssrc/pages/api/storage/insert.tssrc/pages/api/storage/delete.tssrc/pages/api/speaking.tssrc/pages/api/transcribe/index.tssrc/pages/api/tickets/index.tssrc/pages/api/tickets/[id].tssrc/pages/api/tickets/assignedToUser/index.tssrc/pages/api/hello.ts
6.2 Backend Utility Files (14 files)
These files contain MongoDB query logic used by API routes. Delete all:
src/utils/approval.workflows.be.tssrc/utils/assignments.be.tssrc/utils/codes.be.tssrc/utils/disabled.be.tssrc/utils/entities.be.tssrc/utils/exams.be.tssrc/utils/grading.be.tssrc/utils/groups.be.tssrc/utils/invites.be.tssrc/utils/permissions.be.tssrc/utils/roles.be.tssrc/utils/sessions.be.tssrc/utils/stats.be.tssrc/utils/users.be.ts
6.3 Library Files (3 files)
src/lib/session.ts-- iron-session config (replaced bysrc/lib/api.ts)src/lib/mongodb.ts-- MongoDB connection (no longer needed)src/lib/createWorkflowsOnExamCreation.ts-- server-side workflow logic (moved to Odoo)
Keep:
src/lib/utils.ts-- general client-side utilitiessrc/lib/formidable-serverless.d.ts-- only needed if any remaining file upload logic stays; likely deletable
6.4 Firebase Files
If Odoo replaces Firebase Auth entirely, also remove:
src/firebase/directory (Firebase client config)src/constants/platform.json(Firebase service account -- server-side only)src/constants/staging.json(Firebase service account -- server-side only)
6.5 Dependencies to Remove from package.json
iron-session
firebase
firebase-admin
mongodb
@beam-australia/react-env (if no longer needed for client env)
bcrypt
formidable
6.6 Dependencies to Add
js-cookie (JWT cookie storage)
@types/js-cookie (TypeScript types)
7. Migration Checklist
A step-by-step checklist for the frontend developer. Complete in order.
Phase 1: Infrastructure Setup
- 1.1 Add
NEXT_PUBLIC_API_URLto.env.local(point to Odoo dev instance) - 1.2 Create
src/lib/api.tswith shared axios instance, base URL, and JWT interceptor (Section 2.3) - 1.3 Create
src/lib/auth.tswithsetToken(),getToken(),clearToken()helpers - 1.4 Add
js-cookiedependency:yarn add js-cookie @types/js-cookie - 1.5 Create
src/lib/ssr-auth.tswithwithAuth()helper for SSR (Section 5.2)
Phase 2: Auth Migration
- 2.1 Update
src/pages/login.tsx: useapi.post(), store JWT, extract user from response - 2.2 Update
src/pages/register.tsx: move registration to client-side, useapi.post() - 2.3 Update logout calls in
Sidebar.tsx,MobileMenu.tsx,official-exam.tsx: useapi.post()+clearToken() - 2.4 Update
src/hooks/useUser.tsx: useapiinstance for SWR fetcher - 2.5 Update
src/utils/email.ts: useapiinstance - 2.6 Verify login/logout/register work end-to-end with Odoo
Phase 3: SSR Migration
- 3.1 For each page in the SSR page list (Section 5.4), replace
withIronSessionSsrwithwithAuthor client-sideuseUser({ redirectTo: "/login" }) - 3.2 Update pages that fetch server-side data to call Odoo with the JWT token
- 3.3 Verify all ~35 protected pages redirect correctly when not authenticated
- 3.4 Verify the login page redirects correctly when already authenticated
Phase 4: API Call Migration (domain by domain)
For each domain, replace axios with api from @/lib/api:
- 4.1 User endpoints (
src/pages/profile.tsx,src/components/UserCard.tsx,src/utils/groups.ts,src/components/Imports/StudentClassroomTransfer.tsx) - 4.2 Exam endpoints (
src/utils/exams.ts,src/hooks/useExams.tsx,src/pages/generation.tsx, allExamEditorfiles,ExamList.tsx) - 4.3 Evaluation endpoints (
src/utils/evaluation.ts) - 4.4 Storage endpoints (all
ExamEditor/SettingsEditorfiles,src/utils/evaluation.ts) - 4.5 Speaking audio (
src/components/Solutions/Speaking.tsx,InteractiveSpeaking.tsx) - 4.6 Sessions (
src/stores/exam/index.ts,src/components/Medium/SessionCard.tsx) - 4.7 Stats (
src/stores/exam/index.ts,src/pages/training/[id]/index.tsx,src/pages/statistical.tsx) - 4.8 Training (
src/pages/training/index.tsx,src/pages/training/[id]/index.tsx) - 4.9 Groups (
src/utils/groups.ts,src/pages/classrooms/create.tsx,src/pages/classrooms/[id].tsx) - 4.10 Entities (
src/pages/entities/create.tsx,src/pages/entities/[id]/index.tsx) - 4.11 Roles (
src/pages/entities/[id]/roles/index.tsx,[role].tsx) - 4.12 Permissions (
src/pages/permissions/[id].tsx) - 4.13 Payments (
src/pages/payment-record.tsx,src/components/PaymentAssetManager.tsx,src/components/PaymobPayment.tsx) - 4.14 Assignments (
src/pages/assignments/creator/index.tsx,[id].tsx,src/pages/assignments/[id].tsx) - 4.15 Invites (
src/components/Medium/InviteCard.tsx,InviteWithUserCard.tsx) - 4.16 Approval workflows (
src/pages/approval-workflows/*.tsx) - 4.17 Codes (
src/pages/register.tsx) - 4.18 Remaining: transcribe, batch_users, make_user, statistical
- 4.19 Update
WordUploader.tsxfetch()call with base URL and auth header
Phase 5: Cleanup
- 5.1 Delete all 108 files in
src/pages/api/ - 5.2 Delete all 14
src/utils/*.be.tsfiles - 5.3 Delete
src/lib/session.ts,src/lib/mongodb.ts,src/lib/createWorkflowsOnExamCreation.ts - 5.4 Delete
src/firebase/directory (if Firebase Auth fully removed) - 5.5 Delete
src/constants/platform.json,src/constants/staging.json - 5.6 Remove unused dependencies from
package.json:iron-session,firebase,firebase-admin,mongodb,bcrypt,formidable - 5.7 Run
yarn installto update lockfile - 5.8 Run TypeScript compiler (
yarn tsc --noEmit) and fix any remaining type errors - 5.9 Run linter (
yarn lint) and fix issues
Phase 6: Testing
- 6.1 Test login flow (email + password -> JWT stored -> user loaded)
- 6.2 Test registration flow (individual and corporate)
- 6.3 Test logout (token cleared, redirect to login)
- 6.4 Test SSR protection (unauthenticated access to protected page -> redirect)
- 6.5 Test exam taking flow (load exam, answer questions, submit, save stats)
- 6.6 Test writing/speaking grading (submit, poll, receive result)
- 6.7 Test entity/group/classroom management (CRUD operations)
- 6.8 Test assignment lifecycle (create, start, release, archive)
- 6.9 Test payment flows (Stripe, PayPal, Paymob)
- 6.10 Test file uploads (exam media, profile pictures)
- 6.11 Test training content generation
- 6.12 Test ticket creation (from platform and landing page)
- 6.13 Test admin user management (create, update, batch import)
- 6.14 Cross-browser test (Chrome, Firefox, Safari)