Files
RentalManager/frontend/src/routes/users/+page.svelte
2026-04-11 12:12:07 +02:00

158 lines
6.9 KiB
Svelte

<script>
import { onMount } from 'svelte';
import { api } from '$lib/stores/api.js';
import { Users, Plus, Trash2, X, Check, AlertCircle } from 'lucide-svelte';
let users = [];
let loading = true;
let showForm = false;
let error = '';
let successMsg = '';
let form = { email: '', name: '', password: '', confirm: '' };
onMount(load);
async function load() {
loading = true;
users = await api.users.list() || [];
loading = false;
}
function openForm() {
form = { email: '', name: '', password: '', confirm: '' };
error = '';
showForm = true;
}
async function create() {
error = '';
if (!form.email || !form.name || !form.password) { error = 'Tous les champs sont requis.'; return; }
if (form.password !== form.confirm) { error = 'Les mots de passe ne correspondent pas.'; return; }
if (form.password.length < 6) { error = 'Minimum 6 caractères.'; return; }
try {
await api.auth.register({ email: form.email, name: form.name, password: form.password });
showForm = false;
successMsg = `Compte "${form.name}" créé avec succès.`;
setTimeout(() => successMsg = '', 4000);
await load();
} catch (e) {
error = e.message;
}
}
async function remove(id, name) {
if (!confirm(`Supprimer le compte de "${name}" ?`)) return;
try {
await api.users.delete(id);
await load();
} catch (e) {
alert(e.message);
}
}
const fmtDate = (d) => new Date(d).toLocaleDateString('fr-FR', { day: '2-digit', month: 'long', year: 'numeric' });
</script>
<div class="p-6 max-w-2xl mx-auto">
<div class="flex items-center justify-between mb-6">
<div class="flex items-center gap-3">
<Users size={22} class="text-gray-400"/>
<h1 class="text-2xl font-semibold text-gray-900 dark:text-white">Utilisateurs</h1>
</div>
<button on:click={openForm}
class="flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg text-sm font-medium transition-colors">
<Plus size={16}/> Ajouter un membre
</button>
</div>
<p class="text-sm text-gray-500 dark:text-gray-400 mb-5">
Ajoutez les membres de votre famille qui peuvent accéder à l'application.
Chacun a son propre compte et mot de passe.
</p>
{#if successMsg}
<div class="flex items-center gap-2 text-sm px-4 py-3 rounded-lg mb-4 bg-green-50 dark:bg-green-950/30 text-green-700 dark:text-green-300">
<Check size={14}/> {successMsg}
</div>
{/if}
{#if loading}
<div class="space-y-2">
{#each [1,2] as _}<div class="h-16 bg-gray-100 dark:bg-gray-800 rounded-xl animate-pulse"/>{/each}
</div>
{:else}
<div class="bg-white dark:bg-gray-900 rounded-xl border border-gray-100 dark:border-gray-800 overflow-hidden">
{#each users as u, i (u.id)}
<div class="flex items-center gap-4 px-5 py-4
{i > 0 ? 'border-t border-gray-50 dark:border-gray-800' : ''}">
<!-- Avatar -->
<div class="w-9 h-9 rounded-full bg-blue-100 dark:bg-blue-900 flex items-center justify-center text-sm font-semibold text-blue-700 dark:text-blue-300 shrink-0">
{u.name?.[0]?.toUpperCase() ?? '?'}
</div>
<div class="flex-1 min-w-0">
<p class="text-sm font-medium text-gray-900 dark:text-white">{u.name}</p>
<p class="text-xs text-gray-400 dark:text-gray-500">{u.email} · Depuis le {fmtDate(u.created_at)}</p>
</div>
{#if i === 0}
<span class="text-xs px-2 py-0.5 rounded-full bg-blue-50 dark:bg-blue-950 text-blue-700 dark:text-blue-300 font-medium shrink-0">
Admin
</span>
{:else}
<button on:click={() => remove(u.id, u.name)}
class="p-2 text-gray-300 hover:text-red-500 rounded-lg hover:bg-red-50 dark:hover:bg-red-950 transition-colors shrink-0">
<Trash2 size={15}/>
</button>
{/if}
</div>
{/each}
</div>
{/if}
</div>
<!-- Modal -->
{#if showForm}
<div class="fixed inset-0 bg-black/40 flex items-center justify-center z-50 p-4" on:click|self={() => showForm = false}>
<div class="bg-white dark:bg-gray-900 rounded-2xl w-full max-w-md shadow-xl border border-gray-100 dark:border-gray-800">
<div class="flex items-center justify-between px-6 py-4 border-b border-gray-100 dark:border-gray-800">
<h2 class="font-semibold text-gray-900 dark:text-white">Ajouter un membre</h2>
<button on:click={() => showForm = false} class="text-gray-400 hover:text-gray-600"><X size={18}/></button>
</div>
<div class="px-6 py-5 space-y-4">
{#if error}
<div class="flex items-center gap-2 text-sm px-3 py-2 rounded-lg bg-red-50 dark:bg-red-950/30 text-red-600 dark:text-red-400">
<AlertCircle size={13}/> {error}
</div>
{/if}
<div>
<label class="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">Nom affiché</label>
<input bind:value={form.name} placeholder="Ex: Marie Dupont"
class="w-full px-3 py-2 rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-white text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"/>
</div>
<div>
<label class="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">Email</label>
<input type="email" bind:value={form.email} placeholder="marie@exemple.fr"
class="w-full px-3 py-2 rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-white text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"/>
</div>
<div>
<label class="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">Mot de passe</label>
<input type="password" bind:value={form.password}
class="w-full px-3 py-2 rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-white text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"/>
</div>
<div>
<label class="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">Confirmer le mot de passe</label>
<input type="password" bind:value={form.confirm}
class="w-full px-3 py-2 rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-white text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"/>
</div>
</div>
<div class="flex justify-end gap-3 px-6 py-4 border-t border-gray-100 dark:border-gray-800">
<button on:click={() => showForm = false} class="px-4 py-2 text-sm text-gray-600 dark:text-gray-400">Annuler</button>
<button on:click={create}
class="flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg text-sm font-medium transition-colors">
<Check size={15}/> Créer le compte
</button>
</div>
</div>
</div>
{/if}