This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Onlinemat is a material/equipment lending management system for Cevi (Swiss youth organization). It's a React TypeScript SPA where departments ("Abteilungen") manage their material inventory, and members can place orders to borrow equipment. The UI is in German.
# Prerequisites: Node.js 24 LTS via nvm, yarn installed globally
nvm use 24
# Install dependencies
yarn
# Start dev server (http://localhost:3000)
yarn dev
# Production build
yarn build
# Preview production build
yarn preview
# Run tests
yarn test # watch mode
yarn test:run # single run (CI-friendly)
yarn test:coverage # single run with coverage report (output: ./coverage)A .env file is required (copy .env.example and fill in secrets). Environment variables use the VITE_ prefix (e.g. VITE_FIREBASE_API_KEY).
Tests live in __tests__/ directories next to the source they test:
src/util/__tests__/CartUtil.test.ts
src/util/__tests__/MaterialUtil.test.ts
src/util/__tests__/UserPermission.test.ts
src/util/__tests__/OrderUtil.test.ts
src/config/casl/__tests__/ability.integration.test.ts
src/hooks/__tests__/useFirestoreCollection.test.ts
- Framework: Vitest (configured in
vite.config.ts) - Component testing:
@testing-library/react+@testing-library/user-event - API mocking:
msw(Mock Service Worker) - Coverage:
@vitest/coverage-v8— reports in text, HTML, and lcov formats - E2E: Playwright (config:
playwright.config.ts, tests:e2e/)
- React 18 with TypeScript 5, built with Vite
- Ant Design (antd) 5 for UI components (CSS-in-JS, no global CSS import)
- Firebase 10 (modular API) as the backend — all domain data flows through real-time Firestore listeners (
onSnapshot) - Auth0 (
@auth0/auth0-reactv2) for authentication, which issues a custom Firebase token for Firestore access - Redux (with redux-actions/redux-thunk) — used only for user authentication state, not for domain data
- CASL for attribute-based access control (permissions)
- dayjs for date handling (with
de-chlocale,isSameOrBeforeandlocalizedFormatplugins) - react-cookie for cart persistence
- Sentry v8 for error tracking
- SCSS modules for styling
tsconfig.json sets baseUrl: "src", so imports are absolute from src/ (e.g., import { ability } from "config/casl/ability"). Vite resolves these via vite-tsconfig-paths.
Auth0 handles user login → issues a custom Firebase token (stored at user["https://mat.cevi.tools/firebase_token"]) → App.tsx calls signInWithCustomToken(auth, token) → Firebase auth state change triggers a Firestore listener on the user document → Redux store and CASL ability rules are updated.
Domain data (materials, orders, categories, etc.) does NOT go through Redux. Instead, AbteilungDetails component sets up real-time Firestore onSnapshot listeners and distributes data via React Contexts:
AbteilungenContext(fromNavigationMenu) — list of all departmentsMembersContext,CategorysContext,MaterialsContext,StandorteContext,MembersUserDataContext(fromAbteilungDetails) — per-department data
All Firebase calls use the modular (tree-shakeable) API:
import { collection, doc, onSnapshot, updateDoc, deleteDoc } from 'firebase/firestore'
import { db } from 'config/firebase/firebase'Collection path constants are in src/config/firebase/collections.ts.
Two-tier access control defined in util/UserPermission.ts:
- Staff users (
user.staff === true): full CRUD on all entities globally - Regular users: role-based permissions per abteilung, stored in
user.roles[abteilungId]
Roles (from most to least privileged): admin → matchef (material chief) → member → guest → pending
Components use <Can I='action' this={{ __caslSubjectType__: 'Type', abteilungId }}> to conditionally render based on permissions.
Routes are defined in src/routes.tsx as AppRoute objects with metadata (icon, access flags: public, private, staffOnly, showInMenue). NavigationMenu filters visible routes based on auth state. Private routes use ProtectedRoute wrapper with Auth0's withAuthenticationRequired.
Auto-deployed via GitHub Actions on push to master. Builds a Docker container (Node 24 build stage → nginx serving stage) pushed to registry.cevi.tools.