diff --git a/src/components/settings/ConnectionStatus.tsx b/src/components/settings/ConnectionStatus.tsx new file mode 100644 index 0000000..e059f0a --- /dev/null +++ b/src/components/settings/ConnectionStatus.tsx @@ -0,0 +1,66 @@ +'use client' +import { useState } from 'react' + +interface TestResult { + ok: boolean + message: string +} + +interface Results { + shopify: TestResult + sheets: TestResult +} + +export function ConnectionStatus() { + const [results, setResults] = useState(null) + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + + const runTest = async () => { + setLoading(true) + setError(null) + try { + const res = await fetch('/api/admin/connections/test') + if (!res.ok) throw new Error('Accès refusé') + setResults(await res.json()) + } catch (e) { + setError(e instanceof Error ? e.message : 'Erreur') + } finally { + setLoading(false) + } + } + + return ( +
+

Test des connexions

+ + {error &&

{error}

} + {results && ( +
+ {( + [ + { key: 'shopify', label: 'Shopify Admin API' }, + { key: 'sheets', label: 'Google Sheets API' }, + ] as const + ).map(({ key, label }) => ( +
+ + {results[key].ok ? '✓' : '✗'} + +
+

{label}

+

{results[key].message}

+
+
+ ))} +
+ )} +
+ ) +} diff --git a/src/components/settings/UsersManager.tsx b/src/components/settings/UsersManager.tsx new file mode 100644 index 0000000..013120f --- /dev/null +++ b/src/components/settings/UsersManager.tsx @@ -0,0 +1,151 @@ +'use client' +import { useState, useEffect } from 'react' + +interface User { + id: string + email: string + name: string + role: string + createdAt: string +} + +interface FormData { + email: string + name: string + password: string + role: 'admin' | 'gestionnaire' +} + +const EMPTY_FORM: FormData = { email: '', name: '', password: '', role: 'gestionnaire' } + +export function UsersManager() { + const [users, setUsers] = useState([]) + const [loading, setLoading] = useState(true) + const [form, setForm] = useState(EMPTY_FORM) + const [submitting, setSubmitting] = useState(false) + const [error, setError] = useState(null) + const [success, setSuccess] = useState(null) + + const load = () => { + setLoading(true) + fetch('/api/admin/users') + .then((r) => r.json()) + .then(setUsers) + .catch(() => setError('Impossible de charger les utilisateurs')) + .finally(() => setLoading(false)) + } + + useEffect(load, []) + + const handleCreate = async (e: React.FormEvent) => { + e.preventDefault() + setSubmitting(true) + setError(null) + setSuccess(null) + try { + const res = await fetch('/api/admin/users', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(form), + }) + const data = await res.json() + if (!res.ok) throw new Error(data.error) + setSuccess(`Utilisateur ${data.name} créé.`) + setForm(EMPTY_FORM) + load() + } catch (e) { + setError(e instanceof Error ? e.message : 'Erreur') + } finally { + setSubmitting(false) + } + } + + const handleDelete = async (id: string, name: string) => { + if (!confirm(`Supprimer ${name} ?`)) return + try { + const res = await fetch(`/api/admin/users/${id}`, { method: 'DELETE' }) + const data = await res.json() + if (!res.ok) throw new Error(data.error) + load() + } catch (e) { + setError(e instanceof Error ? e.message : 'Erreur suppression') + } + } + + return ( +
+

Gestion des utilisateurs

+ + {/* Liste */} + {loading ? ( +

Chargement…

+ ) : ( +
+ {users.map((u) => ( +
+
+

{u.name}

+

{u.email} — {u.role}

+
+ +
+ ))} + {users.length === 0 &&

Aucun utilisateur.

} +
+ )} + + {/* Formulaire création */} +
+

Nouvel utilisateur

+
+ setForm((f) => ({ ...f, email: e.target.value }))} + className="bg-slate-700 border border-slate-600 rounded-lg px-3 py-2 text-sm text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-indigo-500" + /> + setForm((f) => ({ ...f, name: e.target.value }))} + className="bg-slate-700 border border-slate-600 rounded-lg px-3 py-2 text-sm text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-indigo-500" + /> + setForm((f) => ({ ...f, password: e.target.value }))} + className="bg-slate-700 border border-slate-600 rounded-lg px-3 py-2 text-sm text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-indigo-500" + /> + +
+ {error &&

{error}

} + {success &&

{success}

} + +
+
+ ) +}