167 lines
5.5 KiB
Markdown
167 lines
5.5 KiB
Markdown
# Smash or Pass — Frontend
|
||
|
||
Vue 3 + TypeScript + Vite + Pinia + Tailwind CSS v4. Talks to the [`sop-back`](../sop-back/) FastAPI server.
|
||
|
||
See [`../CLAUDE.md`](../CLAUDE.md) for full architecture notes.
|
||
|
||
---
|
||
|
||
## Stack
|
||
|
||
- Vue 3 with `<script setup lang="ts">` (TypeScript everywhere)
|
||
- Vite 8 (dev server + bundler)
|
||
- Vue Router (SPA: `/`, `/game/:id`, `/summary`)
|
||
- Pinia (game state: queue / smashed / passed)
|
||
- Tailwind CSS v4 via `@tailwindcss/vite`. Custom palette + fonts in [`src/assets/main.css`](src/assets/main.css) inside an `@theme { ... }` block.
|
||
|
||
---
|
||
|
||
## Project layout
|
||
|
||
```
|
||
sop-front/
|
||
├── index.html
|
||
├── vite.config.ts # Tailwind v4 plugin + alias '@'
|
||
├── nginx.conf # SPA fallback for production image
|
||
├── Dockerfile # multi-stage: vite build → nginx
|
||
└── src/
|
||
├── main.ts # imports ./assets/main.css
|
||
├── App.vue # only <RouterView />
|
||
├── router/index.ts
|
||
├── api/client.ts # typed fetch wrapper, reads VITE_API_BASE_URL
|
||
├── stores/game.ts # Pinia store
|
||
├── assets/main.css # Tailwind + @theme tokens (palette, fonts)
|
||
├── components/
|
||
│ ├── SwipeCard.vue # pointer-event drag, exposes triggerSmash/Pass
|
||
│ └── AdminPanel.vue
|
||
└── views/
|
||
├── HomeView.vue
|
||
├── GameView.vue
|
||
└── SummaryView.vue
|
||
```
|
||
|
||
---
|
||
|
||
## Configuration
|
||
|
||
One environment variable, **build-time** (Vite inlines it):
|
||
|
||
| Var | Default | Notes |
|
||
|---|---|---|
|
||
| `VITE_API_BASE_URL` | `http://localhost:8000` | Backend origin |
|
||
|
||
For dev, set in a `.env.local`:
|
||
|
||
```
|
||
VITE_API_BASE_URL=http://localhost:8000
|
||
```
|
||
|
||
For Docker builds, pass it as a build arg (already wired in the repo-root `docker-compose.yml`):
|
||
|
||
```yaml
|
||
frontend:
|
||
build:
|
||
context: ./sop-front
|
||
args:
|
||
VITE_API_BASE_URL: "https://api.example.com"
|
||
```
|
||
|
||
---
|
||
|
||
## Local development
|
||
|
||
```bash
|
||
cd sop-front
|
||
npm install
|
||
npm run dev
|
||
```
|
||
|
||
- App: http://localhost:5173
|
||
- Make sure the backend is running at `VITE_API_BASE_URL` (default `http://localhost:8000`).
|
||
|
||
### Other scripts
|
||
|
||
| Command | What |
|
||
|---|---|
|
||
| `npm run dev` | Vite dev server with HMR |
|
||
| `npm run build` | Type-check + production build into `dist/` |
|
||
| `npm run type-check` | `vue-tsc --build` only |
|
||
| `npm run preview` | Serve the built `dist/` locally |
|
||
| `npm run lint` | oxlint + eslint |
|
||
| `npm run format` | Prettier |
|
||
| `npm run test:unit` | Vitest |
|
||
|
||
---
|
||
|
||
## Customizing colors / fonts
|
||
|
||
**All design tokens live in [`src/assets/main.css`](src/assets/main.css)** under `@theme`. The palette is **graded** (Tailwind-style): each hue has a 50–900 scale, with **500** as the canonical brand value. Add new shades there; don't hardcode hex values in components.
|
||
|
||
### Scales (`50` lightest → `900/950` darkest)
|
||
|
||
| Family | 500 (brand) | Use |
|
||
|---|---|---|
|
||
| `--color-purple-{50…900}` | `#8324DE` | Primary brand |
|
||
| `--color-red-{50…900}` | `#FF453B` | Pass / errors / destructive |
|
||
| `--color-lime-{50…900}` | `#B9D532` | Smash / success |
|
||
| `--color-neutral-{50…950}` | `#63586E` | Backgrounds, surfaces, text, borders |
|
||
|
||
Components reference shades like `var(--color-purple-300)`, `var(--color-neutral-800)`, etc. The neutral scale handles dark-mode surfaces (`50` = off-white text, `950` = `#16141A` background).
|
||
|
||
### Semantic aliases (prefer these in components)
|
||
|
||
These point at scale values — change them once in `main.css` to retheme:
|
||
|
||
| Alias | → Scale | Role |
|
||
|---|---|---|
|
||
| `--color-primary` | `purple-500` | CTAs / brand |
|
||
| `--color-primary-hover` | `purple-400` | CTA hover |
|
||
| `--color-smash` | `lime-500` | Smash action |
|
||
| `--color-pass` | `red-500` | Pass action |
|
||
| `--color-bg` | `neutral-950` | App background |
|
||
| `--color-surface` | `neutral-900` | Card surface |
|
||
| `--color-surface-2` | `neutral-800` | Elevated surface |
|
||
| `--color-border` | `neutral-700` | Borders |
|
||
| `--color-text` | `neutral-50` | Primary text |
|
||
| `--color-text-muted` | `neutral-300` | Secondary text |
|
||
|
||
Fonts: **Bebas Neue** (display) + **Roboto** (body), loaded via Google Fonts in `main.css`.
|
||
|
||
---
|
||
|
||
## Production deployment
|
||
|
||
### Docker (recommended)
|
||
|
||
A multi-stage Dockerfile builds the SPA with Vite and serves the static bundle with nginx. From the repo root:
|
||
|
||
```bash
|
||
docker compose up -d --build frontend
|
||
```
|
||
|
||
The image listens on port 80 inside the container; the compose file maps it to host `:8080`.
|
||
|
||
For a real deployment:
|
||
|
||
1. **Set `VITE_API_BASE_URL`** to the public backend URL **at build time** (build arg, not runtime env). Rebuild the image whenever it changes.
|
||
2. **Front it with HTTPS** — Caddy / Traefik / Cloudflare in front of port 80.
|
||
3. The bundled `nginx.conf` already does SPA fallback (`try_files $uri /index.html`).
|
||
4. Make sure the backend CORS `ALLOWED_ORIGINS` includes the frontend's public origin.
|
||
|
||
### Static hosting (no Docker)
|
||
|
||
```bash
|
||
npm install
|
||
VITE_API_BASE_URL=https://api.example.com npm run build
|
||
```
|
||
|
||
Upload the contents of `dist/` to any static host (S3+CloudFront, Netlify, Vercel, GitHub Pages, etc.). Configure SPA fallback so any unknown path serves `index.html`.
|
||
|
||
---
|
||
|
||
## Notes
|
||
|
||
- `VITE_API_BASE_URL` is **baked into the JS bundle** at build time. Changing it requires a rebuild.
|
||
- All API calls go through [`src/api/client.ts`](src/api/client.ts) — extend that wrapper rather than calling `fetch` directly from components.
|
||
- The admin panel renders only when `GET /admin/status` returns `admin_enabled: true`. That flag lives in the **backend's** `.env`.
|