add auth
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useState } from "react";
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { api, type Library } from "../api/client";
|
||||
import { api, type Library, type AuthUser } from "../api/client";
|
||||
import { useAuth } from "../auth/useAuth";
|
||||
|
||||
function LibraryRow({ lib, onRemove }: { lib: Library; onRemove: (id: number) => void }) {
|
||||
const qc = useQueryClient();
|
||||
@@ -48,6 +49,91 @@ function LibraryRow({ lib, onRemove }: { lib: Library; onRemove: (id: number) =>
|
||||
);
|
||||
}
|
||||
|
||||
function UserManagement() {
|
||||
const qc = useQueryClient();
|
||||
const { user: currentUser } = useAuth();
|
||||
const { data: users = [] } = useQuery<AuthUser[]>({
|
||||
queryKey: ["users"],
|
||||
queryFn: api.auth.listUsers,
|
||||
});
|
||||
|
||||
const [newUsername, setNewUsername] = useState("");
|
||||
const [newPassword, setNewPassword] = useState("");
|
||||
const [userError, setUserError] = useState("");
|
||||
|
||||
const createMutation = useMutation({
|
||||
mutationFn: () => api.auth.createUser(newUsername, newPassword),
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ["users"] });
|
||||
setNewUsername("");
|
||||
setNewPassword("");
|
||||
setUserError("");
|
||||
},
|
||||
onError: (e: Error) => setUserError(e.message),
|
||||
});
|
||||
|
||||
const deleteMutation = useMutation({
|
||||
mutationFn: (id: number) => api.auth.deleteUser(id),
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: ["users"] }),
|
||||
onError: (e: Error) => setUserError(e.message),
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 style={{ color: "var(--text)", marginTop: 32 }}>Users</h2>
|
||||
|
||||
<form
|
||||
onSubmit={(e) => { e.preventDefault(); createMutation.mutate(); }}
|
||||
style={{ display: "flex", flexDirection: "column", gap: 8, marginBottom: 24 }}
|
||||
>
|
||||
<input
|
||||
placeholder="Username"
|
||||
value={newUsername}
|
||||
onChange={(e) => setNewUsername(e.target.value)}
|
||||
required
|
||||
autoComplete="off"
|
||||
/>
|
||||
<input
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
value={newPassword}
|
||||
onChange={(e) => setNewPassword(e.target.value)}
|
||||
required
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
{userError && <p style={{ color: "var(--danger)", margin: 0, fontSize: 13 }}>{userError}</p>}
|
||||
<button type="submit" disabled={createMutation.isPending} style={{ background: "var(--accent)", color: "#fff", border: "none" }}>
|
||||
{createMutation.isPending ? "Creating…" : "Add User"}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<ul style={{ listStyle: "none", padding: 0 }}>
|
||||
{users.map((u) => (
|
||||
<li key={u.id} style={{ display: "flex", justifyContent: "space-between", alignItems: "center", padding: "8px 0", borderBottom: "1px solid var(--border-subtle)" }}>
|
||||
<div>
|
||||
<strong style={{ color: "var(--text)" }}>{u.username}</strong>
|
||||
{u.is_admin && (
|
||||
<span style={{ marginLeft: 8, fontSize: 11, color: "var(--accent)", fontWeight: 600 }}>
|
||||
ADMIN
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{u.id !== currentUser?.id && (
|
||||
<button
|
||||
onClick={() => deleteMutation.mutate(u.id)}
|
||||
disabled={deleteMutation.isPending}
|
||||
style={{ color: "var(--danger)", background: "transparent", border: "none" }}
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function SettingsPage() {
|
||||
const qc = useQueryClient();
|
||||
const { data: libraries = [] } = useQuery<Library[]>({
|
||||
@@ -97,6 +183,8 @@ export default function SettingsPage() {
|
||||
<LibraryRow key={lib.id} lib={lib} onRemove={(id) => deleteMutation.mutate(id)} />
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<UserManagement />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user