# Copilot Instructions for Vontor CZ ## Overview This monorepo contains a Django backend and a Vite/React frontend, orchestrated via Docker Compose. The project is designed for a Czech e-marketplace, with custom payment integrations and real-time features. ## Architecture - **backend/**: Django project (`vontor_cz`), custom `account` app, and third-party payment integrations (`thirdparty/`). - Uses Django REST Framework, Channels (ASGI), Celery, and S3/static/media via `django-storages`. - Custom user model: `account.CustomUser`. - API docs: DRF Spectacular (`/api/schema/`). - **frontend/**: Vite + React + TypeScript app. - Organized by `src/api/`, `components/`, `features/`, `layouts/`, `pages/`, `routes/`. - Uses React Router layouts and nested routes (see `src/layouts/`, `src/routes/`). - Uses Tailwind CSS for styling (configured via `src/index.css` with `@import "tailwindcss";`). Prefer utility classes over custom CSS. ## Developer Workflows - **Backend** - Local dev: `python manage.py runserver` (or use Docker Compose) - Migrations: `python manage.py makemigrations && python manage.py migrate` - Celery: `celery -A vontor_cz worker -l info` - Channels: Daphne/ASGI (see Docker Compose command) - Env config: `.env` files in `backend/` (see `.gitignore` for secrets) - **Frontend** - Install: `npm install` - Dev server: `npm run dev` - Build: `npm run build` - Preview: `npm run preview` - Static assets: `src/assets/` (import in JS/CSS), `public/` (referenced in HTML) ## Conventions & Patterns - **Backend** - Use environment variables for secrets and config (see `settings.py`). - Static/media files: S3 in production, local in dev (see `settings.py`). - API versioning and docs: DRF Spectacular config in `settings.py`. - Custom permissions, filters, and serializers in each app. - **Frontend** - Use React Router layouts for shared UI (see `src/layouts/`, `LAYOUTS.md`). - API calls and JWT handling in `src/api/`. - Route definitions and guards in `src/routes/` (`ROUTES.md`). - Use TypeScript strict mode (see `tsconfig.*.json`). - Linting: ESLint config in `eslint.config.js`. - Styling: Tailwind CSS is present. Prefer utility classes; keep minimal component-scoped CSS. Global/base styles live in `src/index.css`. Avoid inline styles and CSS-in-JS unless necessary. ### Frontend API Client (required) All frontend API calls must use the shared client at frontend/src/api/Client.ts. - Client.public: no cookies, no Authorization header (for public Django endpoints). - Client.auth: sends cookies and includes Bearer token; auto-refreshes on 401 (retries up to 2x). - Centralized error handling: subscribe via Client.onError to show toasts/snackbars. - Tokens are stored in cookies by Client.setTokens and cleared by Client.clearTokens. Example usage (TypeScript) ```ts import Client from "@/api/Client"; // Public request (no credentials) async function listPublicItems() { const res = await Client.public.get("/api/public/items/"); return res.data; } // Login (obtain tokens and persist to cookies) async function login(username: string, password: string) { // Default SimpleJWT endpoint (adjust if your backend differs) const res = await Client.public.post("/api/token/", { username, password }); const { access, refresh } = res.data; Client.setTokens(access, refresh); } // Authenticated requests (auto Bearer + refresh on 401) async function fetchProfile() { const res = await Client.auth.get("/api/users/me/"); return res.data; } function logout() { Client.clearTokens(); window.location.assign("/login"); } // Global error toasts import { useEffect } from "react"; function useApiErrors(showToast: (msg: string) => void) { useEffect(() => { const unsubscribe = Client.onError((e) => { const { message, status } = e.detail; showToast(status ? `${status}: ${message}` : message); }); return unsubscribe; }, [showToast]); } ``` Vite env used by the client: - VITE_API_BASE_URL (default: http://localhost:8000) - VITE_API_REFRESH_URL (default: /api/token/refresh/) - VITE_LOGIN_PATH (default: /login) Notes - Public client never sends cookies or Authorization. - Ensure Django CORS settings allow your frontend origin. See backend/vontor_cz/settings.py. - Use React Router layouts and guards as documented in frontend/src/routes/ROUTES.md and frontend/src/layouts/LAYOUTS.md. ## Integration Points - **Payments**: `thirdparty/` contains custom integrations for Stripe, GoPay, Trading212. - **Real-time**: Django Channels (ASGI, Redis) for websockets. - **Task queue**: Celery + Redis for async/background jobs. - **API**: REST endpoints, JWT auth, API key support. ### OpenAPI Client Generation (Orval) This project uses **Orval** to auto-generate TypeScript API clients from the Django OpenAPI schema. #### Configuration - **Orval config**: `frontend/src/orval.config.ts` - **Schema URL**: `/api/schema/` (DRF Spectacular endpoint) - **Fetch script**: `frontend/scripts/fetch-openapi.js` - **Commands**: - `npm run api:update` — fetches schema + generates client - Runs: `node scripts/fetch-openapi.js && npx orval` #### Generated Output - **Location**: `frontend/src/api/generated/` - **Files**: TypeScript interfaces, Axios-based API hooks - Uses custom mutators: `publicMutator` and `privateMutator` #### Custom Mutators Two Axios clients handle public/private API requests: **Public Client** (`frontend/src/api/publicClient.ts`): ```ts import axios, { type AxiosRequestConfig } from "axios"; const backendUrl = import.meta.env.VITE_BACKEND_URL || "http://localhost:8000"; export const publicApi = axios.create({ baseURL: backendUrl + "/api/", withCredentials: false, // no cookies for public endpoints }); export const publicMutator = async (config: AxiosRequestConfig): Promise => { const response = await publicApi.request(config); return response.data; }; ``` **Private Client** (`frontend/src/api/privateClient.ts`): ```ts import axios, { type AxiosRequestConfig } from "axios"; const backendUrl = import.meta.env.VITE_BACKEND_URL || "http://localhost:8000"; export const privateApi = axios.create({ baseURL: backendUrl + "/api/", withCredentials: true, // sends HttpOnly cookies (access/refresh tokens) }); // Auto-refresh on 401 privateApi.interceptors.response.use( (res) => res, async (error) => { const original = error.config; if (error.response?.status === 401 && !original._retry) { original._retry = true; try { await privateApi.post("/auth/refresh/"); return privateApi(original); } catch { // optional: logout } } return Promise.reject(error); } ); export const privateMutator = async (config: AxiosRequestConfig): Promise => { const response = await privateApi.request(config); return response.data; }; ``` #### Environment Variables (Vite) - **IMPORTANT**: Use `import.meta.env.VITE_*` instead of `process.env` in browser code - **NEVER** import `dotenv/config` in frontend files (causes "process is not defined" error) - **Available vars**: - `VITE_BACKEND_URL` (default: `http://localhost:8000`) - `VITE_API_BASE_URL` (if using Client.ts wrapper) - `VITE_API_REFRESH_URL` (default: `/api/token/refresh/`) - `VITE_LOGIN_PATH` (default: `/login`) #### Usage Example ```ts import { useGetOrders } from "@/api/generated/orders"; function OrdersList() { const { data, isLoading, error } = useGetOrders(); if (isLoading) return
Loading...
; if (error) return
Error: {error.message}
; return (
    {data?.map(order =>
  • {order.status}
  • )}
); } ``` #### Helpers - **Choices helper**: `frontend/src/api/get_choices.ts` - Function: `getChoices(requests, lang)` - Returns: `{ "Model.field": [{ value, label }] }` ## References - [frontend/REACT.md](../frontend/REACT.md): Frontend structure, workflows, and conventions. - [frontend/src/layouts/LAYOUTS.md](../frontend/src/layouts/LAYOUTS.md): Layout/component patterns. - [frontend/src/routes/ROUTES.md](../frontend/src/routes/ROUTES.md): Routing conventions. - [backend/vontor_cz/settings.py](../backend/vontor_cz/settings.py): All backend config, env, and integration details. - [docker-compose.yml](../docker-compose.yml): Service orchestration and dev workflow. --- **When in doubt, check the referenced markdown files and `settings.py` for project-specific logic and patterns.**