Skip to content

Commit 0277d63

Browse files
committed
UX hardening: separate pending/revoked moderation states and normalize cloud sync errors
1 parent d3d511a commit 0277d63

2 files changed

Lines changed: 30 additions & 8 deletions

File tree

src/components/AuthSyncPanel.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useEffect, useMemo, useRef, useState } from "react";
22
import { fetchCloudLibrary, pushCloudLibrary } from "../lib/cloudLibrary";
3+
import { getUiErrorMessage } from "../lib/uiError";
34
import { useAppStore } from "../store/appStore";
45

56
const SYNC_DEBOUNCE_MS = 1200;
@@ -44,7 +45,7 @@ export function AuthSyncPanel() {
4445
useEffect(() => {
4546
if (hydrated.current) return;
4647
void refreshFromCloud().catch((error) => {
47-
const message = error instanceof Error ? error.message : String(error);
48+
const message = getUiErrorMessage(error);
4849
setStatus(`Cloud load failed: ${message}`);
4950
});
5051
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -62,7 +63,7 @@ export function AuthSyncPanel() {
6263
await pushCloudLibrary(cloudPayload);
6364
setStatus(`Cloud sync updated at ${new Date().toLocaleTimeString()}.`);
6465
} catch (error) {
65-
const message = error instanceof Error ? error.message : String(error);
66+
const message = getUiErrorMessage(error);
6667
setStatus(`Cloud save failed: ${message}`);
6768
}
6869
})();

src/components/UserAdminPanel.tsx

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ export function UserAdminPanel() {
101101
const [managedUser, setManagedUser] = useState<CloudUser | null>(null);
102102
const [managedNameDraft, setManagedNameDraft] = useState("");
103103
const [managedEmailDraft, setManagedEmailDraft] = useState("");
104-
const [userFilter, setUserFilter] = useState<"all" | "pending" | "approved">("all");
104+
const [userFilter, setUserFilter] = useState<"all" | "pending" | "approved" | "revoked">("all");
105105
const [userSearch, setUserSearch] = useState("");
106106
const [dismissedNotifications, setDismissedNotifications] = useState<Set<string>>(() =>
107107
typeof window === "undefined" ? new Set() : readDismissedNotificationIds(),
@@ -211,12 +211,21 @@ export function UserAdminPanel() {
211211
}, [canAdmin]);
212212

213213
const userRows = useMemo(() => users.filter((user) => user.id !== me?.id), [users, me?.id]);
214-
const pendingUserCount = useMemo(() => userRows.filter((user) => !user.isApproved).length, [userRows]);
214+
const pendingUserCount = useMemo(
215+
() => userRows.filter((user) => (user.accountState ?? (user.isApproved ? "approved" : "pending")) === "pending").length,
216+
[userRows],
217+
);
218+
const revokedUserCount = useMemo(
219+
() => userRows.filter((user) => (user.accountState ?? (user.isApproved ? "approved" : "pending")) === "revoked").length,
220+
[userRows],
221+
);
215222
const filteredUserRows = useMemo(() => {
216223
const q = userSearch.trim().toLowerCase();
217224
return userRows.filter((user) => {
218-
if (userFilter === "pending" && user.isApproved) return false;
219-
if (userFilter === "approved" && !user.isApproved) return false;
225+
const state = user.accountState ?? (user.isApproved ? "approved" : "pending");
226+
if (userFilter === "pending" && state !== "pending") return false;
227+
if (userFilter === "approved" && state !== "approved") return false;
228+
if (userFilter === "revoked" && state !== "revoked") return false;
220229
if (!q) return true;
221230
return (
222231
user.username.toLowerCase().includes(q) ||
@@ -595,14 +604,19 @@ export function UserAdminPanel() {
595604
<div className="user-manager-list">
596605
<div className="section-heading">
597606
<p className="field-help">Users: open a profile to review and moderate.</p>
598-
<p className="field-help">Pending: {pendingUserCount}</p>
607+
<p className="field-help">Pending: {pendingUserCount} | Revoked: {revokedUserCount}</p>
599608
</div>
600609
<label className="field-grid user-field-grid">
601610
<span>Filter</span>
602-
<select className="locale-select" onChange={(event) => setUserFilter(event.target.value as "all" | "pending" | "approved")} value={userFilter}>
611+
<select
612+
className="locale-select"
613+
onChange={(event) => setUserFilter(event.target.value as "all" | "pending" | "approved" | "revoked")}
614+
value={userFilter}
615+
>
603616
<option value="all">All users</option>
604617
<option value="pending">Pending only</option>
605618
<option value="approved">Approved only</option>
619+
<option value="revoked">Revoked only</option>
606620
</select>
607621
</label>
608622
<label className="field-grid user-field-grid">
@@ -617,6 +631,13 @@ export function UserAdminPanel() {
617631
<p className="field-help">
618632
<strong>{user.username}</strong>
619633
</p>
634+
<p className="field-help">
635+
{user.accountState === "revoked"
636+
? "Revoked"
637+
: user.isApproved
638+
? "Approved"
639+
: "Pending"}
640+
</p>
620641
<p className="field-help">{user.email ?? "-"}</p>
621642
</div>
622643
</div>

0 commit comments

Comments
 (0)