feat: Permettre au super admin de se connecter et accéder à son dashboard
Le super admin (table super_admins, master DB) ne pouvait pas se connecter via /api/login car ce firewall n'utilisait que le provider tenant. De même, le JWT n'était pas enrichi pour les super admins, l'endpoint /api/me/roles les rejetait, et le frontend redirigeait systématiquement vers /dashboard. Un chain provider (super_admin + tenant) résout l'authentification, le JwtPayloadEnricher et MyRolesProvider gèrent désormais les deux types d'utilisateurs, et le frontend redirige selon le rôle après login.
This commit is contained in:
227
frontend/src/routes/super-admin/establishments/+page.svelte
Normal file
227
frontend/src/routes/super-admin/establishments/+page.svelte
Normal file
@@ -0,0 +1,227 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import {
|
||||
getEstablishments,
|
||||
switchTenant,
|
||||
type EstablishmentData
|
||||
} from '$lib/features/super-admin/api/super-admin';
|
||||
|
||||
let establishments = $state<EstablishmentData[]>([]);
|
||||
let isLoading = $state(true);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
establishments = await getEstablishments();
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Erreur inconnue';
|
||||
} finally {
|
||||
isLoading = false;
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSwitch(tenantId: string) {
|
||||
try {
|
||||
await switchTenant(tenantId);
|
||||
window.location.href = '/dashboard';
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Erreur lors du basculement';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="establishments-page">
|
||||
<div class="page-header">
|
||||
<h2>Établissements</h2>
|
||||
<a href="/super-admin/establishments/new" class="btn-create">
|
||||
Nouvel établissement
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{#if error}
|
||||
<div class="error-banner">{error}</div>
|
||||
{/if}
|
||||
|
||||
{#if isLoading}
|
||||
<div class="loading">Chargement...</div>
|
||||
{:else if establishments.length === 0}
|
||||
<div class="empty-state">
|
||||
<p>Aucun établissement configuré.</p>
|
||||
<a href="/super-admin/establishments/new" class="btn-create">Créer le premier établissement</a>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nom</th>
|
||||
<th>Sous-domaine</th>
|
||||
<th>Statut</th>
|
||||
<th>Dernière activité</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each establishments as establishment}
|
||||
<tr>
|
||||
<td class="name-cell">
|
||||
<a href="/super-admin/establishments/{establishment.id}">
|
||||
{establishment.name}
|
||||
</a>
|
||||
</td>
|
||||
<td class="subdomain-cell">{establishment.subdomain}</td>
|
||||
<td>
|
||||
<span class="badge" class:active={establishment.status === 'active'}>
|
||||
{establishment.status === 'active' ? 'Actif' : 'Inactif'}
|
||||
</span>
|
||||
</td>
|
||||
<td class="date-cell">
|
||||
{#if establishment.lastActivityAt}
|
||||
{new Date(establishment.lastActivityAt).toLocaleDateString('fr-FR')}
|
||||
{:else}
|
||||
—
|
||||
{/if}
|
||||
</td>
|
||||
<td class="actions-cell">
|
||||
<button
|
||||
class="btn-sm"
|
||||
onclick={() => handleSwitch(establishment.tenantId)}
|
||||
>
|
||||
Accéder
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.establishments-page h2 {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
color: #1a1a2e;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.btn-create {
|
||||
background: #1a1a2e;
|
||||
color: white;
|
||||
padding: 0.5rem 1.25rem;
|
||||
border-radius: 8px;
|
||||
text-decoration: none;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.btn-create:hover {
|
||||
background: #16213e;
|
||||
}
|
||||
|
||||
.error-banner {
|
||||
background: #fee2e2;
|
||||
color: #dc2626;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
padding: 3rem;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 4rem 2rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
padding: 0.75rem 1rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
color: #666;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 0.75rem 1rem;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.name-cell a {
|
||||
color: #1a1a2e;
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.name-cell a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.subdomain-cell {
|
||||
color: #666;
|
||||
font-family: monospace;
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
.date-cell {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.badge {
|
||||
font-size: 0.75rem;
|
||||
padding: 0.125rem 0.5rem;
|
||||
border-radius: 12px;
|
||||
background: #fee2e2;
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.badge.active {
|
||||
background: #dcfce7;
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
.actions-cell {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: 0.375rem 0.75rem;
|
||||
background: #1a1a2e;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.btn-sm:hover {
|
||||
background: #16213e;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user