102 lines
9.9 KiB
Markdown
102 lines
9.9 KiB
Markdown
# Strava Frontend Developer Instructions
|
|
|
|
You must strictly follow these architectural, coding, and formatting rules for this project (Vue 3 SPA "cycle-rider"). All generated code and modifications must fully comply with the guidelines below.
|
|
|
|
### 🧠 Agent Behavior
|
|
|
|
- **Reasoning Language**: All thoughts, analysis, and reasoning MUST be written in English to minimize token usage.
|
|
- **Communication**: Communicate with the user in Russian unless asked otherwise.
|
|
- **Task Progress**: Always track implementation progress with a todo list.
|
|
|
|
### 🏗 Project Structure (src/)
|
|
|
|
The SPA is organized as follows — keep the dependency direction `pages -> components / stores / services`:
|
|
|
|
- **`src/main.ts`** — app bootstrap: axios instances, Pinia, router, i18n, Vuestic UI, Yandex Maps, optional GTM. Do NOT relocate providers; components rely on `app.provide` keys.
|
|
- **`src/router/index.ts`** — single router config. All pages MUST be lazy-loaded via `() => import(...)`. Routes are nested under `AppLayout` (authenticated shell) or `AuthLayout` (auth pages); catch-all redirects to `explore`. Top-level routes (e.g. `workout_public_item` at `/public/workouts/:id`) are registered outside layouts for auth-free access + SSR alignment.
|
|
- **`src/pages/`** — route-level components and page-private logic. One folder per domain area (`workouts/`, `routes/`, `auth/`, `preferences/`, `admin/`). Page-private helper modules live in a `components/` subfolder of the page (e.g., `pages/workouts/components/GetWorkout.ts`).
|
|
- **`src/components/`** — reusable presentational components (`navbar/`, `sidebar/`, `icons/`, `app-layout-navigation/`). No direct API calls here; receive data via props.
|
|
- **`src/stores/`** — Pinia stores (options style via `defineStore`). `useUserStore` (user/profile data hydrated from `localStorage`), `useGlobalStore` (sidebar/UI state).
|
|
- **`src/i18n/`** — vue-i18n setup; locale files auto-discovered from `src/i18n/locales/*.json` (locale name = file name).
|
|
- **`src/services/`** — cross-cutting helpers: `utils.ts` (validators, sleep), `vuestic-ui/` (global config, themes, icon registry).
|
|
- **`src/scss/`** — global styles; do not duplicate Vuestic theme tokens — use `--va-*` CSS variables.
|
|
|
|
### 🌐 API Access Pattern
|
|
|
|
- Two Axios instances are created in `src/main.ts` and injected app-wide:
|
|
- `axiosAuth` — request interceptor attaches `Authorization: Bearer <localStorage.token>`;
|
|
- `axiosPublic` — no auth header.
|
|
- Consume them with `inject('axiosAuth') as AxiosInstance` / `inject('axiosPublic') as AxiosInstance`. Do NOT create new axios instances in components.
|
|
- All backend endpoints are under `/api/v0` (base URL = `HOST` constant in `main.ts`).
|
|
- A shared response interceptor handles 401 (clears `localStorage`, redirects to `login`). Do not add per-component 401 handling.
|
|
- Auth data lives in `localStorage` keys: `token`, `user`, `profile`, `attachments`. Keep key names stable — `useUserStore` and `main.ts` depend on them.
|
|
|
|
### 📊 Charts & Maps
|
|
|
|
- **Charts**: Chart.js 4 via `vue-chartjs`; adapters/plugins already installed (`chartjs-adapter-moment`, `chartjs-plugin-zoom`, `chartjs-chart-geo` for elevation). Chart-data building logic belongs in page-level `.ts` helper modules (e.g., `pages/workouts/components/*`), not in `.vue` templates.
|
|
- **Maps**: Yandex Maps via `vue-yandex-maps` (registered in `main.ts`). Route polylines are built from workout `results` coordinates `[longitude, latitude]`.
|
|
- Workout detail API response shape: `{ workout, results: [{ timestamp, longitude, latitude, elevation, power, heart_rate, speed }, ...] }`. Null-valued metrics (power/heart_rate/elevation) must be detected and their charts hidden, matching the existing pattern in `GetWorkout.ts`.
|
|
|
|
### 💾 State Management (Pinia)
|
|
|
|
- Use options-style stores (`defineStore("name", { state, actions })`) consistent with existing stores.
|
|
- Auth/profile state is hydrated from `localStorage` inside `state()` — there is no pinia-persist plugin. Do not add persistence of sensitive data beyond the existing keys.
|
|
- UI/visual state (sidebar, theme) belongs in `useGlobalStore`.
|
|
|
|
### 🎨 Styling
|
|
|
|
- Vuestic UI is the component library. Customize through `src/services/vuestic-ui/global-config.ts` and `themes.ts`, never by forking component internals.
|
|
- Tailwind CSS utilities are available; color tokens MUST reference Vuestic CSS variables (`var(--va-primary)` etc.) as mapped in `tailwind.config.js` (`primary`, `textPrimary`, `backgroundCardPrimary`, ...). Do not hardcode hex colors that bypass the theme.
|
|
- Font-size tokens: `text-tag`, `text-regularSmall`, `text-regularMedium`, `text-regularLarge` (defined in `tailwind.config.js`).
|
|
- Global styles live in `src/scss/main.scss`; page-specific overrides go in the page's `<style scoped>`.
|
|
- Custom Vuestic icons are registered in `src/services/vuestic-ui/icons-config/`.
|
|
|
|
### 🌍 i18n
|
|
|
|
- vue-i18n composition mode (`legacy: false`); default and fallback locale is `ru`.
|
|
- New translations: add the key to ALL locale files under `src/i18n/locales/` (`br, cn, es, gb, ir, ru`) — missing keys in any locale break the UI for that locale.
|
|
- In components use the `useI18n()` composable (`t()`).
|
|
- User-facing strings must go through i18n; exception: `validators` in `src/services/utils.ts` currently hardcode Russian messages — keep the existing style when extending that file.
|
|
|
|
### 📘 Coding Conventions (TypeScript / Vue)
|
|
|
|
- **TypeScript strict**: `tsconfig.json` has `strict: true` and the build runs `vue-tsc --noEmit` — type errors break the build. Mandatory type hints for all functions and props.
|
|
- **Modern types**: use built-in types (`Array`, `Record`, `| null` / `| undefined` unions). Avoid `any`; when unavoidable (e.g., axios error handling), keep it local and typed via type guards (`axios.isAxiosError`).
|
|
- **Composition API**: prefer `<script setup lang="ts">` for new components; do not force-convert existing Options API components unless the task requires it.
|
|
- **Naming**: PascalCase for components (`WorkoutItem.vue`), kebab-case for component usage in templates, camelCase for variables/functions, UPPER_SNAKE_CASE for constants.
|
|
- **Async style**: existing pages use `.then/.catch` chains — match the surrounding style in a file rather than mixing in async/await.
|
|
- **No forbidden debugging**: `console.log` / `debugger` statements are prohibited in committed code (the shared axios interceptor in `main.ts` is a known exception — do not add more).
|
|
- **No mutable module-level state** in helper `.ts` modules (the current `GetWorkout.ts` pattern of module-level `let` is tech debt — do NOT copy it; return state from functions instead).
|
|
- **Absolute imports**: use `../`-relative or `@/` paths consistently; the project uses relative imports from `src/` — keep it relative within `src`.
|
|
|
|
### 🧩 Component Guidelines
|
|
|
|
- Pages own data fetching and orchestration; presentational components receive props and emit events.
|
|
- Reusable logic (fetch + transform) belongs in a page-level `.ts` module exporting pure functions (see `GetWorkout.ts`, `LineWithLineChart.ts` as the pattern to follow — but without the module-level `let` mutation).
|
|
- Route meta and navigation: use named routes (`router.push({ name: "login" })`), never string paths for internal navigation.
|
|
|
|
### 📋 Page/Route Catalog (current)
|
|
|
|
| Route name | Path | Page | Notes |
|
|
| ----------------------------------------------------------------------------- | ---------------------- | -------------------------------------- | --------------------------------------- |
|
|
| `explore` | `/explore` | `pages/workouts/Feed.vue` | public workout feed, default landing |
|
|
| `routes` | `/routes` | `pages/routes/Route.vue` | routes listing |
|
|
| `list_workouts` | `/workouts` | `pages/workouts/WorkoutList.vue` | user's workouts |
|
|
| `upload_workouts` | `/workouts/upload` | `pages/workouts/WorkoutUpload.vue` | FIT/GPX upload |
|
|
| `workout_item` | `/workouts/:id` | `pages/workouts/WorkoutItem.vue` | private detail, charts + map |
|
|
| `workout_public_item` | `/public/workouts/:id` | `pages/workouts/WorkoutPublicItem.vue` | public detail (top-level, no auth, SSR) |
|
|
| `preferences` | `/preferences` | `pages/preferences/Preferences.vue` | profile settings, modals |
|
|
| `login` / `signup` / `logout` / `recover-password` / `recover-password-email` | `/auth/*` | `pages/auth/*` | AuthLayout pages |
|
|
| `404` | `/404` | `pages/404.vue` | error page |
|
|
|
|
> Legacy: `/dashboard` → redirects to `explore`.
|
|
|
|
### ⚠️ Environment & Secrets
|
|
|
|
- The API `HOST`, Yandex Maps API key, and GTM config live in `src/main.ts` / `import.meta.env` (`VITE_APP_GTM_ENABLED`, `VITE_APP_GTM_KEY`). Do not hardcode new secrets; use `import.meta.env.VITE_*` for anything environment-specific.
|
|
- Do not swap the production `HOST` unless the task explicitly requires local development.
|
|
|
|
### 🚀 Final Verification
|
|
|
|
When working in agent mode, always run `yarn lint` and `yarn build` as the final step before completing the task (build includes `vue-tsc --noEmit` type-check). There is NO test runner in this repo — do not invent test commands.
|