Réglages : préférences (langue, notifications push + test), gestion complète des tokens d'accès (liste/création/révocation, garde anti lock-out sur le dernier token), URL Gitea configurable, infos serveur en lecture seule (port/bind/origines/VAPID + flags CLI). Aide : documentation bilingue EN/FR de toutes les fonctionnalités, avec recherche. Icône Gitea (lien externe) dans la nav (sidebar + MoreSheet mobile). Backend : routes /api/v1/auth/tokens (GET/POST/DELETE) + tokenId dans /me ; routes/settings.ts (GET/PATCH, allow-list stricte gitea_url, aucun secret exposé, URL validée http/https anti-XSS) ; AuthService.listTokens/revokeToken (transaction). Front : NavItem gère les liens externes, nav primaire/secondaire, store settings, vues SettingsView/HelpView. 236 tests verts (+15 nouveaux : auth-tokens, settings-routes).
59 lines
2.0 KiB
Vue
59 lines
2.0 KiB
Vue
<template>
|
|
<aside class="hidden w-56 shrink-0 flex-col border-r border-zinc-800/80 bg-zinc-900/40 md:flex">
|
|
<RouterLink :to="{ name: 'dashboard' }" class="flex items-center gap-2 px-4 py-3.5">
|
|
<img src="/icon.svg" class="h-7 w-7" alt="" />
|
|
<span class="text-base font-semibold text-zinc-100">{{ t('common.appName') }}</span>
|
|
</RouterLink>
|
|
<button
|
|
type="button"
|
|
class="mx-2 mb-2 flex items-center gap-2 rounded-lg border border-zinc-800 bg-zinc-950/40 px-2.5 py-1.5 text-sm text-zinc-500 transition-colors hover:border-zinc-700 hover:text-zinc-300 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-emerald-500/50"
|
|
@click="palette.show()"
|
|
>
|
|
<Search :size="15" />
|
|
<span class="flex-1 text-left">{{ t('common.search') }}</span>
|
|
<kbd class="rounded border border-zinc-700 px-1 text-[10px] text-zinc-500">⌘K</kbd>
|
|
</button>
|
|
<nav class="flex flex-1 flex-col gap-1 px-2">
|
|
<NavItem
|
|
v-for="item in primary"
|
|
:key="item.key"
|
|
:to="item.to"
|
|
:icon="item.icon"
|
|
:label="item.label"
|
|
:badge="item.badge"
|
|
:active="isActive(item.match)"
|
|
orientation="row"
|
|
/>
|
|
</nav>
|
|
<nav class="flex flex-col gap-1 px-2 pb-1">
|
|
<NavItem
|
|
v-for="item in secondary"
|
|
:key="item.key"
|
|
:to="item.to"
|
|
:href="item.href"
|
|
:icon="item.icon"
|
|
:label="item.label"
|
|
:badge="item.badge"
|
|
:active="isActive(item.match)"
|
|
orientation="row"
|
|
/>
|
|
</nav>
|
|
<div class="border-t border-zinc-800/80 p-3">
|
|
<AppShellFooter />
|
|
</div>
|
|
</aside>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { useI18n } from 'vue-i18n';
|
|
import { Search } from '@lucide/vue';
|
|
import { useNav } from '../../composables/useNav';
|
|
import { useCommandPalette } from '../../composables/useCommandPalette';
|
|
import NavItem from './NavItem.vue';
|
|
import AppShellFooter from './AppShellFooter.vue';
|
|
|
|
const { t } = useI18n();
|
|
const { primary, secondary, isActive } = useNav();
|
|
const palette = useCommandPalette();
|
|
</script>
|