// @ts-nocheck import { useEffect, useState } from "react"; import type { AdminDetailTab, BoardTab, LauncherRequest, LauncherRequestAnalysisItem, } from "./types"; import { createLauncherRequest, deleteLauncherRequest, loadLauncherRecentSaveEvents, loadLauncherRequestMeta, loadLauncherRequests, promoteLauncherAdminRequest, requeueLauncherAdminRequest, saveLauncherAdminRequest, triggerLauncherPendingQueue, verifyLauncherAdminPassword, } from "./requestApi"; import { appendUniqueString, cloneLauncherRequest, hydrateLauncherRequestForUi, isAdminAccessError, isNeedsReviewRequest, isQueuedPendingRequest, normalizeStringList, openAdminPanelWindow, removeStringValue, requestMatchesFilters, } from "./utils"; export function useLauncherRequestBoard({ adminWindowMode }) { const adminPanelOpen = adminWindowMode; const [activeBoardTab, setActiveBoardTab] = useState(adminWindowMode ? "requests" : "news"); const [requests, setRequests] = useState([]); const [requestsLoading, setRequestsLoading] = useState(true); const [requestsError, setRequestsError] = useState(""); const [requestDraftOpen, setRequestDraftOpen] = useState(false); const [requestDraft, setRequestDraft] = useState(""); const [requestSubmitting, setRequestSubmitting] = useState(false); const [requestMutatingId, setRequestMutatingId] = useState(""); const [requestSearchText, setRequestSearchText] = useState(""); const [requestFilterMenuOpen, setRequestFilterMenuOpen] = useState(false); const [requestStatusFilters, setRequestStatusFilters] = useState([]); const [requestTagFilters, setRequestTagFilters] = useState([]); const [allowedRequestTags, setAllowedRequestTags] = useState([]); const [expandedRequestIds, setExpandedRequestIds] = useState([]); const [adminAccessGranted, setAdminAccessGranted] = useState(false); const [adminPassword, setAdminPassword] = useState(""); const [adminPasswordDraft, setAdminPasswordDraft] = useState(""); const [adminAuthSubmitting, setAdminAuthSubmitting] = useState(false); const [adminPasswordError, setAdminPasswordError] = useState(""); const [selectedAdminRequestId, setSelectedAdminRequestId] = useState(""); const [selectedAdminAnalysisIndex, setSelectedAdminAnalysisIndex] = useState(0); const [adminSearchText, setAdminSearchText] = useState(""); const [adminFilterMenuOpen, setAdminFilterMenuOpen] = useState(false); const [adminStatusFilters, setAdminStatusFilters] = useState([]); const [adminTagFilters, setAdminTagFilters] = useState([]); const [adminEditorDraft, setAdminEditorDraft] = useState(null); const [adminDetailTab, setAdminDetailTab] = useState("routing"); const [adminSaving, setAdminSaving] = useState(false); const [recentSaveEvents, setRecentSaveEvents] = useState([]); const [logsLoading, setLogsLoading] = useState(false); const [logsModalOpen, setLogsModalOpen] = useState(false); const [logsError, setLogsError] = useState(""); const [queueTriggering, setQueueTriggering] = useState(false); const [requeueingMode, setRequeueingMode] = useState<"" | "saved" | "draft">(""); const [adminNotice, setAdminNotice] = useState(""); async function loadRequests(options?: { silent?: boolean }): Promise { const silent = options?.silent === true; if (!silent) { setRequestsLoading(true); } try { const payload = await loadLauncherRequests(); setRequests(Array.isArray(payload.requests) ? payload.requests.map(hydrateLauncherRequestForUi) : []); setRequestsError(""); } catch (nextError: unknown) { setRequestsError(String(nextError || "Failed to load requests.")); } finally { if (!silent) { setRequestsLoading(false); } } } async function loadRequestMeta(): Promise { try { const payload = await loadLauncherRequestMeta(); setAllowedRequestTags(Array.isArray(payload.allowedTags) ? payload.allowedTags : []); } catch { setAllowedRequestTags([]); } } async function loadRecentSaveEvents(): Promise { setLogsLoading(true); try { const payload = await loadLauncherRecentSaveEvents(adminPassword); setRecentSaveEvents(Array.isArray(payload.saves) ? payload.saves : []); setLogsError(""); } catch (nextError: unknown) { if (isAdminAccessError(nextError)) { setAdminAccessGranted(false); } setLogsError(String(nextError || "Failed to load admin logs.")); } finally { setLogsLoading(false); } } async function verifyAdminPassword(password: string): Promise { await verifyLauncherAdminPassword(password); } async function refreshAdminData(options?: { includeLogs?: boolean; silentRequests?: boolean }): Promise { await loadRequests({ silent: options?.silentRequests === true }); if (options?.includeLogs && adminAccessGranted && adminPassword) { await loadRecentSaveEvents(); } } useEffect(() => { void loadRequests(); void loadRequestMeta(); }, []); useEffect(() => { if (!adminPanelOpen || !adminAccessGranted || !adminPassword) { return; } void loadRecentSaveEvents(); }, [adminPanelOpen, adminAccessGranted, adminPassword]); useEffect(() => { if (activeBoardTab !== "requests") { return; } let cancelled = false; const refreshBoard = async (): Promise => { if (!adminPanelOpen) { try { const payload = await loadLauncherRequests(); if (!cancelled) { setRequests(Array.isArray(payload.requests) ? payload.requests.map(hydrateLauncherRequestForUi) : []); } } catch { // Keep the current list visible during background refresh failures. } } if (!adminPanelOpen || !adminAccessGranted || !adminPassword) { return; } try { const payload = await loadLauncherRecentSaveEvents(adminPassword); if (!cancelled) { setRecentSaveEvents(Array.isArray(payload.saves) ? payload.saves : []); } } catch { // Avoid surfacing noisy polling failures in the admin panel. } }; const intervalId = window.setInterval(() => { void refreshBoard(); }, 15000); return () => { cancelled = true; window.clearInterval(intervalId); }; }, [activeBoardTab, adminPanelOpen, adminAccessGranted, adminPassword]); useEffect(() => { if (!adminPanelOpen || !adminAccessGranted) { return; } if (requests.length === 0) { setSelectedAdminRequestId(""); setAdminEditorDraft(null); return; } const selectedRequest = requests.find((entry) => entry.id === selectedAdminRequestId); if (selectedRequest) { if (!adminEditorDraft || adminEditorDraft.id !== selectedRequest.id) { setAdminEditorDraft(cloneLauncherRequest(hydrateLauncherRequestForUi(selectedRequest))); } return; } setSelectedAdminRequestId(requests[0].id); setAdminEditorDraft(cloneLauncherRequest(hydrateLauncherRequestForUi(requests[0]))); }, [adminPanelOpen, adminAccessGranted, requests, selectedAdminRequestId, adminEditorDraft]); useEffect(() => { setSelectedAdminAnalysisIndex(0); }, [selectedAdminRequestId]); useEffect(() => { setAdminDetailTab("routing"); }, [selectedAdminRequestId]); async function handleAddRequest(): Promise { const text = requestDraft.trim(); if (!text) { setRequestsError("Write a request before saving it."); return; } setRequestSubmitting(true); try { const payload = await createLauncherRequest(text); setRequests(Array.isArray(payload.requests) ? payload.requests.map(hydrateLauncherRequestForUi) : []); setRequestDraft(""); setRequestDraftOpen(false); setRequestsError(""); setAdminNotice("Request saved. The VPS queue worker will pick it up if analysis autorun is enabled."); if (adminPanelOpen && adminAccessGranted) { void loadRecentSaveEvents(); } window.setTimeout(() => { void refreshAdminData({ includeLogs: adminPanelOpen && adminAccessGranted, silentRequests: true }); }, 3500); } catch (nextError: unknown) { setRequestsError(String(nextError || "Failed to save request.")); } finally { setRequestSubmitting(false); } } async function handleAdminPanelToggle(): Promise { setRequestDraftOpen(false); setRequestsError(""); setLogsError(""); if (adminWindowMode) { return; } if (!openAdminPanelWindow()) { setAdminNotice("Allow popups to open the admin review window."); } } async function handleAdminUnlock(): Promise { const submittedPassword = adminPasswordDraft.trim(); if (!submittedPassword) { setAdminPasswordError("Enter the admin password to continue."); return; } setAdminAuthSubmitting(true); setAdminPasswordError(""); try { await verifyAdminPassword(submittedPassword); setAdminPassword(submittedPassword); setAdminAccessGranted(true); setAdminNotice("Admin access granted."); await refreshAdminData({ includeLogs: true, silentRequests: true }); } catch (nextError: unknown) { setAdminAccessGranted(false); setAdminPassword(""); setAdminPasswordError(String(nextError || "Failed to unlock the admin panel.")); } finally { setAdminAuthSubmitting(false); } } function handleSelectAdminRequest(requestId: string): void { const nextRequest = requests.find((entry) => entry.id === requestId); setSelectedAdminRequestId(requestId); setSelectedAdminAnalysisIndex(0); setAdminEditorDraft(nextRequest ? cloneLauncherRequest(hydrateLauncherRequestForUi(nextRequest)) : null); setAdminNotice(""); setAdminPasswordError(""); } function updateAdminDraft(updater: (current: LauncherRequest) => LauncherRequest): void { setAdminEditorDraft((current) => (current ? updater(current) : current)); } function updateAdminDraftItem( itemIndex: number, updater: (item: LauncherRequestAnalysisItem) => LauncherRequestAnalysisItem, ): void { updateAdminDraft((current) => { const next = cloneLauncherRequest(current); if (!next.analysis) { next.analysis = { state: "needs_review", items: [], }; } const items = Array.isArray(next.analysis.items) ? [...next.analysis.items] : []; const existingItem = items[itemIndex] || {}; items[itemIndex] = updater({ ...existingItem, tags: Array.isArray(existingItem.tags) ? [...existingItem.tags] : [], affectedSystems: Array.isArray(existingItem.affectedSystems) ? [...existingItem.affectedSystems] : [], affectedFiles: Array.isArray(existingItem.affectedFiles) ? [...existingItem.affectedFiles] : [], reviewOptions: Array.isArray(existingItem.reviewOptions) ? [...existingItem.reviewOptions] : [], }); next.analysis.items = items; next.analysis.itemCount = items.length; next.analysis.updatedAt = new Date().toISOString(); return next; }); } async function handleSaveAdminRequest(): Promise { if (!adminEditorDraft) { return; } setAdminSaving(true); try { const payload = await saveLauncherAdminRequest(adminPassword, adminEditorDraft); const nextRequests = Array.isArray(payload.requests) ? payload.requests.map(hydrateLauncherRequestForUi) : requests; setRequests(nextRequests); const refreshed = nextRequests.find((entry) => entry.id === adminEditorDraft.id) || hydrateLauncherRequestForUi(payload.request || adminEditorDraft); setAdminEditorDraft(cloneLauncherRequest(hydrateLauncherRequestForUi(refreshed))); setAdminNotice(`Saved admin changes for "${adminEditorDraft.title}".`); if (adminPanelOpen) { void loadRecentSaveEvents(); } } catch (nextError: unknown) { if (isAdminAccessError(nextError)) { setAdminAccessGranted(false); setAdminPassword(""); setAdminPasswordError("Admin access expired. Enter the password again."); } setLogsError(String(nextError || "Failed to save admin changes.")); } finally { setAdminSaving(false); } } async function handleApproveAdminRequest(): Promise { if (!adminEditorDraft) { return; } const nextDraft = cloneLauncherRequest(adminEditorDraft); if (!nextDraft.analysis) { setLogsError("This request has no analysis to approve yet."); return; } const items = Array.isArray(nextDraft.analysis.items) ? nextDraft.analysis.items : []; if (items.length === 0) { setLogsError("This request does not have a structured analysis item to approve yet."); return; } nextDraft.analysis.items = items.map((item) => ({ ...item, statusRecommendation: "active", })); nextDraft.analysis.state = "processed"; nextDraft.analysis.updatedAt = new Date().toISOString(); setAdminEditorDraft(nextDraft); setAdminSaving(true); try { await saveLauncherAdminRequest(adminPassword, nextDraft); const promotePayload = await promoteLauncherAdminRequest(adminPassword, nextDraft); const nextRequests = Array.isArray(promotePayload.requests) ? promotePayload.requests.map(hydrateLauncherRequestForUi) : []; setRequests(nextRequests); const fallbackSelection = nextRequests[0] || null; setSelectedAdminRequestId(fallbackSelection?.id || ""); setAdminEditorDraft(fallbackSelection ? cloneLauncherRequest(hydrateLauncherRequestForUi(fallbackSelection)) : null); setAdminNotice(`Approved "${nextDraft.title}" and promoted its active request item${(nextDraft.analysis.items?.length || 0) === 1 ? "" : "s"}.`); if (adminPanelOpen) { void loadRecentSaveEvents(); } } catch (nextError: unknown) { if (isAdminAccessError(nextError)) { setAdminAccessGranted(false); setAdminPassword(""); setAdminPasswordError("Admin access expired. Enter the password again."); } setLogsError(String(nextError || "Failed to approve this request.")); } finally { setAdminSaving(false); } } async function handleRequeueAnalysis(mode: "saved" | "draft"): Promise { if (!adminEditorDraft) { return; } setRequeueingMode(mode); setLogsError(""); try { const payload = await requeueLauncherAdminRequest(adminPassword, adminEditorDraft, mode); const nextRequests = Array.isArray(payload.requests) ? payload.requests.map(hydrateLauncherRequestForUi) : requests; setRequests(nextRequests); const refreshed = nextRequests.find((entry) => entry.id === adminEditorDraft.id) || hydrateLauncherRequestForUi(payload.request || adminEditorDraft); setAdminEditorDraft(cloneLauncherRequest(hydrateLauncherRequestForUi(refreshed))); if (payload.launched) { setAdminNotice(mode === "draft" ? "Edited draft resubmitted to the analyzer." : "Saved request resubmitted to the analyzer."); } else { const reason = String(payload.reason || "no-op"); if (reason === "request-analysis-already-running") { setAdminNotice("The queue worker is already running. This request will be picked up on the next pass."); } else if (reason === "request-not-queued") { setAdminNotice("That request is not currently eligible for review reruns."); } else { setAdminNotice(`Review rerun returned: ${reason}.`); } } await refreshAdminData({ includeLogs: true, silentRequests: true }); window.setTimeout(() => { void refreshAdminData({ includeLogs: true, silentRequests: true }); }, 4200); } catch (nextError: unknown) { if (isAdminAccessError(nextError)) { setAdminAccessGranted(false); setAdminPassword(""); setAdminPasswordError("Admin access expired. Enter the password again."); } setLogsError(String(nextError || "Failed to requeue this request for review.")); } finally { setRequeueingMode(""); } } function handleToggleExpandedRequest(requestId: string): void { setExpandedRequestIds((current) => ( current.includes(requestId) ? current.filter((entry) => entry !== requestId) : [...current, requestId] )); } async function handleDeleteRequest(requestEntry: LauncherRequest): Promise { const confirmed = window.confirm(`Delete this request?\n\n${requestEntry.title}`); if (!confirmed) { return; } setRequestMutatingId(requestEntry.id); try { const payload = await deleteLauncherRequest(adminPassword, requestEntry.id); setRequests(Array.isArray(payload.requests) ? payload.requests.map(hydrateLauncherRequestForUi) : []); setRequestsError(""); setExpandedRequestIds((current) => current.filter((entry) => entry !== requestEntry.id)); setAdminNotice(`Deleted request "${requestEntry.title}".`); if (adminPanelOpen) { void loadRecentSaveEvents(); } } catch (nextError: unknown) { if (isAdminAccessError(nextError)) { setAdminAccessGranted(false); setAdminPassword(""); setAdminPasswordError("Admin access expired. Enter the password again."); } setRequestsError(String(nextError || "Failed to delete request.")); } finally { setRequestMutatingId(""); } } async function handleProcessPendingQueue(): Promise { setQueueTriggering(true); try { const payload = await triggerLauncherPendingQueue(adminPassword); if (payload.launched) { setAdminNotice(`Queue worker launched for ${payload.queuedPendingCount ?? 0} pending request${payload.queuedPendingCount === 1 ? "" : "s"}.`); } else { const reason = String(payload.reason || "no-op"); if (reason === "no-pending-requests") { setAdminNotice("No unprocessed pending requests are waiting in the queue."); } else if (reason === "request-analysis-already-running") { setAdminNotice("The request analysis worker is already running on the VPS."); } else if (reason === "request-analysis-not-configured") { setAdminNotice("Request analysis is not configured on the server."); } else { setAdminNotice(`Queue trigger returned: ${reason}.`); } } await refreshAdminData({ includeLogs: true, silentRequests: true }); if (payload.launched) { window.setTimeout(() => { void refreshAdminData({ includeLogs: true, silentRequests: true }); }, 4200); } } catch (nextError: unknown) { if (isAdminAccessError(nextError)) { setAdminAccessGranted(false); setAdminPassword(""); setAdminPasswordError("Admin access expired. Enter the password again."); } setLogsError(String(nextError || "Failed to trigger the queue worker.")); } finally { setQueueTriggering(false); } } const requestCount = requests.length; const pendingRequestCount = requests.filter((entry) => entry.status === "pending").length; const activeRequestCount = requests.filter((entry) => entry.status === "active").length; const implementedRequestCount = requests.filter((entry) => entry.status === "implemented").length; const queuedPendingRequestCount = requests.filter(isQueuedPendingRequest).length; const needsReviewRequestCount = requests.filter(isNeedsReviewRequest).length; const requestTags = (allowedRequestTags.length > 0 ? allowedRequestTags : Array.from(new Set( requests .flatMap((entry) => Array.isArray(entry.tags) ? entry.tags : []) .map((entry) => String(entry || "").trim()) .filter(Boolean), ))).sort((a, b) => a.localeCompare(b)); const requestTagFilterOptions = requestTags .map((tag) => ({ tag, count: requests.filter((entry) => entry.tags.includes(tag)).length, })) .filter((entry) => entry.count > 0); const requestStatusFilterOptions = [ { id: "pending", label: "Pending", count: pendingRequestCount }, { id: "queued", label: "Queued", count: queuedPendingRequestCount }, { id: "review", label: "Needs Review", count: needsReviewRequestCount }, { id: "active", label: "Active", count: activeRequestCount }, { id: "implemented", label: "Implemented", count: implementedRequestCount }, ].filter((entry) => entry.count > 0); const filteredRequests = requests.filter((entry) => requestMatchesFilters( entry, requestSearchText, requestStatusFilters, requestTagFilters, )); const adminFilteredRequests = requests.filter((entry) => requestMatchesFilters( entry, adminSearchText, adminStatusFilters, adminTagFilters, )); const selectedAnalysisItem = adminEditorDraft?.analysis?.items?.[selectedAdminAnalysisIndex] || null; const standardizedTagOptions = normalizeStringList([ ...allowedRequestTags, ...requestTags, ...requests.flatMap((entry) => [ ...entry.tags, ...(Array.isArray(entry.analysis?.routing?.suggestedTags) ? entry.analysis.routing.suggestedTags : []), ...(Array.isArray(entry.analysis?.items) ? entry.analysis.items.flatMap((item) => Array.isArray(item.tags) ? item.tags : []) : []), ]), ...(adminEditorDraft?.tags || []), ...(Array.isArray(adminEditorDraft?.analysis?.routing?.suggestedTags) ? adminEditorDraft.analysis.routing.suggestedTags : []), ...(Array.isArray(selectedAnalysisItem?.tags) ? selectedAnalysisItem.tags : []), ]); const categoryOptions = normalizeStringList([ ...standardizedTagOptions, ...requests.map((entry) => entry.category), ...requests.flatMap((entry) => Array.isArray(entry.analysis?.items) ? entry.analysis.items.map((item) => String(item.primaryCategory || "")) : []), String(adminEditorDraft?.category || ""), String(selectedAnalysisItem?.primaryCategory || ""), ]); const boardTitle = adminWindowMode ? "Worldshaper Admin" : "Worldshaper Board"; const boardHint = adminWindowMode ? `${queuedPendingRequestCount} queued, ${needsReviewRequestCount} review, ${activeRequestCount} active, ${implementedRequestCount} implemented` : (activeBoardTab === "news" ? "Latest announcements" : `${queuedPendingRequestCount} queued, ${needsReviewRequestCount} review, ${activeRequestCount} active, ${implementedRequestCount} implemented`); return { adminPanelOpen, activeBoardTab, setActiveBoardTab, requests, requestsLoading, requestsError, requestDraftOpen, setRequestDraftOpen, requestDraft, setRequestDraft, requestSubmitting, requestMutatingId, requestSearchText, setRequestSearchText, requestFilterMenuOpen, setRequestFilterMenuOpen, requestStatusFilters, setRequestStatusFilters, requestTagFilters, setRequestTagFilters, allowedRequestTags, expandedRequestIds, adminAccessGranted, adminPassword, adminPasswordDraft, setAdminPasswordDraft, adminAuthSubmitting, adminPasswordError, selectedAdminRequestId, selectedAdminAnalysisIndex, setSelectedAdminAnalysisIndex, adminSearchText, setAdminSearchText, adminFilterMenuOpen, setAdminFilterMenuOpen, adminStatusFilters, setAdminStatusFilters, adminTagFilters, setAdminTagFilters, adminEditorDraft, adminDetailTab, setAdminDetailTab, adminSaving, recentSaveEvents, logsLoading, logsModalOpen, setLogsModalOpen, logsError, queueTriggering, requeueingMode, adminNotice, refreshAdminData, loadRecentSaveEvents, handleAddRequest, handleAdminPanelToggle, handleAdminUnlock, handleSelectAdminRequest, updateAdminDraft, updateAdminDraftItem, handleSaveAdminRequest, handleApproveAdminRequest, handleRequeueAnalysis, handleToggleExpandedRequest, handleDeleteRequest, handleProcessPendingQueue, requestCount, pendingRequestCount, activeRequestCount, implementedRequestCount, queuedPendingRequestCount, needsReviewRequestCount, requestTags, requestTagFilterOptions, requestStatusFilterOptions, filteredRequests, adminFilteredRequests, selectedAnalysisItem, standardizedTagOptions, categoryOptions, boardTitle, boardHint, }; }