Refonte front (packages/web) : coquille applicative partagée (sidebar desktop + barre d'onglets mobile + PageHeader, langue/push/logout centralisés) pilotée par route.meta.layout ; design system (BaseButton/BaseBadge/SegmentedControl/EmptyState/ SkeletonRow, anneau de focus, icônes @lucide/vue) ; tri/filtre/recherche/pagination côté client via useListControls (+ synchro URL) sur sessions/worktrees/groupes ; section « À traiter », palette de commandes ⌘K, toasts. Aucune modif serveur/protocole/DB.
58 lines
1.7 KiB
Vue
58 lines
1.7 KiB
Vue
<template>
|
|
<div class="flex flex-col gap-2">
|
|
<p v-if="pushErrorText" class="text-xs text-amber-400">{{ pushErrorText }}</p>
|
|
<div class="flex items-center gap-2">
|
|
<LanguageSwitcher />
|
|
<BaseButton
|
|
v-if="push.supported"
|
|
variant="ghost"
|
|
size="sm"
|
|
icon-only
|
|
:icon="push.enabled ? Bell : BellOff"
|
|
:class="push.enabled ? 'text-emerald-300' : ''"
|
|
:disabled="push.busy"
|
|
:title="pushErrorText || undefined"
|
|
:aria-label="push.enabled ? t('push.disable') : t('push.enable')"
|
|
@click="push.toggle()"
|
|
/>
|
|
<BaseButton
|
|
variant="ghost"
|
|
size="sm"
|
|
icon-only
|
|
:icon="LogOut"
|
|
class="ml-auto"
|
|
:aria-label="t('common.logout')"
|
|
@click="logout"
|
|
/>
|
|
</div>
|
|
<span v-if="auth.serverVersion" class="text-[11px] text-zinc-600">v{{ auth.serverVersion }}</span>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, onMounted } from 'vue';
|
|
import { useI18n } from 'vue-i18n';
|
|
import { Bell, BellOff, LogOut } from '@lucide/vue';
|
|
import { useAuthStore } from '../../stores/auth';
|
|
import { usePushStore } from '../../stores/push';
|
|
import { useSession } from '../../composables/useSession';
|
|
import BaseButton from '../ui/BaseButton.vue';
|
|
import LanguageSwitcher from '../LanguageSwitcher.vue';
|
|
|
|
const { t } = useI18n();
|
|
const auth = useAuthStore();
|
|
const push = usePushStore();
|
|
const { logout } = useSession();
|
|
|
|
// 'denied'/'unsupported' sont des clés i18n ; tout autre message d'erreur est affiché tel quel.
|
|
const pushErrorText = computed(() => {
|
|
const e = push.error;
|
|
if (!e) return '';
|
|
return e === 'denied' || e === 'unsupported' ? t(`push.${e}`) : e;
|
|
});
|
|
|
|
onMounted(() => {
|
|
void push.refresh();
|
|
});
|
|
</script>
|