feat: sprint 1 — auth + CRUD engagements
Ship the first feature end-to-end on the UI: users log in with JWT,
admins manage user accounts, and any authenticated user (per RBAC)
can create, list, view, edit, and delete engagements.
Backend (Flask + SQLAlchemy + SQLite, 63 pytest)
- User / Engagement models, Alembic 0001 initial schema
- argon2 password hashing, JWT bearer (60-min TTL), @login_required
and @role_required decorators
- 13 API endpoints under /api/*, including last-admin protection on
DELETE/PATCH user and JSON 404 on unknown /api/* paths
- `flask create-admin` CLI with duplicate / short-password handling
Frontend (React + Vite + Tailwind + TanStack Query, 20 vitest)
- Inter font bundled locally (no CDN), DESIGN.md tokens in Tailwind
- LoginPage / EngagementsList / EngagementForm / EngagementDetail /
UsersAdmin pages with role-aware UI
- Layout, ProtectedRoute, StatusBadge, FormField, LoadingState,
ErrorState, EmptyState, Toast + provider
- Axios client: Bearer interceptor, 401 → purge + /login + "Session
expirée" toast, 403 → "Accès refusé" toast (declarative <Navigate>
for already-authed users, Fragment-keyed admin user rows)
Deployment
- Single multistage Dockerfile (node:20-alpine → python:3.12-slim)
- docker/entrypoint.sh runs `flask db upgrade` before `flask run`
- Makefile: build/start/stop/restart/update/logs/create-admin/
update-mitre/test-{backend,frontend,e2e}/clean
- .env.example documenting MIMIC_JWT_SECRET / MIMIC_DB_PATH / MIMIC_PORT
- SQLite at /data/mimic.sqlite on named volume mimic-data
Acceptance suite (Playwright, 36 tests, all 27 ACs)
- e2e/ scaffold with playwright.config + auth/api fixtures
- One spec per user story (us1-bootstrap through us6-deployment)
- Portable via MIMIC_CONTAINER_CMD / MIMIC_BASE_URL (docker or podman)
Docs
- README.md with quick-start and architecture overview
- CHANGELOG.md updated with Sprint 1 deliverables
- pyrightconfig.json so the Python LSP sees backend/.venv and
resolves the `backend.app.*` absolute imports
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
101
frontend/tailwind.config.ts
Normal file
101
frontend/tailwind.config.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import type { Config } from 'tailwindcss';
|
||||
|
||||
/**
|
||||
* Tokens mirror DESIGN.md.
|
||||
* Forma DJR Micro substitut: Inter (bundled locally via @fontsource-variable/inter).
|
||||
*/
|
||||
const config: Config = {
|
||||
content: ['./index.html', './src/**/*.{ts,tsx}'],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
// Brand & Accent
|
||||
primary: {
|
||||
DEFAULT: '#024ad8',
|
||||
bright: '#296ef9',
|
||||
deep: '#0e3191',
|
||||
soft: '#c9e0fc',
|
||||
},
|
||||
// Surface
|
||||
canvas: '#ffffff',
|
||||
paper: '#ffffff',
|
||||
cloud: '#f7f7f7',
|
||||
fog: '#e8e8e8',
|
||||
steel: '#c2c2c2',
|
||||
hairline: '#e8e8e8',
|
||||
// Text
|
||||
ink: {
|
||||
DEFAULT: '#1a1a1a',
|
||||
deep: '#000000',
|
||||
soft: '#292929',
|
||||
on: '#ffffff',
|
||||
},
|
||||
charcoal: '#3d3d3d',
|
||||
graphite: '#636363',
|
||||
// Semantic / decorative
|
||||
bloom: {
|
||||
coral: '#ff5050',
|
||||
rose: '#f9d4d2',
|
||||
deep: '#b3262b',
|
||||
wine: '#5a1313',
|
||||
},
|
||||
storm: {
|
||||
mist: '#8ebdce',
|
||||
sea: '#7fadbe',
|
||||
deep: '#356373',
|
||||
},
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['"Inter Variable"', 'Inter', 'Arial', 'sans-serif'],
|
||||
},
|
||||
fontSize: {
|
||||
// DESIGN.md typography scale
|
||||
'display-xxl': ['72px', { lineHeight: '1.0', fontWeight: '500' }],
|
||||
'display-xl': ['56px', { lineHeight: '1.0', fontWeight: '500' }],
|
||||
'display-lg': ['44px', { lineHeight: '1.0', fontWeight: '500' }],
|
||||
'display-md': ['32px', { lineHeight: '1.0', fontWeight: '500' }],
|
||||
'display-sm': ['24px', { lineHeight: '1.17', fontWeight: '500' }],
|
||||
'display-xs': ['20px', { lineHeight: '1.0', fontWeight: '500' }],
|
||||
'body-lg': ['18px', { lineHeight: '1.33', fontWeight: '400' }],
|
||||
'body-md': ['16px', { lineHeight: '1.38', fontWeight: '400' }],
|
||||
'body-emphasis': ['16px', { lineHeight: '1.38', fontWeight: '500' }],
|
||||
'caption-md': ['14px', { lineHeight: '1.5', fontWeight: '400' }],
|
||||
'caption-bold': ['14px', { lineHeight: '1.3', fontWeight: '700' }],
|
||||
'caption-sm': ['12px', { lineHeight: '1.33', fontWeight: '400' }],
|
||||
'link-md': ['16px', { lineHeight: '1.38', fontWeight: '500' }],
|
||||
'button-md': ['14px', { lineHeight: '1.4', fontWeight: '600', letterSpacing: '0.7px' }],
|
||||
},
|
||||
spacing: {
|
||||
// DESIGN.md spacing tokens (named, complement Tailwind defaults)
|
||||
xxs: '4px',
|
||||
xs: '8px',
|
||||
sm: '12px',
|
||||
md: '16px',
|
||||
lg: '20px',
|
||||
xl: '24px',
|
||||
xxl: '32px',
|
||||
section: '80px',
|
||||
},
|
||||
borderRadius: {
|
||||
// DESIGN.md radius tokens
|
||||
none: '0px',
|
||||
xs: '2px',
|
||||
sm: '3px',
|
||||
md: '4px',
|
||||
lg: '8px',
|
||||
xl: '16px',
|
||||
pill: '9999px',
|
||||
},
|
||||
boxShadow: {
|
||||
'soft-lift': '0 2px 8px rgba(26, 26, 26, 0.08)',
|
||||
floating: '0 8px 24px rgba(26, 26, 26, 0.12)',
|
||||
},
|
||||
maxWidth: {
|
||||
page: '1366px',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
|
||||
export default config;
|
||||
Reference in New Issue
Block a user