feat: add backup recovery, setup wizard and user-view guards
This commit is contained in:
@@ -4,8 +4,9 @@ import Image from "next/image";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { authFetch, clearToken, getApiBase, getToken } from "../../lib/auth";
|
||||
import { canAccess } from "../../lib/features";
|
||||
import { canAccess, type FeatureAccess } from "../../lib/features";
|
||||
import { lockBodyScroll } from "../../lib/scrollLock";
|
||||
import { useEffectiveRole } from "../../lib/viewMode";
|
||||
import PageHeading from "../../ui/PageHeading";
|
||||
import LatestActivity from "./LatestActivity";
|
||||
import RequestLanguage from "./RequestLanguage";
|
||||
@@ -329,8 +330,10 @@ export default function RequestTimelinePage() {
|
||||
const [historySnapshots, setHistorySnapshots] = useState<SnapshotHistory[]>([]);
|
||||
const [historyActions, setHistoryActions] = useState<ActionHistory[]>([]);
|
||||
const [operationProgress, setOperationProgress] = useState<OperationProgress | null>(null);
|
||||
const [isAdmin, setIsAdmin] = useState(false);
|
||||
const [canReportIssues, setCanReportIssues] = useState(false);
|
||||
const [viewer, setViewer] = useState<{ role?: string; features?: Partial<FeatureAccess> } | null>(null);
|
||||
const effectiveRole = useEffectiveRole(viewer?.role);
|
||||
const isAdmin = effectiveRole === "admin";
|
||||
const canReportIssues = canAccess(viewer ? { ...viewer, role: effectiveRole ?? undefined } : null, "issues");
|
||||
const [selectedAdditionalSeasons, setSelectedAdditionalSeasons] = useState<number[]>([]);
|
||||
const awaitingMediaIndex = Boolean(
|
||||
snapshot?.presentation?.pipeline?.some((stage) => stage.id === "available" && stage.state === "active"),
|
||||
@@ -389,32 +392,13 @@ export default function RequestTimelinePage() {
|
||||
throw new Error("Unable to verify your request access.");
|
||||
}
|
||||
const me = await meResponse.json();
|
||||
const viewerIsAdmin = me?.role === "admin";
|
||||
setIsAdmin(viewerIsAdmin);
|
||||
setCanReportIssues(canAccess(me, "issues"));
|
||||
setViewer(me);
|
||||
if (!snapshotResponse.ok) {
|
||||
throw new Error(await readApiError(snapshotResponse, "Unable to load this request."));
|
||||
}
|
||||
const snapshotData = await snapshotResponse.json();
|
||||
if (!isSnapshotPayload(snapshotData)) throw new Error("Unable to load this request.");
|
||||
setSnapshot(snapshotData);
|
||||
if (viewerIsAdmin) {
|
||||
const [historyResponse, actionsResponse] = await Promise.all([
|
||||
authFetch(`${baseUrl}/requests/${requestId}/history?limit=10`),
|
||||
authFetch(`${baseUrl}/requests/${requestId}/actions?limit=10`),
|
||||
]);
|
||||
if (historyResponse.ok) {
|
||||
const historyData = await historyResponse.json();
|
||||
if (Array.isArray(historyData.snapshots)) setHistorySnapshots(historyData.snapshots);
|
||||
}
|
||||
if (actionsResponse.ok) {
|
||||
const actionsData = await actionsResponse.json();
|
||||
if (Array.isArray(actionsData.actions)) setHistoryActions(actionsData.actions);
|
||||
}
|
||||
} else {
|
||||
setHistorySnapshots([]);
|
||||
setHistoryActions([]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
setLoadError(error instanceof Error ? error.message : "Unable to load this request.");
|
||||
@@ -425,6 +409,37 @@ export default function RequestTimelinePage() {
|
||||
void load();
|
||||
}, [requestId, router]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isAdmin || !requestId) {
|
||||
setShowDetails(false);
|
||||
setHistorySnapshots([]);
|
||||
setHistoryActions([]);
|
||||
return;
|
||||
}
|
||||
const controller = new AbortController();
|
||||
const loadHistory = async () => {
|
||||
try {
|
||||
const baseUrl = getApiBase();
|
||||
const [historyResponse, actionsResponse] = await Promise.all([
|
||||
authFetch(`${baseUrl}/requests/${requestId}/history?limit=10`, { signal: controller.signal }),
|
||||
authFetch(`${baseUrl}/requests/${requestId}/actions?limit=10`, { signal: controller.signal }),
|
||||
]);
|
||||
if (historyResponse.ok) {
|
||||
const data = await historyResponse.json();
|
||||
if (!controller.signal.aborted && Array.isArray(data.snapshots)) setHistorySnapshots(data.snapshots);
|
||||
}
|
||||
if (actionsResponse.ok) {
|
||||
const data = await actionsResponse.json();
|
||||
if (!controller.signal.aborted && Array.isArray(data.actions)) setHistoryActions(data.actions);
|
||||
}
|
||||
} catch (error) {
|
||||
if (!controller.signal.aborted) console.error(error);
|
||||
}
|
||||
};
|
||||
void loadHistory();
|
||||
return () => controller.abort();
|
||||
}, [isAdmin, requestId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!getToken() || !requestId) return;
|
||||
let stopped = false;
|
||||
|
||||
Reference in New Issue
Block a user