diff --git a/.roo/memory-bank/activeContext.md b/.roo/memory-bank/activeContext.md index 070a880..69e40cf 100644 --- a/.roo/memory-bank/activeContext.md +++ b/.roo/memory-bank/activeContext.md @@ -2,93 +2,77 @@ ## Task State -- task_id: TASK-SSR-DETAIL +- task_id: TASK-MCP-SETUP - status: success -- parent_task: TASK-SSR-2 -- summary: **SSR /public/workouts/:id — HTML-контент переписан под DOM-структуру WorkoutItem.vue (вёрстка + фото + OG:image + noscript).** -- next task: — (no active tasks) +- parent_task: — +- summary: **Настроить Playwright MCP для UI-верификации через браузер. Конфиг Zoo Code обновлён (Node 22), Chromium установлен.** +- 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-ы и при неизменённых пропсах). -- Ф14: `v-memo="[photo.id]"` на фото-маркерах — жёсткий пропуск патча, `onUpdated`/`clearElement()` больше не срабатывают. Методология и причины записаны в `systemPatterns.md` (секция «Charts & maps»). +**Что сделано:** +- MCP-конфиг Zoo Code (`mcp_settings.json`): server `web-browser` → `@playwright/mcp@latest`, Node 22 via nvm absolute path, `--headless` +- 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). -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. +**1. `src/components/sidebar/NavigationRoutes.ts`** -### 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` -- `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 +**2. `src/components/sidebar/AppSidebar.vue`** (Options API, `setup()`) -## ✅ 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")`. -- `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). +**3. `src/components/app-layout-navigation/AppLayoutNavigation.vue`** (script setup) -## ✅ 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 транзитивно). -- Создан `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` — `` заменён на «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. +### Acceptance Criteria (TASK-AUTH-NAV-2) -## ✅ 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`: - - Удалён вложенный `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-AUTH-NAV-2 -## ✅ 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 - -- `server/index.ts` — 4 SSR route handlers + `app.disable("x-powered-by")`: `/`, `/explore`, `/public/workouts/:id`, `/routes`. -- Helpers: `getAssetTagsSafe()`, `escapeHtml`, `formatDate`, `formatDuration`, `formatDistance`, `formatSpeed`, `getToken`. -- Canonical base: `SSR_BASE_URL` || `https://cycle-rider.ru`. -- 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. +- **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. +- TASK-404: success — HTTP 404 для неизвестных страниц. +- TASK-PUBLIC-ROUTE-FIX: success — workout_public_item в top-level router. +- TASK-F11/F12/F13/F14: success — SEO + фиксы карты тренировки. diff --git a/.roo/memory-bank/progress.md b/.roo/memory-bank/progress.md index aafd407..a877148 100644 --- a/.roo/memory-bank/progress.md +++ b/.roo/memory-bank/progress.md @@ -1,5 +1,18 @@ # 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) - `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")`. diff --git a/.roo/memory-bank/techContext.md b/.roo/memory-bank/techContext.md index 90f28e3..9d828a4 100644 --- a/.roo/memory-bank/techContext.md +++ b/.roo/memory-bank/techContext.md @@ -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. - 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 -- 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`). - Keep `vue-tsc` clean — build fails on type errors. diff --git a/src/components/app-layout-navigation/AppLayoutNavigation.vue b/src/components/app-layout-navigation/AppLayoutNavigation.vue index b758ebb..c506d5b 100644 --- a/src/components/app-layout-navigation/AppLayoutNavigation.vue +++ b/src/components/app-layout-navigation/AppLayoutNavigation.vue @@ -29,11 +29,14 @@ import { useColors } from "vuestic-ui"; import VaIconMenuCollapsed from "../icons/VaIconMenuCollapsed.vue"; import { storeToRefs } from "pinia"; import { useGlobalStore } from "../../stores/global-store"; -import NavigationRoutes, { +import { + authRoutes, + publicRoutes, type INavigationRoute, } from "../sidebar/NavigationRoutes"; -const { isSidebarMinimized } = storeToRefs(useGlobalStore()); +const globalStore = useGlobalStore(); +const { isSidebarMinimized } = storeToRefs(globalStore); const router = useRouter(); const route = useRoute(); @@ -61,7 +64,7 @@ const findRouteName = (name: string) => { return ""; }; - return traverse(NavigationRoutes.routes); + return traverse(globalStore.isAuthenticated ? authRoutes : publicRoutes); }; const items = computed(() => { diff --git a/src/components/sidebar/AppSidebar.vue b/src/components/sidebar/AppSidebar.vue index 227e75a..a59213f 100644 --- a/src/components/sidebar/AppSidebar.vue +++ b/src/components/sidebar/AppSidebar.vue @@ -6,10 +6,7 @@ minimized-width="0" > <VaAccordion v-model="value" multiple> - <VaCollapse - v-for="(route, index) in navigationRoutes.routes" - :key="index" - > + <VaCollapse v-for="(route, index) in routes" :key="index"> <template #header="{ value: isCollapsed }"> <VaSidebarItem :to="route.children ? undefined : { name: route.name }" @@ -72,7 +69,12 @@ import { useRoute } from "vue-router"; import { useI18n } from "vue-i18n"; 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({ name: "AppSidebar", @@ -86,6 +88,10 @@ export default defineComponent({ const { getColor, colorToRgba } = useColors(); const route = useRoute(); const { t } = useI18n(); + const globalStore = useGlobalStore(); + const routes = computed(() => + globalStore.isAuthenticated ? authRoutes : publicRoutes, + ); const value = ref<boolean[]>([]); @@ -108,7 +114,7 @@ export default defineComponent({ }; const setActiveExpand = () => - (value.value = navigationRoutes.routes.map((route: INavigationRoute) => + (value.value = routes.value.map((route: INavigationRoute) => routeHasActiveChild(route), )); @@ -131,7 +137,7 @@ export default defineComponent({ value, color, activeColor, - navigationRoutes, + routes, routeHasActiveChild, isActiveChildRoute, t, diff --git a/src/components/sidebar/NavigationRoutes.ts b/src/components/sidebar/NavigationRoutes.ts index d9b91ee..a055204 100644 --- a/src/components/sidebar/NavigationRoutes.ts +++ b/src/components/sidebar/NavigationRoutes.ts @@ -4,7 +4,7 @@ export interface INavigationRoute { meta: { icon: string }; children?: INavigationRoute[]; } -const authRoutes = [ +export const authRoutes = [ { name: "explore", displayName: "menu.dashboard", @@ -44,7 +44,7 @@ const authRoutes = [ }, ] as INavigationRoute[]; -const publicRoutes = [ +export const publicRoutes = [ { name: "routes", displayName: "menu.routes", @@ -61,11 +61,9 @@ const publicRoutes = [ }, ] as INavigationRoute[]; -// localStorage.getItem('token') export default { root: { name: "/", displayName: "navigationRoutes.home", }, - routes: localStorage.getItem("token") ? authRoutes : publicRoutes, }; diff --git a/src/pages/auth/Login.vue b/src/pages/auth/Login.vue index a4c61b0..a0ad805 100644 --- a/src/pages/auth/Login.vue +++ b/src/pages/auth/Login.vue @@ -72,6 +72,7 @@ import { reactive } from "vue"; import { useRouter } from "vue-router"; import { useForm, useToast } from "vuestic-ui"; import { validators } from "../../services/utils"; +import { useGlobalStore } from "../../stores/global-store"; const { validate } = useForm("form"); const { push } = useRouter(); @@ -81,6 +82,7 @@ const formData = reactive({ password: "", keepLoggedIn: false, }); +const globalStore = useGlobalStore(); const token = localStorage.getItem("token"); if (token != undefined && token.length > 0) { push({ name: "explore" }).catch(() => {}); @@ -112,6 +114,7 @@ const submit = () => { JSON.stringify(response.data.attachments), ); + globalStore.setAuthenticated(true); init({ message: "Вы успешно вошли!", color: "success" }); push({ name: "explore" }); }) diff --git a/src/pages/auth/Logout.vue b/src/pages/auth/Logout.vue index 2eb2c5d..d79ee48 100644 --- a/src/pages/auth/Logout.vue +++ b/src/pages/auth/Logout.vue @@ -2,7 +2,10 @@ <script lang="ts" setup> import { useRouter } from "vue-router"; +import { useGlobalStore } from "../../stores/global-store"; +const globalStore = useGlobalStore(); localStorage.clear(); +globalStore.setAuthenticated(false); useRouter().push({ name: "login" }); </script> diff --git a/src/stores/global-store.ts b/src/stores/global-store.ts index f7462c7..610e8ed 100644 --- a/src/stores/global-store.ts +++ b/src/stores/global-store.ts @@ -4,6 +4,8 @@ export const useGlobalStore = defineStore("global", { state: () => { return { isSidebarMinimized: false, + isAuthenticated: + typeof window !== "undefined" ? !!localStorage.getItem("token") : false, }; }, @@ -11,5 +13,8 @@ export const useGlobalStore = defineStore("global", { toggleSidebar() { this.isSidebarMinimized = !this.isSidebarMinimized; }, + setAuthenticated(v: boolean) { + this.isAuthenticated = v; + }, }, });