feat: Dashboard placeholder avec preview Score Sérénité
Permet aux parents de visualiser une démo du Score Sérénité dès leur première connexion, avant même que les données réelles soient disponibles. Les autres rôles (enseignant, élève, admin) ont également leur dashboard adapté avec des sections placeholder. La landing page redirige automatiquement vers /dashboard si l'utilisateur est déjà authentifié, offrant un accès direct au tableau de bord.
This commit is contained in:
208
frontend/src/routes/dashboard/+layout.svelte
Normal file
208
frontend/src/routes/dashboard/+layout.svelte
Normal file
@@ -0,0 +1,208 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { logout } from '$lib/auth/auth.svelte';
|
||||
|
||||
let { children } = $props();
|
||||
let isLoggingOut = $state(false);
|
||||
|
||||
// Note: Authentication is handled by authenticatedFetch in the page component.
|
||||
// If not authenticated, authenticatedFetch will attempt refresh and redirect to /login if needed.
|
||||
|
||||
async function handleLogout() {
|
||||
isLoggingOut = true;
|
||||
try {
|
||||
await logout();
|
||||
} finally {
|
||||
isLoggingOut = false;
|
||||
}
|
||||
}
|
||||
|
||||
function goHome() {
|
||||
goto('/dashboard');
|
||||
}
|
||||
|
||||
function goSettings() {
|
||||
goto('/settings');
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="dashboard-layout">
|
||||
<header class="dashboard-header">
|
||||
<div class="header-content">
|
||||
<button class="logo-button" onclick={goHome}>
|
||||
<span class="logo-text">Classeo</span>
|
||||
</button>
|
||||
<nav class="header-nav">
|
||||
<a href="/dashboard" class="nav-link active">Tableau de bord</a>
|
||||
<button class="nav-button" onclick={goSettings}>Parametres</button>
|
||||
<button class="logout-button" onclick={handleLogout} disabled={isLoggingOut}>
|
||||
{#if isLoggingOut}
|
||||
<span class="spinner"></span>
|
||||
Deconnexion...
|
||||
{:else}
|
||||
Deconnexion
|
||||
{/if}
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="dashboard-main">
|
||||
<div class="main-content">
|
||||
{@render children()}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.dashboard-layout {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--surface-primary, #f8fafc);
|
||||
}
|
||||
|
||||
.dashboard-header {
|
||||
background: var(--surface-elevated, #fff);
|
||||
border-bottom: 1px solid var(--border-subtle, #e2e8f0);
|
||||
padding: 0 1.5rem;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
.logo-button {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
color: var(--accent-primary, #0ea5e9);
|
||||
}
|
||||
|
||||
.header-nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-secondary, #64748b);
|
||||
text-decoration: none;
|
||||
border-radius: 0.5rem;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
color: var(--text-primary, #1f2937);
|
||||
background: var(--surface-primary, #f8fafc);
|
||||
}
|
||||
|
||||
.nav-link.active {
|
||||
color: var(--accent-primary, #0ea5e9);
|
||||
background: var(--accent-primary-light, #e0f2fe);
|
||||
}
|
||||
|
||||
.nav-button {
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-secondary, #64748b);
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 0.5rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.nav-button:hover {
|
||||
color: var(--text-primary, #1f2937);
|
||||
background: var(--surface-primary, #f8fafc);
|
||||
}
|
||||
|
||||
.logout-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-secondary, #64748b);
|
||||
background: transparent;
|
||||
border: 1px solid var(--border-subtle, #e2e8f0);
|
||||
border-radius: 0.5rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.logout-button:hover:not(:disabled) {
|
||||
color: var(--color-alert, #ef4444);
|
||||
border-color: var(--color-alert, #ef4444);
|
||||
}
|
||||
|
||||
.logout-button:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border: 2px solid var(--border-subtle, #e2e8f0);
|
||||
border-top-color: var(--text-secondary, #64748b);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
.dashboard-main {
|
||||
flex: 1;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.header-content {
|
||||
flex-wrap: wrap;
|
||||
height: auto;
|
||||
padding: 0.75rem 0;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.header-nav {
|
||||
width: 100%;
|
||||
justify-content: flex-end;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.dashboard-main {
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user