- Fixed broken Markdown formatting (removed excessive backslashes) - Added Section 11: Feasibility Assessment - Current state summary (8 items already in place) - Identified gaps (6 items) - Detailed gap analysis for session timer, monorepo, nested routes - Feasibility score: 7/10 - Recommended execution order (Phase 0-4) - Summary with challenges and strengths
20 KiB
Frontend Migration Plan: React + Tailwind + shadcn/ui
Project: IRT Bank Soal
Target Architecture: Decoupled (FastAPI Backend + React SPA Frontend)
Date Prepared: 2026-06-17
1. Executive Summary
This document outlines the strategic plan to migrate the user interface of the IRT Bank Soal project from a server-rendered application (FastAPI-Admin & Jinja/Web) to a modern Single Page Application (SPA) architecture using React, Tailwind CSS, and shadcn/ui components. This migration aims to significantly enhance UI responsiveness—especially for the Computer Adaptive Testing (CAT) feature—and streamline the development of future interactive features.
2. Target Frontend Tech Stack
| Category | Primary Technology | Rationale |
|---|---|---|
| Framework | Vite + React (TypeScript) | Fast build times, responsive Hot Module Replacement (HMR), industry standard for SPAs. |
| Styling | Tailwind CSS | Utility-first styling approach, accelerating the UI slicing process. |
| UI Components | shadcn/ui | Accessible headless components (Radix UI) that are copy-pasteable and fully customizable via Tailwind. |
| Data Fetching | TanStack Query (React Query) | Server-state management, caching, automatic retry logic for unstable connections, and elegant loading/error state handling. |
| State Management | Zustand / React Context | Lightweight client-state storage (e.g., WordPress JWT tokens, X-Website-ID, UI themes). Employs persist middleware for crash/reload recovery. |
| Routing | React Router DOM | Seamless navigation using BrowserRouter for clean URLs. Enables robust Nested Routing required for the new hierarchical admin structure. |
| Form Handling | React Hook Form + Zod | Strict input validation and type safety (aligning perfectly with Pydantic models in the backend). |
3. Repository Restructuring (Monorepo Approach)
It is highly recommended to use a single repository (monorepo) structure with separate folders for the frontend and backend to simplify version control.
yellow-bank-soal/
├── backend/ # Existing FastAPI application folder (app/, alembic/, etc.)
│ ├── Dockerfile # Patched Backend Dockerfile
│ ├── app/
│ ├── requirements.txt
│ └── ...
├── frontend/ # New React application
│ ├── Dockerfile # New Frontend Dockerfile (Multi-stage Nginx)
│ ├── .env.example # VITE_API_BASE_URL references
│ ├── src/
│ ├── package.json
│ └── ...
└── docker-compose.yml # Orchestrates ALL services (API, UI, Redis, Celery)
4. Migration Phases
The migration will be divided into 4 sequential phases to minimize disruption to the existing system.
Phase 1: Backend Preparation (API Readiness)
Focus on preparing FastAPI to securely communicate with an external React application.
- CORS Configuration: Update
ALLOWED_ORIGINSinapp/core/config.pyto permit frontend origins (e.g.,http://localhost:5173for development and the target production domain). - Endpoint Audit & Restructuring: Ensure all administrative functionalities are exposed via RESTful API endpoints. Crucially, restructure the API endpoints to match the new UI hierarchy (e.g.,
GET /api/v1/admin/tryouts/{id}/questionsinstead of a standalone/questionsendpoint). - Data Scrubbing: Strictly ensure that endpoints like
/api/v1/session/{id}/next-itemomit sensitive fields such ascorrect_answer,ctt_p, andirt_bfrom the response payload to prevent client-side cheating.
Phase 2: Frontend Scaffolding & Design System
Focus on project initialization and establishing the UI foundation.
-
Vite Initialization: Run
npm create vite@latest frontend -- --template react-ts. -
Environment Variables Setup: Define
VITE_API_BASE_URLin frontend.envso Axios knows exactly where to route the API requests across different environments. -
Tailwind & shadcn Setup:
- Install Tailwind CSS and configure
tailwind.config.js. - Run
npx shadcn-ui@latest initto set up base CSS variables and utility functions.
- Install Tailwind CSS and configure
-
Core Components (shadcn): Install frequently used foundational components:
npx shadcn-ui@latest add button card input label dialog alert table tabs progress radio-group toast collapsible accordion badge -
API Client Setup: Configure an Axios instance to universally append:
- The Authorization Bearer Token (from WordPress).
- The
X-Website-IDheader for multi-tenant isolation.
Phase 3: Student Portal Construction (Core Business Flow)
Focus on the primary user interaction: executing the adaptive tryout.
- Tryout Listing: A view displaying available tryouts for the user based on their X-Website-ID.
- Exam Dashboard (Session) & Asynchronous Forms:
- UI: Utilize shadcn's Card for the question area, RadioGroup for options, and a Progress bar for tracking exam status.
- No-Reload Submissions (AJAX): Completely replace legacy PHP
$_POSTform actions. Forms will usee.preventDefault()to stop browser reloads. Submissions to/adaptive/respondwill be handled asynchronously in the background via TanStack Query. - Instant Feedback (Toast): Upon submitting an answer, the UI will instantly display a non-intrusive shadcn Toast notification and smoothly render the next question.
- State Recovery & Timer Security (Crucial):
- Anti-Refresh: Use Zustand's persist middleware to save the current session ID and active question ID into localStorage. If the user accidentally hits F5 or closes the tab, the React app can instantly resume the exam state.
- Server-Synced Timer: Do not rely on the client's
Date.now(). Fetch the exam's exact server-side end time (expires_at) from the FastAPI backend and calculate the countdown on the frontend based on that fixed timestamp.
- Result Page: A summary view to display the Raw Score (NM) and Normalized Score (NN) upon exam completion.
Phase 4: Admin Panel Construction (Hierarchy-Driven Redesign)
Based on the ADMIN_TRYOUT_RESTRUCTURE_PLAN, the admin UI will shift from a scattered menu to a deeply nested, Tryout-centric navigation leveraging React Router DOM.
- Tree-Based Root Navigation (
/admin/tryouts):- Replace standard data tables with an interactive Tree/Collapsible layout grouped by Websites.
- Implement Stat Cards inline for each Tryout (showing NM, NN averages, and Calibration Progress).
- Add a global
[+ Import Tryout]button/modal directly in the header of this tree view.
- Nested Tryout Workspaces:
- Utilize React Router's nested routing to build drill-down pages maintaining the parent Tryout context:
/admin/tryout/:id/attempts(filtered DataTable of sessions)/admin/tryout/:id/normalization(settings form to update NM/NN targets)/admin/tryout/:id/questions(filtered DataTable of basis questions)
- Utilize React Router's nested routing to build drill-down pages maintaining the parent Tryout context:
- Question AI Workspace (
/admin/tryout/:id/questions/:questionId/workspace):- Build a dedicated tabbed interface using shadcn Tabs (Generate, Review, Batch).
- Provide seamless integration with the OpenRouter AI generation API endpoints.
5. UI/UX Design Guidelines (Human-Centric Approach)
To resolve the "developer-centric" nature of the legacy system, the frontend must adopt a "Human POV" ensuring the dashboard is intuitive, workflow-oriented, and actionable.
5.1. Dashboard Layout Re-imagination
Shift away from displaying raw database counts. The new Home Dashboard (/admin/dashboard) must include:
- Personalized Greeting: E.g., "Good Morning, Admin! Last login..."
- Actionable System Overview: Display meaningful KPIs (Active Tryouts, Average Scores, Completion Rates).
- Attention Needed (Alerts): A dedicated section highlighting urgent tasks (e.g., "23 questions need calibration", "5 AI questions pending review").
- Quick Actions: Prominent buttons for daily workflows (
[Import Tryout],[Generate AI]).
5.2. Visual Indicators & Color Coding
Use Tailwind CSS classes to create consistent, semantic color coding across the application.
- Difficulty Badges:
- Easy (p > 0.70): Green (
bg-green-100 text-green-800) - Medium (0.30 ≤ p ≤ 0.70): Yellow (
bg-yellow-100 text-yellow-800) - Hard (p < 0.30): Red (
bg-red-100 text-red-800)
- Easy (p > 0.70): Green (
- Calibration Status:
- Ready (≥90%): Green Checkmark icon / Progress Bar
- Partial (50-89%): Yellow Warning icon / Progress Bar
- Needs Data (<50%): Red Cross icon / Progress Bar
5.3. Terminology Mapping (System to UI)
While the FastAPI backend retains technical database names, the React frontend must translate these terms into human-readable labels:
| System Term (Backend) | UI Label (Frontend) | Context |
|---|---|---|
| Session | Student Attempt | Table headers, Navigation |
| Calibration | Question Quality | Dashboards, Menus |
| IRT | Adaptive Scoring | Tryout Settings |
| CTT | Standard Scoring | Tryout Settings |
| NM (Nilai Mentah) | Raw Score | Reports, Attempt Lists |
| NN (Nilai Nasional) | Normalized Score | Reports, Attempt Lists |
| p-value | Difficulty Score | Question Data Grids |
6. Key shadcn/ui Component Mapping
| Feature Requirement | Recommended shadcn/ui Component | Usage / Context |
|---|---|---|
| Tryouts Hierarchy Map | Collapsible, Accordion | Creates the nested tree structure for Websites -> Tryouts list. |
| Tryout Stat Cards | Card, Badge, Progress | Displays quick metrics (Participants, NM avg, Calibration progress) in the tree. |
| Visual Indicators | Badge | Colored badges for difficulty levels and calibration status. |
| Nested Navigation | Tabs | Switches between "Generate", "Review", and "Batch" in the Question Workspace. |
| Question & Options View | Card, RadioGroup | Wraps the question stem and manages A/B/C/D selections. |
| Form Feedback & Notices | Toast | Displays non-blocking success/error messages asynchronously without page reloads. |
| Alerts / Errors | Alert | Displays prominent API error messages (e.g., lost internet connection). |
| Submit Confirmation | AlertDialog | Prompts the user before calling /session/complete to prevent accidental submissions. |
| Item/Student Roster | DataTable (Table) | Renders data grids for Attempts and Questions with server-side sort/filter capabilities. |
| Tryout Settings | Switch, Form | Configures CTT/IRT parameters and Normalization targets. |
7. Security Checklist
- Client-Side Authorization: Implement Route Guards via React Router to prevent unauthorized access to active exam sessions and admin pages.
- Sensitive Data Protection: Ensure the client-state manager (Zustand) never stores data the student shouldn't see (e.g., b values, p-values, or correct answers).
- HTML Sanitization: If the question text (stem) contains rich HTML (from an editor), process it through a library like dompurify before rendering it via
dangerouslySetInnerHTMLto prevent XSS attacks.
8. References
- Vite Documentation
- Tailwind CSS Documentation
- shadcn/ui Documentation
- TanStack Query Documentation
- React Router Documentation
9. Deployment & Routing Strategy
Since the React application operates as a standalone frontend and relies on WordPress exclusively via API for data integration, the URL routing must be handled cleanly without interference from WordPress.
Chosen Strategy: BrowserRouter (HTML5 History API)
- URL Format:
https://app.domain.com/session/123(Clean, SEO-friendly URLs) - Rationale: As a standalone deployment, there are no conflicts with WordPress's internal rewrite rules or
.htaccess. BrowserRouter provides the standard React routing experience. - Server Requirement (Crucial): To prevent 404 Not Found errors when users manually refresh a page (e.g., hitting F5 on
/session/123), the web server hosting the built React files must be configured to redirect all missing paths back toindex.html.
10. Docker & Containerization Strategy
To support the decoupled architecture and the existing AI features, the deployment process will utilize Docker Compose to orchestrate the backend, frontend, Redis, and Celery workers.
10.1. Backend Dockerfile Patch (backend/Dockerfile)
The existing Dockerfile is well-structured but needs a minor patch for production. The --reload flag must be removed as it consumes excessive resources.
# ... existing setup ...
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
# PATCH: Removed the '--reload' flag for production readiness
CMD ["sh", "-c", "alembic upgrade head && uvicorn app.main:app --host 0.0.0.0 --port 8000"]
10.2. Frontend Dockerfile (frontend/Dockerfile)
A new multi-stage Dockerfile is required for the React application. Stage 1 compiles the application using Node.js, and Stage 2 serves the static files using a lightweight Nginx server configured for BrowserRouter.
# Stage 1: Build the React application
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
# Using ARG to inject API URL during the build phase
ARG VITE_API_BASE_URL
ENV VITE_API_BASE_URL=$VITE_API_BASE_URL
RUN npm run build
# Stage 2: Serve with Nginx
FROM nginx:alpine
# Copy the built assets from Stage 1
COPY --from=builder /app/dist /usr/share/nginx/html
# Add a custom Nginx configuration to support BrowserRouter (catch-all rule)
RUN echo 'server { \
listen 80; \
location / { \
root /usr/share/nginx/html; \
index index.html index.htm; \
try_files $uri $uri/ /index.html; \
} \
}' > /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
10.3. Full Orchestration (docker-compose.yml)
Place this at the root of the monorepo to run the complete ecosystem. This includes Redis and Celery which are required for the OpenRouter AI generation feature outlined in the PRD.
version: '3.8'
services:
# 1. FastAPI Backend
backend:
build:
context: ./backend
ports:
- "8000:8000"
env_file:
- ./backend/.env
depends_on:
- redis
restart: unless-stopped
# 2. Redis Message Broker (Required by Celery)
redis:
image: redis:7-alpine
ports:
- "6379:6379"
restart: unless-stopped
# 3. Celery Worker (For AI Generation Background Tasks)
celery_worker:
build:
context: ./backend
command: ["celery", "-A", "app.core.celery_app", "worker", "--loglevel=info"]
env_file:
- ./backend/.env
depends_on:
- backend
- redis
restart: unless-stopped
# 4. React Frontend SPA
frontend:
build:
context: ./frontend
args:
# Inject the backend API URL into the React build
VITE_API_BASE_URL: "https://api.yourdomain.com/api/v1"
ports:
- "80:80"
depends_on:
- backend
restart: unless-stopped
11. Feasibility Assessment
Assessed: 2026-06-17
11.1 Current State Summary
| Item | Status | Notes |
|---|---|---|
| FastAPI Backend | ✅ Complete | Well-structured with routers, models, services |
| CORS Configuration | ✅ Configured | ALLOWED_ORIGINS in app/core/config.py - just needs to add Vite dev server port (localhost:5173) |
| Session API Endpoints | ✅ Complete | GET /api/v1/session/{id}/next_item, POST /session/{id}/submit_answer, POST /session/{id}/complete |
| Tryout API Endpoints | ✅ Complete | GET /tryout, GET /tryout/{id}/config, PUT /tryout/{id}/normalization |
| Admin Endpoints | ✅ Complete | Calibration, AI toggle, normalization reset |
| Redis/Celery Setup | ✅ In docker-compose.dev.yml | Used for AI generation |
| WordPress Auth Integration | ✅ In place | X-Website-ID header support via app/core/auth.py |
| Data Scrubbing (Security) | ✅ Done | Session endpoints already omit sensitive fields (correct_answer, ctt_p, irt_b) |
11.2 Identified Gaps
| Item | Status | Action Required |
|---|---|---|
No frontend/ folder |
❌ Missing | Create React app from scratch |
No root docker-compose.yml |
❌ Missing | Currently only docker-compose.dev.yml exists |
| Backend Dockerfile location | ⚠️ Inconsistent | Current Dockerfile is at root, needs to be moved to backend/ |
expires_at in session |
⚠️ Not found | Session model may not have server-side end time for timer sync |
| Nested admin routes | ⚠️ Flat structure | Current admin routes are flat, need hierarchical restructuring |
| Monorepo structure | ⚠️ Not set up | Root currently IS the backend, needs folder restructuring |
11.3 Detailed Gap Analysis
Gap 1: Session Timer Implementation
The plan mentions fetching expires_at from the backend for server-synced timers. However, the Session model (app/models/session.py) does not have an explicit expires_at field. Only start_time exists.
Action needed: Add expires_at field to Session model and update session creation endpoint.
Gap 2: Monorepo Structure
Current repository layout:
yellow-bank-soal/ # Root = backend
├── app/ # FastAPI app
├── Dockerfile # Backend Dockerfile at root
├── docker-compose.dev.yml # Dev setup
Plan requires:
yellow-bank-soal/ # Root = monorepo
├── backend/ # Move current root to backend/
├── frontend/ # New React app
└── docker-compose.yml # New orchestration
Action needed: Significant file reorganization - move existing files to backend/ subfolder.
Gap 3: Nested Admin Routes
Current admin endpoints are flat:
/api/v1/admin/{tryout_id}/calibrate✅/api/v1/tryout/{tryout_id}/config✅
Plan requires nested structure:
/api/v1/admin/tryouts/{id}/questions❌ (doesn't exist)/api/v1/admin/tryout/{id}/attempts❌ (doesn't exist)
Action needed: Create new nested router structure in app/routers/admin/.
11.4 Feasibility Score: 7/10
| Category | Score | Notes |
|---|---|---|
| Backend API Readiness | 8/10 | Core endpoints exist, minor gaps in session expiration |
| Infrastructure | 6/10 | Needs restructuring for monorepo |
| Auth Integration | 9/10 | WordPress JWT + X-Website-ID already in place |
| Docker Setup | 5/10 | Need new docker-compose.yml + frontend Dockerfile |
| Data Security | 9/10 | Already scrubbing sensitive fields |
11.5 Recommended Execution Order
-
Phase 0: Repository Restructuring (High Impact)
- Move current root contents →
backend/ - Create
frontend/with Vite scaffold - Update Dockerfile references
- Create root
docker-compose.yml
- Move current root contents →
-
Phase 1: Backend Additions
- Add
expires_atto Session model - Create nested admin endpoints (
/admin/tryout/:id/questions, etc.) - Update CORS for
localhost:5173
- Add
-
Phase 2-4: Frontend Build (Follow original plan)
11.6 Summary
The migration plan is doable but requires significant upfront work on repository restructuring and a few backend additions. The core FastAPI infrastructure is solid, and the auth/scoring logic is already well-implemented.
Main challenges:
- Monorepo migration - moving existing code to
backend/subfolder - Session expiration tracking - adding server-side timer (
expires_at) - Nested admin routes - restructuring some API endpoints
Strengths:
- Complete session/tryout API already exists
- Data scrubbing already implemented
- WordPress integration already in place
- Redis/Celery for AI already configured