Complete technical documentation for the Educado content creator web application.
The web application is built with modern React patterns and provides an intuitive interface for content creators to manage educational materials. It emphasizes speed, type safety, and developer experience.
- React 18 - Component-based UI library
- TypeScript - Type-safe JavaScript
- Vite - Build tool and dev server with HMR
- shadcn/ui - Accessible component library built on Radix UI
- Tailwind CSS v4 - Utility-first styling
- Radix UI - Unstyled, accessible component primitives
- Material Design Icons (MDI) - Icon library
- TanStack Query (React Query) - Server state management
- Declarative data fetching
- Automatic caching and background updates
- Request deduplication
- Optimistic updates
- Zustand - Client state management
- React Router v6 - Client-side routing
- React Hook Form - Performant form validation
- Zod - Schema validation
- TanStack Table - Headless table primitives
- Sorting, filtering, pagination
- Virtualization support
- Fully customizable
web/
├── src/
│ ├── App.tsx # Root component
│ ├── main.tsx # Application entry point
│ ├── features/ # Feature-based modules
│ │ ├── auth/ # Authentication
│ │ ├── course/ # Course management
│ │ ├── lecture/ # Lecture management
│ │ ├── exercise/ # Exercise management
│ │ └── media/ # Media library (see features/media-library.md)
│ ├── shared/ # Shared utilities
│ │ ├── api/ # Generated API client
│ │ ├── components/ # Reusable components
│ │ ├── hooks/ # Custom React hooks
│ │ ├── lib/ # Utility functions
│ │ └── types/ # TypeScript types
│ ├── unplaced/ # Components pending organization
│ └── legacy/ # Unused components that are preserved for potential future reuse
├── public/ # Static assets
├── __tests__/ # Jest tests
├── cypress/ # E2E tests
└── i18n/ # Internationalization
The web app uses an automatically generated API client from the backend's OpenAPI specification:
npm run generate-strapi-clientThis ensures:
- ✅ Type safety for all API calls
- ✅ Auto-completion in IDE
- ✅ Compile-time error checking
- ✅ Automatic updates when backend changes
Components are copy-paste, fully customizable, and accessible:
import { Button } from "@/components/ui/button"
import { Dialog } from "@/components/ui/dialog"
// Fully typed, accessible, themeable
<Button variant="primary">Click me</Button>Benefits:
- Own the code (no npm package bloat)
- Full customization
- Built on Radix UI (accessible by default)
- Tailwind styling
import { useQuery } from "@tanstack/react-query";
import { coursesApi } from "@/shared/api";
function Courses() {
const { data, isLoading } = useQuery({
queryKey: ["courses"],
queryFn: () => coursesApi.getCourses(),
});
// Automatic caching, background refetch, stale-while-revalidate
}import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
const schema = z.object({
title: z.string().min(1),
description: z.string().optional(),
});
function CourseForm() {
const form = useForm({
resolver: zodResolver(schema),
});
// Type-safe, validated forms
}# From repository root
npm run dev:web
# Or from web directory
cd web
npm run devVisits http://localhost:5174
Required variables (in root .env):
VITE_STRAPI_URL=http://localhost:1337
VITE_STRAPI_API_TOKEN=your_token_here
VITE_FRONTEND_PORT=5174All client-side env vars must be prefixed with VITE_.
# Development build
npm run build
# Production build (with optimizations)
NODE_ENV=production npm run buildNote: Production builds currently have issues. The app runs in dev mode even in Docker. See Web Deployment Modes for details.
# Run all tests
npm run test
# Update snapshots
npm run updateSnapshots
# Watch mode
npm run test -- --watch# Open Cypress UI
npx cypress open
# Run headless
npm run cypress_localTest files location: cypress/e2e/
# Check for issues
npm run lint:check
# Fix issues automatically
npm run lint:fix
# Compact format (CI-friendly)
npm run lint:compact# Check formatting
npm run format:check
# Fix formatting
npm run format:fixnpm run type-checkVite automatically code-splits by route. Additional splits can be added:
const HeavyComponent = lazy(() => import("./HeavyComponent"));- Images optimized via Vite
- SVGs inlined when small
- Fonts preloaded
- Prefetch queries for predictable navigation
- Use
staleTimeto reduce unnecessary refetches - Implement pagination for large lists
Currently uses Vite dev server (see Web Deployment Modes):
# Build image
npm run docker:build:web
# Run container
docker run -p 5174:5174 educado-web:latestOnce build issues are resolved:
docker build -f web/Dockerfile.production -t web:prod .This will create a ~50MB nginx image serving static files.
API calls failing
- Check
VITE_STRAPI_API_TOKENis set - Verify backend is running on
VITE_STRAPI_URL - Check browser console for CORS errors
Build fails
- Clear
.turbocache:rm -rf .turbo - Clear node_modules:
npm ci - Check for TypeScript errors:
npm run type-check
Hot reload not working
- Restart dev server
- Check Vite config
- Ensure WSL users have proper file watching setup
// ✅ Good: Collocate related files
features / courses / components / CourseCard.tsx;
CourseForm.tsx;
hooks / useCourses.ts;
types / course.types.ts;
// ❌ Bad: Separate by type
components / CourseCard.tsx;
UserCard.tsx;
hooks / useCourses.ts;
useUsers.ts;// ✅ Good: Use generated client
import { coursesApi } from "@/shared/api";
const courses = await coursesApi.getCourses();
// ❌ Bad: Manual fetch
const res = await fetch("/api/courses");// ✅ Good: Server state with React Query
const { data } = useQuery({
queryKey: ["courses"],
queryFn: fetchCourses,
});
// ✅ Good: Client state with Zustand
const theme = useThemeStore((state) => state.theme);
// ❌ Bad: Server state in local state
const [courses, setCourses] = useState([]);
useEffect(() => {
fetchCourses().then(setCourses);
}, []);