Sprint 5 : integration et production -- securite, performance, API publique, documentation

Backend: rate limiter, security headers, blockchain cache service avec RPC,
public API (7 endpoints read-only), WebSocket auth + heartbeat, DB connection
pooling, structured logging, health check DB. Frontend: API retry/timeout,
WebSocket auth + heartbeat + typed events, notifications toast, mobile hamburger
+ drawer, error boundary, offline banner, loading skeletons, dashboard enrichi.
Documentation: guides utilisateur complets (demarrage, vote, sanctuaire, FAQ 30+),
guide deploiement, politique securite. 123 tests, 155 fichiers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Yvv
2026-02-28 15:12:50 +01:00
parent 3cb1754592
commit 403b94fa2c
31 changed files with 4472 additions and 356 deletions

View File

@@ -0,0 +1,70 @@
<script setup lang="ts">
/**
* Error boundary component.
*
* Wraps slot content with NuxtErrorBoundary and displays a user-friendly
* error message in French when child components crash.
* Logs error details to console and emits error event for monitoring.
*/
const emit = defineEmits<{
error: [error: any]
}>()
const hasError = ref(false)
const errorDetails = ref<string | null>(null)
function handleError(error: any) {
hasError.value = true
errorDetails.value = error?.message || error?.toString() || 'Erreur inconnue'
// Log to console for debugging
console.error('[ErrorBoundary] Erreur capturee:', error)
// Emit for external monitoring
emit('error', error)
}
function retry() {
hasError.value = false
errorDetails.value = null
}
</script>
<template>
<NuxtErrorBoundary @error="handleError">
<template v-if="!hasError">
<slot />
</template>
<template v-else>
<div class="flex flex-col items-center justify-center p-8 text-center">
<div class="max-w-md space-y-4">
<UIcon
name="i-lucide-alert-triangle"
class="text-5xl text-warning mx-auto"
/>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
Une erreur est survenue
</h3>
<p class="text-sm text-gray-600 dark:text-gray-400">
Un probleme inattendu s'est produit lors du chargement de ce contenu.
Vous pouvez essayer de recharger cette section.
</p>
<p
v-if="errorDetails"
class="text-xs text-gray-400 dark:text-gray-500 font-mono bg-gray-100 dark:bg-gray-800 p-2 rounded"
>
{{ errorDetails }}
</p>
<UButton
icon="i-lucide-refresh-cw"
label="Reessayer"
color="primary"
variant="soft"
@click="retry"
/>
</div>
</div>
</template>
</NuxtErrorBoundary>
</template>

View File

@@ -0,0 +1,81 @@
<script setup lang="ts">
/**
* Reusable skeleton loader component.
*
* Provides multiple skeleton variants for loading states:
* - Card: card layout with title and content lines
* - List: multiple rows with optional avatar
* - Detail: detailed view with mixed content
*
* Uses Nuxt UI USkeleton components.
*/
const props = withDefaults(
defineProps<{
/** Number of skeleton lines to display (default: 3). */
lines?: number
/** Show an avatar circle placeholder. */
avatar?: boolean
/** Render as a card skeleton with border and padding. */
card?: boolean
}>(),
{
lines: 3,
avatar: false,
card: false,
},
)
/** Generate varying line widths for a natural appearance. */
const lineWidths = computed(() => {
const widths = ['w-full', 'w-3/4', 'w-5/6', 'w-2/3', 'w-4/5']
return Array.from({ length: props.lines }, (_, i) => widths[i % widths.length])
})
</script>
<template>
<!-- Card variant -->
<UCard v-if="card" class="animate-pulse">
<div class="space-y-4">
<!-- Optional avatar row -->
<div v-if="avatar" class="flex items-center gap-3">
<USkeleton class="h-10 w-10 rounded-full" />
<div class="flex-1 space-y-2">
<USkeleton class="h-4 w-1/3" />
<USkeleton class="h-3 w-1/4" />
</div>
</div>
<!-- Title line -->
<USkeleton class="h-5 w-2/3" />
<!-- Content lines -->
<div class="space-y-2">
<USkeleton
v-for="(width, i) in lineWidths"
:key="i"
class="h-3"
:class="width"
/>
</div>
</div>
</UCard>
<!-- List / default variant -->
<div v-else class="space-y-3 animate-pulse">
<div
v-for="(width, i) in lineWidths"
:key="i"
class="flex items-center gap-3"
>
<!-- Optional avatar per line -->
<USkeleton v-if="avatar" class="h-8 w-8 rounded-full flex-shrink-0" />
<!-- Line content -->
<div class="flex-1 space-y-1">
<USkeleton class="h-3" :class="width" />
<USkeleton v-if="i === 0" class="h-2 w-1/4" />
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,95 @@
<script setup lang="ts">
/**
* Offline detection banner.
*
* Uses navigator.onLine and online/offline events to detect
* network connectivity changes. Shows a warning banner when
* offline and a brief success message when back online.
*/
const isOnline = ref(true)
const showReconnected = ref(false)
let reconnectedTimer: ReturnType<typeof setTimeout> | null = null
function handleOffline() {
isOnline.value = false
showReconnected.value = false
if (reconnectedTimer) {
clearTimeout(reconnectedTimer)
reconnectedTimer = null
}
}
function handleOnline() {
isOnline.value = true
showReconnected.value = true
// Show "reconnected" message briefly then hide
reconnectedTimer = setTimeout(() => {
showReconnected.value = false
reconnectedTimer = null
}, 3000)
}
onMounted(() => {
isOnline.value = navigator.onLine
window.addEventListener('offline', handleOffline)
window.addEventListener('online', handleOnline)
})
onUnmounted(() => {
window.removeEventListener('offline', handleOffline)
window.removeEventListener('online', handleOnline)
if (reconnectedTimer) {
clearTimeout(reconnectedTimer)
}
})
</script>
<template>
<Transition name="slide-down">
<div
v-if="!isOnline"
class="bg-warning-100 dark:bg-warning-900/50 border-b border-warning-300 dark:border-warning-700 px-4 py-2 text-center"
role="alert"
>
<div class="flex items-center justify-center gap-2 text-sm text-warning-800 dark:text-warning-200">
<UIcon name="i-lucide-wifi-off" class="text-lg flex-shrink-0" />
<span>Vous etes hors ligne. Certaines fonctionnalites sont indisponibles.</span>
</div>
</div>
</Transition>
<Transition name="slide-down">
<div
v-if="showReconnected && isOnline"
class="bg-success-100 dark:bg-success-900/50 border-b border-success-300 dark:border-success-700 px-4 py-2 text-center"
role="status"
>
<div class="flex items-center justify-center gap-2 text-sm text-success-800 dark:text-success-200">
<UIcon name="i-lucide-wifi" class="text-lg flex-shrink-0" />
<span>Connexion retablie</span>
</div>
</div>
</Transition>
</template>
<style scoped>
.slide-down-enter-active,
.slide-down-leave-active {
transition: all 0.3s ease;
}
.slide-down-enter-from,
.slide-down-leave-to {
transform: translateY(-100%);
opacity: 0;
}
.slide-down-enter-to,
.slide-down-leave-from {
transform: translateY(0);
opacity: 1;
}
</style>