Sprint 1 : scaffolding complet de Glibredecision

Plateforme de decisions collectives pour Duniter/G1.
Backend FastAPI async + PostgreSQL (14 tables, 8 routers, 6 services,
moteur de vote avec formule d'inertie WoT/Smith/TechComm).
Frontend Nuxt 4 + Nuxt UI v3 + Pinia (9 pages, 5 stores).
Infrastructure Docker + Woodpecker CI + Traefik.
Documentation technique et utilisateur (15 fichiers).
Seed : Licence G1, Engagement Forgeron v2.0.0, 4 protocoles de vote.
30 tests unitaires (formules, mode params, vote nuance) -- tous verts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Yvv
2026-02-28 12:46:11 +01:00
commit 25437f24e3
100 changed files with 10236 additions and 0 deletions

128
frontend/app/app.vue Normal file
View File

@@ -0,0 +1,128 @@
<script setup lang="ts">
const auth = useAuthStore()
const route = useRoute()
const navigationItems = [
{
label: 'Documents de reference',
icon: 'i-lucide-book-open',
to: '/documents',
},
{
label: 'Decisions',
icon: 'i-lucide-scale',
to: '/decisions',
},
{
label: 'Mandats',
icon: 'i-lucide-user-check',
to: '/mandates',
},
{
label: 'Protocoles',
icon: 'i-lucide-settings',
to: '/protocols',
},
{
label: 'Sanctuaire',
icon: 'i-lucide-archive',
to: '/sanctuary',
},
]
onMounted(async () => {
auth.hydrateFromStorage()
if (auth.token) {
try {
await auth.fetchMe()
} catch {
auth.logout()
}
}
})
</script>
<template>
<UApp>
<div class="min-h-screen flex flex-col">
<!-- Header -->
<header class="border-b border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex items-center justify-between h-16">
<NuxtLink to="/" class="flex items-center gap-2">
<UIcon name="i-lucide-vote" class="text-primary text-2xl" />
<span class="text-xl font-bold text-gray-900 dark:text-white">Glibredecision</span>
</NuxtLink>
<div class="flex items-center gap-4">
<template v-if="auth.isAuthenticated">
<UBadge
:color="auth.identity?.is_smith ? 'success' : 'neutral'"
variant="subtle"
>
{{ auth.identity?.display_name || auth.identity?.address?.slice(0, 12) + '...' }}
</UBadge>
<UBadge v-if="auth.identity?.is_techcomm" color="info" variant="subtle">
Comite Tech
</UBadge>
<UButton
icon="i-lucide-log-out"
variant="ghost"
color="neutral"
size="sm"
@click="auth.logout()"
/>
</template>
<template v-else>
<UButton
to="/login"
icon="i-lucide-log-in"
label="Se connecter"
variant="soft"
color="primary"
/>
</template>
</div>
</div>
</div>
</header>
<!-- Main content with sidebar -->
<div class="flex flex-1">
<!-- Sidebar navigation -->
<aside class="w-64 border-r border-gray-200 dark:border-gray-800 bg-gray-50 dark:bg-gray-900/50 hidden md:block">
<nav class="p-4">
<UNavigationMenu
:items="navigationItems"
orientation="vertical"
class="w-full"
/>
</nav>
</aside>
<!-- Mobile navigation -->
<div class="md:hidden border-b border-gray-200 dark:border-gray-800 w-full absolute top-16 bg-white dark:bg-gray-900 z-10">
<UNavigationMenu
:items="navigationItems"
class="px-4 py-2 overflow-x-auto"
/>
</div>
<!-- Page content -->
<main class="flex-1 p-4 sm:p-6 lg:p-8 md:mt-0 mt-12">
<NuxtPage />
</main>
</div>
<!-- Footer -->
<footer class="border-t border-gray-200 dark:border-gray-800 bg-gray-50 dark:bg-gray-900/50">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
<div class="flex items-center justify-between text-sm text-gray-500">
<span>Glibredecision v0.1.0 - Decisions collectives pour Duniter/G1</span>
<span>Licence libre</span>
</div>
</div>
</footer>
</div>
</UApp>
</template>