6.2 KiB
6.2 KiB
System Patterns — Strava Frontend
Architecture (SPA layering)
src/
main.ts - app bootstrap, axios instances, providers
App.vue - root component
router/ - vue-router config (lazy pages)
layouts/ - AppLayout (authenticated shell), AuthLayout, RouterBypass
pages/ - route components (workouts/, routes/, auth/, preferences/, admin/)
components/ - reusable UI (navbar/, sidebar/, icons/, app-layout-navigation/)
stores/ - Pinia stores (user-store, global-store)
i18n/ - vue-i18n setup + locales/*.json
services/ - axios helpers, vuestic-ui config (global-config, themes)
scss/ - global styles, vuestic-sass, icon fonts
Dependency direction: pages -> components/stores/services. Pages own business logic; components are presentational.
API access pattern
- Two Axios instances created in
src/main.ts:axiosAuth(addsAuthorization: Bearer <token>fromlocalStorageon every request) andaxiosPublic. Both have a shared response error interceptor: 401 ->localStorage.clear()+ redirect tologinroute. - Instances are provided app-wide:
app.provide('axiosAuth', ...)/app.provide('axiosPublic', ...). Components consume withinject('axiosAuth') as AxiosInstance. - Base URL is a hardcoded
HOSTconstant insrc/main.ts(https://cycle-rider.ru; localhost variant commented out). All backend paths are under/api/v0. - Auth check on startup: if
localStorage.tokenexists, GET/api/v0/auth/check; on failure clear storage.
Data fetching pattern
- No central API layer: components/composable
.tsfiles (e.g.src/pages/workouts/components/GetWorkout.ts) callaxiosAuth.get(url)directly and map the response into view state. - Workout detail data shape:
{ workout, results: [{ timestamp, longitude, latitude, elevation, power, heart_rate, speed }, ...] }— mapped into Chart.js datasets + map line coordinates.
State management
- Pinia with options-style stores.
useUserStorehydrates fromlocalStoragekeysuser,profile,attachmentson store init (no persistence plugin).useGlobalStoreholds sidebar state. - Auth data lives in
localStorage(keys:token,user,profile,attachments).
Routing
- History-mode router (
createWebHistory), all pages lazy-imported. - Two layouts:
AppLayout(sidebar + navbar shell) for authenticated pages;AuthLayoutfor auth pages. Catch-all redirects todashboard(=pages/workouts/Feed.vue). - No global auth guard — unauthenticated handling is done via the 401 interceptor.
Charts & maps
- Chart.js via
vue-chartjs(+chartjs-adapter-moment,chartjs-plugin-zoom,chartjs-chart-geofor elevation). Chart building lives insrc/pages/workouts/components/*.ts. - Yandex Maps via
vue-yandex-maps(createYmapswith a hardcoded API key inmain.ts). Route polyline drawn from workoutresultscoordinates. - Typing contract (since vue 3.5 strict checks):
vue-yandex-maps:settingscoordinates must be theLngLat = [lon, lat, alt?]tuple type — plainnumber[]refs failvue-tsc. Pattern:ref<LngLat>([lon, lat])+as LngLatcasts in templates (seepages/routes/Route.vue,pages/workouts/components/WorkoutItem.vue). - Custom chart controllers extend
chart.jscontrollers (LineWithLineControllerinLineWithLineChart.ts); the map-sync plugin is read fromchart.config.pluginsby id — use non-null assertions onchart.config.data!(do NOT reintroduce@ts-ignore, lint bans it).
Styling
- Vuestic UI (config in
src/services/vuestic-ui/global-config.ts, themes inthemes.ts, custom icons registered inicons-config/). - Tailwind CSS with Vuestic CSS variables as color tokens (
--va-primaryetc. intailwind.config.js). Custom font-size tokens:tag,regularSmall/Medium/Large. - Global SCSS in
src/scss/main.scss; icon fonts insrc/scss/icon-fonts/.
i18n
vue-i18nin composition mode (legacy: false), locale and fallback =ru. Locales auto-loaded fromsrc/i18n/locales/*.jsonviaimport.meta.glob+@intlify/unplugin-vue-i18n/viteplugin.
Error handling
- Central axios interceptor logs and re-throws; 401 triggers logout redirect. Components typically use
.then/.catchchains (callback style, not async/await) — keep the existing style when modifying.
Lint / type-check pipeline
- Flat ESLint config (
eslint.config.mjs, eslint 9 + typescript-eslint 8):tseslint.configs.recommended+pluginVue.configs["flat/essential"]. CRITICAL: the vue flat preset leaves espree as inner parser — the TS parser MUST be attached for**/*.vuevialanguageOptions.parserOptions.parser(from thetypescript-eslintCJS default import). - Override style: file-targeted blocks with justifying comments ONLY (no global rule disabling, no inline eslint-disable). Existing exceptions:
src/main.ts(any/unused — interceptor debt),src/pages/**/*.vue(multi-word names),Logout.vue(valid-template-root),workouts/components/WorkoutItem.vue(no-mutating-props — local-state extraction pending). ban-ts-commentis enforced:@ts-ignoreis not allowed; use@ts-expect-erroror proper typing (non-null assertions).no-unused-varshonorsargsIgnorePattern: "^_".yarn lintrunsprelint: prettier --write .— running lint auto-reformats ~30 files; commit the formatting as part of the change.
Build / deploy
- Vite 5 build with
vue-tsc 2 --noEmittype-check inyarn build. Dev and Docker both use yarn v1 (Docker:node:22,COPY package.json yarn.lock,yarn install --frozen-lockfile). nginx.conffor static serving in the image;run.shlauncher;serve -s ./distfor CI preview.- ⚠️ Legacy
package-lock.jsonis STALE and unused (Docker no longer copies it) — do not runnpm installin this repo; removal is a separate cleanup task. - Version ceilings (learned the hard way): vue-tsc 2.x supports TypeScript up to 5.8 — TS 5.9 crashes it (
Search string not found: "supportedTSExtensions"); pin"typescript": "5.8"no-caret. sass ≥1.7x latest requires node ≥20.19 — Docker (node:22) is fine; verify local dev node before bumping. - yarn 1.22 has no
upalias — useyarn upgrade.