show credential pw

This commit is contained in:
ff
2026-04-07 23:52:05 -04:00
parent cb97e249d0
commit b9edd6a5e6
16 changed files with 1846 additions and 318 deletions

View File

@@ -215,24 +215,80 @@ export default function SettingsPage() {
`Viewing staff member:\n${staff.name} (${staff.email || "No email"})`,
);
// MANAGE USER
// MANAGE USERS (admin only)
const [newUsername, setNewUsername] = useState("");
const [newPassword, setNewPassword] = useState("");
const {
data: allUsers = [],
refetch: refetchUsers,
} = useQuery<{ id: number; username: string }[]>({
queryKey: ["/api/users/list"],
queryFn: async () => {
const res = await apiRequest("GET", "/api/users/list");
if (!res.ok) return [];
return res.json();
},
enabled: false, // loaded lazily below
});
const { user: currentUser } = useAuth();
const isAdmin = currentUser?.username === "admin";
useEffect(() => {
if (isAdmin) refetchUsers();
}, [isAdmin]);
const addUserMutation = useMutation({
mutationFn: async (data: { username: string; password: string }) => {
const res = await apiRequest("POST", "/api/users/", data);
if (!res.ok) {
const err = await res.json().catch(() => null);
throw new Error(err?.error || "Failed to create user");
}
return res.json();
},
onSuccess: () => {
setNewUsername("");
setNewPassword("");
refetchUsers();
toast({ title: "User Created", description: "New user added successfully." });
},
onError: (err: any) => {
toast({ title: "Error", description: err?.message || "Failed to create user", variant: "destructive" });
},
});
const deleteUserMutation = useMutation({
mutationFn: async (id: number) => {
const res = await apiRequest("DELETE", `/api/users/${id}`);
if (!res.ok) throw new Error("Failed to delete user");
},
onSuccess: () => {
refetchUsers();
toast({ title: "User Deleted", description: "User removed successfully." });
},
onError: (err: any) => {
toast({ title: "Error", description: err?.message || "Failed to delete user", variant: "destructive" });
},
});
// MANAGE USER (own account)
const [usernameUser, setUsernameUser] = useState("");
//fetch user
const { user } = useAuth();
useEffect(() => {
if (user?.username) {
setUsernameUser(user.username);
if (currentUser?.username) {
setUsernameUser(currentUser.username);
}
}, [user]);
}, [currentUser]);
//update user mutation
const updateUserMutate = useMutation({
mutationFn: async (
updates: Partial<{ username: string; password: string }>,
) => {
if (!user?.id) throw new Error("User not loaded");
const res = await apiRequest("PUT", `/api/users/${user.id}`, updates);
if (!currentUser?.id) throw new Error("User not loaded");
const res = await apiRequest("PUT", `/api/users/${currentUser.id}`, updates);
if (!res.ok) {
const errorData = await res.json().catch(() => null);
throw new Error(errorData?.error || "Failed to update user");
@@ -303,6 +359,77 @@ export default function SettingsPage() {
</div>
)}
{/* Manage Users section (admin only) */}
{isAdmin && (
<Card className="mt-6">
<CardContent className="space-y-4 py-6">
<h3 className="text-lg font-semibold">Manage Users</h3>
{/* Existing users list */}
<div className="border rounded divide-y">
{allUsers.length === 0 && (
<p className="text-sm text-gray-500 p-3">No users found.</p>
)}
{allUsers.map((u) => (
<div key={u.id} className="flex items-center justify-between px-3 py-2">
<span className="text-sm font-medium">{u.username}</span>
{u.username !== "admin" && (
<button
className="text-sm text-red-600 hover:underline"
onClick={() => deleteUserMutation.mutate(u.id)}
disabled={deleteUserMutation.isPending}
>
Delete
</button>
)}
</div>
))}
</div>
{/* Add new user form */}
<form
className="space-y-3 pt-2"
onSubmit={(e) => {
e.preventDefault();
if (!newUsername.trim() || !newPassword.trim()) return;
addUserMutation.mutate({ username: newUsername.trim(), password: newPassword.trim() });
}}
>
<h4 className="text-sm font-semibold text-gray-700">Add New User</h4>
<div>
<label className="block text-sm font-medium">Username</label>
<input
type="text"
value={newUsername}
onChange={(e) => setNewUsername(e.target.value)}
className="mt-1 p-2 border rounded w-full"
placeholder="Enter username"
required
/>
</div>
<div>
<label className="block text-sm font-medium">Password</label>
<input
type="password"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
className="mt-1 p-2 border rounded w-full"
placeholder="••••••••"
required
/>
</div>
<button
type="submit"
className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700"
disabled={addUserMutation.isPending}
>
{addUserMutation.isPending ? "Adding..." : "Add User"}
</button>
</form>
</CardContent>
</Card>
)}
{/* User Setting section */}
<Card className="mt-6">
<CardContent className="space-y-4 py-6">