navbar after login
Gitea Actions Demo / build_and_push (push) Successful in 1m19s
Details
Gitea Actions Demo / build_and_push (push) Successful in 1m19s
Details
This commit is contained in:
parent
84cd573695
commit
a44267cea8
|
|
@ -2,93 +2,77 @@
|
||||||
|
|
||||||
## Task State
|
## Task State
|
||||||
|
|
||||||
- task_id: TASK-SSR-DETAIL
|
- task_id: TASK-MCP-SETUP
|
||||||
- status: success
|
- status: success
|
||||||
- parent_task: TASK-SSR-2
|
- parent_task: —
|
||||||
- summary: **SSR /public/workouts/:id — HTML-контент переписан под DOM-структуру WorkoutItem.vue (вёрстка + фото + OG:image + noscript).**
|
- summary: **Настроить Playwright MCP для UI-верификации через браузер. Конфиг Zoo Code обновлён (Node 22), Chromium установлен.**
|
||||||
- next task: — (no active tasks)
|
- next task: —
|
||||||
|
|
||||||
### TASK-F12/F13/F14 — фиксы карты тренировки (WorkoutItem.vue)
|
## TASK-MCP-SETUP: Playwright MCP configuration
|
||||||
|
|
||||||
- Ф12: `position="top-center left-center"` (центрирование маркера на координате); `findNearestTrackIndex` (радиус 40 м) вместо точного lookup `getKey`/`coordWithIndex`.
|
**Что сделано:**
|
||||||
- Ф13: стабильные `:settings` фото-маркеров через Map-кэш (`getPhotoMarkerSettings`) — одного этого оказалось НЕДОСТАТОЧНО (Vue патчит VNode-ы и при неизменённых пропсах).
|
- MCP-конфиг Zoo Code (`mcp_settings.json`): server `web-browser` → `@playwright/mcp@latest`, Node 22 via nvm absolute path, `--headless`
|
||||||
- Ф14: `v-memo="[photo.id]"` на фото-маркерах — жёсткий пропуск патча, `onUpdated`/`clearElement()` больше не срабатывают. Методология и причины записаны в `systemPatterns.md` (секция «Charts & maps»).
|
- Chromium `1234` уже установлен в `~/.cache/ms-playwright/`
|
||||||
|
- `.mcp.json` создан в проекте (project-level, для совместимости)
|
||||||
|
- `techContext.md` обновлён: секция "UI Verification (MCP Browser)"
|
||||||
|
|
||||||
## TASK-404: HTTP 404 для несуществующих страниц (SEO)
|
**Как использовать (workflow):**
|
||||||
|
1. `yarn dev` — запустить dev server (порт 5173)
|
||||||
|
2. `browser_navigate` → `http://localhost:5173/explore` — проверить публичную страницу
|
||||||
|
3. `browser_evaluate` → `() => { localStorage.setItem('token', 'test'); localStorage.setItem('user', JSON.stringify({id:1,name:'Test'})); location.reload(); }` — сымитировать auth
|
||||||
|
4. `browser_navigate` → проверить auth-страницы (`/workouts`, `/workouts/:id`)
|
||||||
|
5. `browser_snapshot` — проверить DOM-структуру
|
||||||
|
6. `browser_take_screenshot` — визуальная проверка
|
||||||
|
7. `browser_network_requests` — проверить API-запросы
|
||||||
|
|
||||||
### Проблема
|
### Баг (контекст)
|
||||||
|
|
||||||
Любой неизвестный URL (например `/random-page`) отдаёт `200` + redirect на `/explore`. Поисковик путается.
|
Сайдбар показывает `publicRoutes` (Лента + Маршрут) после логина до F5. Корень: [`NavigationRoutes.ts:70`](src/components/sidebar/NavigationRoutes.ts:70) — `routes` вычисляется один раз при module evaluation и кэшируется. TASK-AUTH-NAV-1 уже добавил реактивный `isAuthenticated` в `useGlobalStore` (инициализация из localStorage + `setAuthenticated` в Login/Logout). Эта задача переключает UI на computed.
|
||||||
|
|
||||||
### Решение
|
### TASK-AUTH-NAV-2: реактивные маршруты (3 файла)
|
||||||
|
|
||||||
1. `server/index.ts` — catch-all 404 handler: `app.use((req, res) => { if (req.method === "GET") res.status(404).send(renderTemplate(...)) })` с SEO-мета (title: "404 — страница не найдена — Cycle Rider", noindex meta).
|
**1. `src/components/sidebar/NavigationRoutes.ts`**
|
||||||
2. `nginx.conf` — whitelist SPA-роутов (`/workouts`, `/auth`, `/preferences`, `/404`) → `try_files $uri /index.html`; всё остальное (кроме SSR-локаций) → `proxy_pass http://127.0.0.1:3001`.
|
|
||||||
3. `src/router/index.ts` — catch-all `/:pathMatch(.*)*` → redirect на `{ name: "404" }` (вместо `explore`).
|
|
||||||
4. `src/router/seo.ts` — запись `404: { title: "404 — страница не найдена — Cycle Rider", description: "..." }` в SEO_MAP.
|
|
||||||
|
|
||||||
### Acceptance Criteria
|
- Сделать named exports: `export const authRoutes: INavigationRoute[]` и `export const publicRoutes: INavigationRoute[]` (сейчас это локальные `const`, не экспортированные).
|
||||||
|
- Убрать статическое поле `routes` из default export (или оставить default без `routes`, т.к. оба потребителя перейдут на computed). `INavigationRoute` интерфейс — не трогать.
|
||||||
|
- Важно: массивы `authRoutes`/`publicRoutes` остаются теми же по структуре/содержимому — только экспортируются.
|
||||||
|
|
||||||
- `curl -s -o /dev/null -w "%{http_code}" http://localhost:80/random-path` → `404`
|
**2. `src/components/sidebar/AppSidebar.vue`** (Options API, `setup()`)
|
||||||
- `curl -s http://localhost:80/random-path | grep -c '404'` → > 0
|
|
||||||
- `curl -s -o /dev/null -w "%{http_code}" http://localhost:80/workouts` → `200`
|
|
||||||
- `curl -s -o /dev/null -w "%{http_code}" http://localhost:80/auth/login` → `200`
|
|
||||||
- `curl -s -o /dev/null -w "%{http_code}" http://localhost:80/explore` → `200`
|
|
||||||
- `yarn lint` → 0, `yarn build` → 0
|
|
||||||
|
|
||||||
## ✅ CODER SUCCESS REPORT — TASK-404
|
- `import { useGlobalStore } from "../../stores/global-store";` + `import { authRoutes, publicRoutes } from "./NavigationRoutes";` (импорт `navigationRoutes` по default можно оставить/убрать — но все обращения `navigationRoutes.routes` заменить).
|
||||||
|
- В `setup()`: `const globalStore = useGlobalStore();`
|
||||||
|
- computed: `const routes = computed(() => globalStore.isAuthenticated ? authRoutes : publicRoutes);`
|
||||||
|
- В `return` заменить `navigationRoutes` на `routes` (тепловой шаблон уже использует `navigationRoutes.routes` → поправить на `routes.value`? НЕТ — в Options API computed из setup доступно в шаблоне как `routes` без `.value`, т.е. в шаблоне `navigationRoutes.routes` → `routes`).
|
||||||
|
- `setActiveExpand`: `navigationRoutes.routes.map(...)` → `routes.value.map(...)` (в setup `.value` нужен).
|
||||||
|
- Шаблоны: `v-for="(route, index) in navigationRoutes.routes"` → `in routes`.
|
||||||
|
|
||||||
- `server/index.ts` — добавлен catch-all 404 handler (`app.use(...)` без path) перед `app.listen`: для GET — `renderTemplate` с SEO-мета (title: "404 — Страница не найдена — Cycle Rider", canonical `/404`), для остальных методов — `res.status(404).send("Not Found")`.
|
**3. `src/components/app-layout-navigation/AppLayoutNavigation.vue`** (script setup)
|
||||||
- `nginx.conf` — реорганизация: добавлен SPA whitelist `~ ^/(workouts|auth|preferences|404)(/|$)` → `try_files $uri /index.html`; старое `location /` (SPA fallback) заменено на catch-all → `proxy_pass http://127.0.0.1:3001` (SSR вернёт 404 для неизвестных путей).
|
|
||||||
- `src/router/index.ts` — catch-all `/:pathMatch(.*)*` redirect: `{ name: "explore" }` → `{ name: "404" }`.
|
|
||||||
- `src/router/seo.ts` — добавлена запись `"404"` в `SEO_MAP` (title + description).
|
|
||||||
- Verified: `yarn lint` exit 0, `yarn build` exit 0 (vue-tsc 0 errors, vite build ✓ 7.02s).
|
|
||||||
|
|
||||||
## ✅ CODER SUCCESS REPORT — TASK-F11
|
- `import { authRoutes, publicRoutes } from "../sidebar/NavigationRoutes";` + `import { useGlobalStore } from "../../stores/global-store";`
|
||||||
|
- `const globalStore = useGlobalStore();`
|
||||||
|
- `findRouteName` (строка ~64): `traverse(NavigationRoutes.routes)` → `traverse(globalStore.isAuthenticated ? authRoutes : publicRoutes)`.
|
||||||
|
- Убедиться, что `items` computed (зависит от `route.matched` и `findRouteName`) остаётся реактивным — т.к. `findRouteName` читает `globalStore.isAuthenticated` внутри `items` computed, реactivity сохранится.
|
||||||
|
|
||||||
- `package.json` — `@unhead/vue` ^3.4.1 в dependencies (установлено через `npm install @unhead/vue --legacy-peer-deps`; peer-конфликт vite 4 vs vite 5 разрешён legacy-flag'ом; vite в project поднят до 5.4.21 транзитивно).
|
### Acceptance Criteria (TASK-AUTH-NAV-2)
|
||||||
- Создан `src/router/seo.ts` — `setupSeo(router, head: Unhead)`: SEO_MAP по name роута + DEFAULT_SEO, `head.push({title, meta:[description]})` + `router.afterEach(() => update())`.
|
|
||||||
- `src/main.ts` — `import { createHead } from "unhead/client"` + `import { headSymbol } from "@unhead/vue"`; `const head = createHead(); app.provide(headSymbol, head); setupSeo(router, head);` (после `app.use(router)`).
|
|
||||||
- `index.html` — `<title>` заменён на «Cycle Rider — платформа для анализа велотренировок: ...»; добавлен `<meta name="description">` в `<head>` как fallback; блок Yandex.Metrika перенесён из `<head>` в начало `<body>` (Vite 5 parse5 запрещает `<div>/<img>` внутри `<noscript>` в `<head>` — `disallowed-content-in-noscript-in-head`).
|
|
||||||
- Verified: `npm run build` exit 0 (vue-tsc 0 ошибок, vite build ✓ 6.65s; warning chunk >500kB — предсуществующий); `npm run lint` exit 0.
|
|
||||||
|
|
||||||
## ✅ CODER SUCCESS REPORT — TASK-PUBLIC-ROUTE-FIX
|
1. `yarn lint` → 0 ошибок.
|
||||||
|
2. `yarn build` → 0 ошибок (vue-tsc --noEmit).
|
||||||
|
3. `NavigationRoutes.ts`: `authRoutes` и `publicRoutes` экспортируются; статического `routes` с localStorage-тернарником больше нет.
|
||||||
|
4. `AppSidebar.vue`: routes = computed от `globalStore.isAuthenticated`; `setActiveExpand` использует `routes.value`.
|
||||||
|
5. `AppLayoutNavigation.vue`: `traverse` использует выбор по `globalStore.isAuthenticated`.
|
||||||
|
6. **Ручной сценарий (ключевой)**: без F5 — выход из профиля → `/explore` → логин → сайдбар **сразу** показывает «Мои тренировки» (authRoutes). Аут → сайдбар сразу `publicRoutes`.
|
||||||
|
|
||||||
- Изменён файл (единственный): `src/router/index.ts`:
|
## ✅ CODER SUCCESS REPORT — TASK-AUTH-NAV-2
|
||||||
- Удалён вложенный `workout_public_item` из `children` маршрута `workouts` (бывшие строки 50–54: `path: "public/workouts/:id"`).
|
|
||||||
- Добавлен top-level роут `{ name: "workout_public_item", path: "/public/workouts/:id", component: () => import("../pages/workouts/WorkoutPublicItem.vue") }` — зарегистрирован **до** catch-all `/:pathMatch(.*)*`, вне `AppLayout` (без auth-guard'а).
|
|
||||||
- Поведение: URL `/public/workouts/:id` теперь доступен без авторизации (не обёрнут в `AppLayout`), совпадает с SSR-эндпоинтом, nginx-прокси `location ~ ^/public/workouts/` и sitemap. Внутренняя навигация по `name: "workout_public_item"` (см. `Feed.vue`) не тронута — имя не менялось.
|
|
||||||
- `WorkoutPublicItem.vue` / `WorkoutItem.vue`: проверено — `inject("axiosAuth")` работает глобально (app.provide в `main.ts`), от AppLayout не зависит; auth-check'ов от вложенности не найдено.
|
|
||||||
- `yarn lint`: exit 0 (prettier: все файлы unchanged, eslint 0 ошибок).
|
|
||||||
- `yarn build`: exit 0 (vue-tsc --noEmit 0 ошибок, vite build ✓ 6.95s; warning о chunk >500kB — предсуществующий).
|
|
||||||
|
|
||||||
## ✅ CODER SUCCESS REPORT — TASK-SSR-3
|
- `src/components/sidebar/NavigationRoutes.ts`: `export const authRoutes` / `export const publicRoutes`; удалён статический `routes: localStorage.getItem("token") ? ...` из default export (оставлен только `root`).
|
||||||
|
- `src/components/sidebar/AppSidebar.vue`: импорт `authRoutes, publicRoutes` + `useGlobalStore`; `computed(() => globalStore.isAuthenticated ? authRoutes : publicRoutes)`; `setActiveExpand` использует `routes.value.map`; шаблон `v-for="(route, index) in routes"`.
|
||||||
|
- `src/components/app-layout-navigation/AppLayoutNavigation.vue`: `const globalStore = useGlobalStore()`; `traverse(globalStore.isAuthenticated ? authRoutes : publicRoutes)` внутри `findRouteName` (вызывается в `items` computed → реactivity сохранена).
|
||||||
|
- Verified: `yarn lint` exit 0, `yarn build` exit 0 (vue-tsc --noEmit 0 ошибок, vite ✓ 7.07s).
|
||||||
|
|
||||||
- Изменённые файлы:
|
## История (закрытые задачи)
|
||||||
- `server/index.ts` — добавлены `GET /sitemap.xml` и `GET /robots.txt`.
|
|
||||||
- `nginx.conf` — полная замена: SSR proxy, cookie-based landing, assets cache, SPA fallback.
|
|
||||||
- `Dockerfile` — multi-stage: node:20-alpine build → runtime (nginx + tsx server).
|
|
||||||
- `run.sh` — `npx tsx server/index.ts & nginx -g 'daemon off;'`.
|
|
||||||
- `package.json` — `tsx` → dependencies.
|
|
||||||
- `src/stores/user-store.ts` — `typeof window` guard для SSR.
|
|
||||||
- `src/main.ts` — `typeof window` guard перед `localStorage`.
|
|
||||||
- Verified: lint 0, build 0, curl /robots.txt 200, /sitemap.xml 200, /health ok.
|
|
||||||
|
|
||||||
## ✅ CODER SUCCESS REPORT — TASK-SSR-2
|
- **TASK-AUTH-NAV-1**: success — `isAuthenticated` + `setAuthenticated` в `useGlobalStore`; Login `setAuthenticated(true)`, Logout `setAuthenticated(false)`. Verified lint/build 0.
|
||||||
|
- TASK-SSR-DETAIL: success — SSR /public/workouts/:id под DOM WorkoutItem.vue.
|
||||||
- `server/index.ts` — 4 SSR route handlers + `app.disable("x-powered-by")`: `/`, `/explore`, `/public/workouts/:id`, `/routes`.
|
- TASK-404: success — HTTP 404 для неизвестных страниц.
|
||||||
- Helpers: `getAssetTagsSafe()`, `escapeHtml`, `formatDate`, `formatDuration`, `formatDistance`, `formatSpeed`, `getToken`.
|
- TASK-PUBLIC-ROUTE-FIX: success — workout_public_item в top-level router.
|
||||||
- Canonical base: `SSR_BASE_URL` || `https://cycle-rider.ru`.
|
- TASK-F11/F12/F13/F14: success — SEO + фиксы карты тренировки.
|
||||||
- Verified: lint 0, build 0, curl all routes OK.
|
|
||||||
|
|
||||||
## ✅ CODER SUCCESS REPORT — TASK-SSR-1
|
|
||||||
|
|
||||||
- Created `server/index.ts`, `server/template.ts`, `server/api.ts`.
|
|
||||||
- `package.json`: +express, +@types/express, +tsx, +3 server scripts.
|
|
||||||
- Verified: lint 0, build 0, curl /health OK.
|
|
||||||
|
|
||||||
## ✅ CODER SUCCESS REPORT — TASK-SSR-0
|
|
||||||
|
|
||||||
- `src/router/index.ts`: `dashboard` → `explore` (name + path), legacy redirect `/dashboard` → `explore`.
|
|
||||||
- `NavigationRoutes.ts`, `AppLayoutNavigation.vue`, `AppLayout.vue`, `Login.vue`, `Signup.vue`, `CheckTheEmail.vue` — все ссылки `dashboard` → `explore`.
|
|
||||||
- Verified: lint 0, build 0.
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,18 @@
|
||||||
# Progress — Strava Frontend
|
# Progress — Strava Frontend
|
||||||
|
|
||||||
|
## 2026-09-24 — TASK-AUTH-NAV-2: реактивные маршруты в сайдбаре и хлебных крошках
|
||||||
|
|
||||||
|
- `NavigationRoutes.ts`: named exports `authRoutes`/`publicRoutes`, удалён module-level `localStorage.getItem("token")` тернарник.
|
||||||
|
- `AppSidebar.vue`: computed `routes` от `globalStore.isAuthenticated`; `AppLayoutNavigation.vue`: `traverse` по `globalStore.isAuthenticated ? authRoutes : publicRoutes`.
|
||||||
|
- Verified: `yarn lint` exit 0, `yarn build` exit 0 (vue-tsc 0 ошибок).
|
||||||
|
|
||||||
|
## 2026-09-24 — TASK-AUTH-NAV-1: auth-состояние в Pinia (реактивный isAuthenticated)
|
||||||
|
|
||||||
|
- `src/stores/global-store.ts` — state: `isAuthenticated` (SSR-guard `typeof window !== "undefined"`), action `setAuthenticated(v: boolean)`.
|
||||||
|
- `src/pages/auth/Login.vue` — `import { useGlobalStore }`; в `.then` после `localStorage.setItem(...)` и до `push({ name: "explore" })`: `globalStore.setAuthenticated(true)`.
|
||||||
|
- `src/pages/auth/Logout.vue` — `import { useGlobalStore }`; после `localStorage.clear()` и до `push({ name: "login" })`: `globalStore.setAuthenticated(false)`.
|
||||||
|
- Verified: `yarn lint` exit 0, `yarn build` exit 0 (vue-tsc 0 errors, vite ✓ 7.17s).
|
||||||
|
|
||||||
## 2026-09-19 — TASK-404: HTTP 404 для несуществующих страниц (SEO)
|
## 2026-09-19 — TASK-404: HTTP 404 для несуществующих страниц (SEO)
|
||||||
|
|
||||||
- `server/index.ts` — catch-all 404 handler (`app.use` без path, перед `app.listen`): GET → `renderTemplate` (SEO-мета, canonical `/404`), non-GET → `res.status(404).send("Not Found")`.
|
- `server/index.ts` — catch-all 404 handler (`app.use` без path, перед `app.listen`): GET → `renderTemplate` (SEO-мета, canonical `/404`), non-GET → `res.status(404).send("Not Found")`.
|
||||||
|
|
|
||||||
|
|
@ -42,8 +42,17 @@
|
||||||
- Deploy: Dockerfile (**multi-stage, node:20-alpine**: build stage + runtime stage with nginx) + `nginx.conf` (SSR proxy + SPA fallback); `run.sh` local runner.
|
- Deploy: Dockerfile (**multi-stage, node:20-alpine**: build stage + runtime stage with nginx) + `nginx.conf` (SSR proxy + SPA fallback); `run.sh` local runner.
|
||||||
- Dev environment requires **Node ≥18.19** (vite 5); Docker uses node:20-alpine.
|
- Dev environment requires **Node ≥18.19** (vite 5); Docker uses node:20-alpine.
|
||||||
|
|
||||||
|
## UI Verification (MCP Browser)
|
||||||
|
|
||||||
|
- **Playwright MCP** (`@playwright/mcp`) configured in Zoo Code MCP settings (`mcp_settings.json`) as server `web-browser`.
|
||||||
|
- Browser: Chromium (headless), already installed at `~/.cache/ms-playwright/chromium-1234`.
|
||||||
|
- Node 22 required (via nvm: `~/.nvm/versions/node/v22.14.0/`). MCP config uses absolute path to `npx`.
|
||||||
|
- Tools available: `browser_navigate`, `browser_snapshot`, `browser_click`, `browser_fill_form`, `browser_take_screenshot`, `browser_evaluate`, `browser_network_requests`, etc.
|
||||||
|
- Usage: after `yarn build` passes, launch dev server (`yarn dev`) and use MCP browser tools to verify UI routes and interactions.
|
||||||
|
- Auth: set `localStorage.token` / `localStorage.user` via `browser_evaluate` before navigating to auth-required pages.
|
||||||
|
|
||||||
## Constraints
|
## Constraints
|
||||||
|
|
||||||
- No test framework configured (no vitest/jest/cypress). Verification = `yarn lint` + `yarn build`.
|
- No test framework configured (no vitest/jest/cypress). Verification = `yarn lint` + `yarn build` + MCP browser spot-checks.
|
||||||
- Backend API contract: `/api/v0/*`, JWT Bearer auth, error envelope may be `{ code, message, detail }` (see backend `app/web/errors.py`).
|
- Backend API contract: `/api/v0/*`, JWT Bearer auth, error envelope may be `{ code, message, detail }` (see backend `app/web/errors.py`).
|
||||||
- Keep `vue-tsc` clean — build fails on type errors.
|
- Keep `vue-tsc` clean — build fails on type errors.
|
||||||
|
|
|
||||||
|
|
@ -29,11 +29,14 @@ import { useColors } from "vuestic-ui";
|
||||||
import VaIconMenuCollapsed from "../icons/VaIconMenuCollapsed.vue";
|
import VaIconMenuCollapsed from "../icons/VaIconMenuCollapsed.vue";
|
||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
import { useGlobalStore } from "../../stores/global-store";
|
import { useGlobalStore } from "../../stores/global-store";
|
||||||
import NavigationRoutes, {
|
import {
|
||||||
|
authRoutes,
|
||||||
|
publicRoutes,
|
||||||
type INavigationRoute,
|
type INavigationRoute,
|
||||||
} from "../sidebar/NavigationRoutes";
|
} from "../sidebar/NavigationRoutes";
|
||||||
|
|
||||||
const { isSidebarMinimized } = storeToRefs(useGlobalStore());
|
const globalStore = useGlobalStore();
|
||||||
|
const { isSidebarMinimized } = storeToRefs(globalStore);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
@ -61,7 +64,7 @@ const findRouteName = (name: string) => {
|
||||||
return "";
|
return "";
|
||||||
};
|
};
|
||||||
|
|
||||||
return traverse(NavigationRoutes.routes);
|
return traverse(globalStore.isAuthenticated ? authRoutes : publicRoutes);
|
||||||
};
|
};
|
||||||
|
|
||||||
const items = computed(() => {
|
const items = computed(() => {
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,7 @@
|
||||||
minimized-width="0"
|
minimized-width="0"
|
||||||
>
|
>
|
||||||
<VaAccordion v-model="value" multiple>
|
<VaAccordion v-model="value" multiple>
|
||||||
<VaCollapse
|
<VaCollapse v-for="(route, index) in routes" :key="index">
|
||||||
v-for="(route, index) in navigationRoutes.routes"
|
|
||||||
:key="index"
|
|
||||||
>
|
|
||||||
<template #header="{ value: isCollapsed }">
|
<template #header="{ value: isCollapsed }">
|
||||||
<VaSidebarItem
|
<VaSidebarItem
|
||||||
:to="route.children ? undefined : { name: route.name }"
|
:to="route.children ? undefined : { name: route.name }"
|
||||||
|
|
@ -72,7 +69,12 @@ import { useRoute } from "vue-router";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useColors } from "vuestic-ui";
|
import { useColors } from "vuestic-ui";
|
||||||
|
|
||||||
import navigationRoutes, { type INavigationRoute } from "./NavigationRoutes";
|
import {
|
||||||
|
authRoutes,
|
||||||
|
publicRoutes,
|
||||||
|
type INavigationRoute,
|
||||||
|
} from "./NavigationRoutes";
|
||||||
|
import { useGlobalStore } from "../../stores/global-store";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "AppSidebar",
|
name: "AppSidebar",
|
||||||
|
|
@ -86,6 +88,10 @@ export default defineComponent({
|
||||||
const { getColor, colorToRgba } = useColors();
|
const { getColor, colorToRgba } = useColors();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
const globalStore = useGlobalStore();
|
||||||
|
const routes = computed(() =>
|
||||||
|
globalStore.isAuthenticated ? authRoutes : publicRoutes,
|
||||||
|
);
|
||||||
|
|
||||||
const value = ref<boolean[]>([]);
|
const value = ref<boolean[]>([]);
|
||||||
|
|
||||||
|
|
@ -108,7 +114,7 @@ export default defineComponent({
|
||||||
};
|
};
|
||||||
|
|
||||||
const setActiveExpand = () =>
|
const setActiveExpand = () =>
|
||||||
(value.value = navigationRoutes.routes.map((route: INavigationRoute) =>
|
(value.value = routes.value.map((route: INavigationRoute) =>
|
||||||
routeHasActiveChild(route),
|
routeHasActiveChild(route),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
@ -131,7 +137,7 @@ export default defineComponent({
|
||||||
value,
|
value,
|
||||||
color,
|
color,
|
||||||
activeColor,
|
activeColor,
|
||||||
navigationRoutes,
|
routes,
|
||||||
routeHasActiveChild,
|
routeHasActiveChild,
|
||||||
isActiveChildRoute,
|
isActiveChildRoute,
|
||||||
t,
|
t,
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ export interface INavigationRoute {
|
||||||
meta: { icon: string };
|
meta: { icon: string };
|
||||||
children?: INavigationRoute[];
|
children?: INavigationRoute[];
|
||||||
}
|
}
|
||||||
const authRoutes = [
|
export const authRoutes = [
|
||||||
{
|
{
|
||||||
name: "explore",
|
name: "explore",
|
||||||
displayName: "menu.dashboard",
|
displayName: "menu.dashboard",
|
||||||
|
|
@ -44,7 +44,7 @@ const authRoutes = [
|
||||||
},
|
},
|
||||||
] as INavigationRoute[];
|
] as INavigationRoute[];
|
||||||
|
|
||||||
const publicRoutes = [
|
export const publicRoutes = [
|
||||||
{
|
{
|
||||||
name: "routes",
|
name: "routes",
|
||||||
displayName: "menu.routes",
|
displayName: "menu.routes",
|
||||||
|
|
@ -61,11 +61,9 @@ const publicRoutes = [
|
||||||
},
|
},
|
||||||
] as INavigationRoute[];
|
] as INavigationRoute[];
|
||||||
|
|
||||||
// localStorage.getItem('token')
|
|
||||||
export default {
|
export default {
|
||||||
root: {
|
root: {
|
||||||
name: "/",
|
name: "/",
|
||||||
displayName: "navigationRoutes.home",
|
displayName: "navigationRoutes.home",
|
||||||
},
|
},
|
||||||
routes: localStorage.getItem("token") ? authRoutes : publicRoutes,
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@ import { reactive } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { useForm, useToast } from "vuestic-ui";
|
import { useForm, useToast } from "vuestic-ui";
|
||||||
import { validators } from "../../services/utils";
|
import { validators } from "../../services/utils";
|
||||||
|
import { useGlobalStore } from "../../stores/global-store";
|
||||||
|
|
||||||
const { validate } = useForm("form");
|
const { validate } = useForm("form");
|
||||||
const { push } = useRouter();
|
const { push } = useRouter();
|
||||||
|
|
@ -81,6 +82,7 @@ const formData = reactive({
|
||||||
password: "",
|
password: "",
|
||||||
keepLoggedIn: false,
|
keepLoggedIn: false,
|
||||||
});
|
});
|
||||||
|
const globalStore = useGlobalStore();
|
||||||
const token = localStorage.getItem("token");
|
const token = localStorage.getItem("token");
|
||||||
if (token != undefined && token.length > 0) {
|
if (token != undefined && token.length > 0) {
|
||||||
push({ name: "explore" }).catch(() => {});
|
push({ name: "explore" }).catch(() => {});
|
||||||
|
|
@ -112,6 +114,7 @@ const submit = () => {
|
||||||
JSON.stringify(response.data.attachments),
|
JSON.stringify(response.data.attachments),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
globalStore.setAuthenticated(true);
|
||||||
init({ message: "Вы успешно вошли!", color: "success" });
|
init({ message: "Вы успешно вошли!", color: "success" });
|
||||||
push({ name: "explore" });
|
push({ name: "explore" });
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,10 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
|
import { useGlobalStore } from "../../stores/global-store";
|
||||||
|
|
||||||
|
const globalStore = useGlobalStore();
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
|
globalStore.setAuthenticated(false);
|
||||||
useRouter().push({ name: "login" });
|
useRouter().push({ name: "login" });
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ export const useGlobalStore = defineStore("global", {
|
||||||
state: () => {
|
state: () => {
|
||||||
return {
|
return {
|
||||||
isSidebarMinimized: false,
|
isSidebarMinimized: false,
|
||||||
|
isAuthenticated:
|
||||||
|
typeof window !== "undefined" ? !!localStorage.getItem("token") : false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -11,5 +13,8 @@ export const useGlobalStore = defineStore("global", {
|
||||||
toggleSidebar() {
|
toggleSidebar() {
|
||||||
this.isSidebarMinimized = !this.isSidebarMinimized;
|
this.isSidebarMinimized = !this.isSidebarMinimized;
|
||||||
},
|
},
|
||||||
|
setAuthenticated(v: boolean) {
|
||||||
|
this.isAuthenticated = v;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue