@@ -187,7 +150,7 @@ export function ReviewEventRow({
}
const changes = Object.entries(event.changes);
- const timestamp = formatEventTimestamp(event.createdAt);
+ const timestamp = formatTimelineTimestamp(event.createdAt);
return (
@@ -220,14 +183,14 @@ export function ReviewEventRow({
{eventTypeLabel(event.eventType)}
{isAuditEvent(event) ? (
{applicationStatusLabel(event.applicationStatus)}
diff --git a/app/admin/applications/review-workspace.tsx b/app/admin/applications/review-workspace.tsx
index 8318782..3194c1c 100644
--- a/app/admin/applications/review-workspace.tsx
+++ b/app/admin/applications/review-workspace.tsx
@@ -2,7 +2,9 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useMounted } from "@/hooks/use-mounted";
-import type { Session } from "@supabase/supabase-js";
+import { useCoalescedAsync } from "@/hooks/use-coalesced-async";
+import { useOrganizerRealtimeSession } from "@/hooks/use-organizer-realtime-session";
+import { usePrivateBroadcastChannel } from "@/hooks/use-private-broadcast-channel";
import { useDefaultLayout, type LayoutStorage } from "react-resizable-panels";
import {
Controller,
@@ -23,7 +25,6 @@ import {
InboxIcon,
ListFilterIcon,
RefreshCwIcon,
- SearchIcon,
SmartphoneIcon,
UserRoundIcon,
type LucideIcon,
@@ -35,10 +36,16 @@ import {
markApplicationReviewed,
} from "@/lib/actions/application-review.server.actions";
import { getResumeDownloadUrl } from "@/lib/actions/resume.server.actions";
+import { formatMonthDay, formatShortDate } from "@/lib/format/dates";
import { createClient } from "@/lib/supabase/client";
+import { isBenignRealtimeChannelError } from "@/lib/supabase/realtime-errors";
+import { sendPrivateBroadcast } from "@/lib/supabase/realtime-broadcast";
+import { applicationStatusBadgeClass } from "@/lib/utils/badge-classes";
import {
reviewCompleteSchema,
reviewDraftSchema,
+ REVIEW_SYNC_CHANNEL,
+ REVIEW_SYNC_EVENT,
reviewSyncPayloadSchema,
type ReviewCounts,
type ReviewWorkspaceData,
@@ -69,7 +76,6 @@ import {
DrawerHeader,
DrawerTitle,
} from "@/components/ui/drawer";
-import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
import { ScrollArea } from "@/components/ui/scroll-area";
@@ -80,6 +86,8 @@ import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
import { clampPageIndex, getPageCount, paginateSlice } from "@/lib/pagination";
import { cn } from "@/lib/utils";
import { AdminPageHeader } from "@/app/admin/components/admin-page-header";
+import { InlineWarningNotice } from "@/app/admin/components/warning-callout";
+import { SearchField } from "@/app/admin/components/search-field";
import {
ApplicationDetailSkeleton,
ResumePreviewSkeleton,
@@ -92,8 +100,6 @@ import {
} from "./display-formatters";
import { ReviewEventTimeline } from "./review-event-timeline";
-type Organizer = { id: string; email: string };
-
type StatusFilter = "all" | "pending" | "reviewed" | "flagged";
type MobileView = "list" | "detail";
type SupabaseBrowserClient = ReturnType
;
@@ -134,17 +140,6 @@ function useIsPhoneLandscape() {
return isPhoneLandscape;
}
-function isBenignRealtimeChannelError(error: unknown) {
- if (!error) return true;
-
- const message = error instanceof Error ? error.message : String(error);
- return (
- message.includes("socket closed: 1001") ||
- message.includes("socket closed") ||
- message.includes("Channel closed")
- );
-}
-
type PresenceMeta = {
userId: string;
email: string;
@@ -156,8 +151,6 @@ const REVIEW_WORKSPACE_PANEL_IDS = [
"application-detail",
"scorecard",
] as const;
-const REVIEW_SYNC_CHANNEL = "application-review:dashboard";
-const REVIEW_SYNC_EVENT = "review_updated";
const PANEL_LAYOUT_STORAGE: LayoutStorage = {
getItem(key) {
@@ -273,16 +266,6 @@ function applicantName(item: ReviewListSummaryItem | ReviewListItem) {
return name || item.application.applicantEmail || "Unnamed applicant";
}
-function statusClassName(status: ReviewListItem["application"]["status"]) {
- if (status === "reviewed") {
- return "border-green-200 bg-green-50 text-green-700 dark:border-green-900/70 dark:bg-green-950/50 dark:text-green-300";
- }
- if (status === "flagged") {
- return "border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-900/70 dark:bg-amber-950/50 dark:text-amber-300";
- }
- return "border-slate-200 bg-slate-50 text-slate-600 dark:border-slate-800 dark:bg-slate-900 dark:text-slate-300";
-}
-
function ReviewBadge({ review }: { review: ReviewRecord | null }) {
if (!review?.reviewedAt) return null;
@@ -611,9 +594,8 @@ export default function ApplicationReviewWorkspace({
>(null);
const [pendingApplicationSwitch, setPendingApplicationSwitch] =
useState(null);
- const [organizer, setOrganizer] = useState(null);
- const [realtimeReady, setRealtimeReady] = useState(false);
const supabase = useMemo(() => createClient(), []);
+ const { organizer, realtimeReady } = useOrganizerRealtimeSession(supabase);
const selectedIdRef = useRef(selectedId);
const serverUpdatedAt = useRef(
initialSelectedDetail?.review?.updatedAt ??
@@ -666,69 +648,6 @@ export default function ApplicationReviewWorkspace({
storage: PANEL_LAYOUT_STORAGE,
});
- useEffect(() => {
- let cancelled = false;
-
- async function syncSession(
- session: Session | null,
- mode: "full" | "refresh",
- ) {
- if (!session?.access_token) {
- await supabase.realtime.setAuth(null);
- if (cancelled) return;
- setOrganizer(null);
- setRealtimeReady(false);
- return;
- }
-
- const {
- data: { user },
- error,
- } = await supabase.auth.getUser();
- if (cancelled || error || !user) {
- await supabase.realtime.setAuth(null);
- setOrganizer(null);
- setRealtimeReady(false);
- return;
- }
-
- if (mode === "refresh") {
- await supabase.realtime.setAuth(session.access_token);
- return;
- }
-
- setRealtimeReady(false);
- await supabase.realtime.setAuth(session.access_token);
- if (cancelled) return;
-
- setOrganizer({ id: user.id, email: user.email ?? "" });
- setRealtimeReady(true);
- }
-
- const {
- data: { subscription },
- } = supabase.auth.onAuthStateChange((event, session) => {
- if (event === "INITIAL_SESSION" || event === "SIGNED_IN") {
- void syncSession(session, "full");
- return;
- }
-
- if (event === "TOKEN_REFRESHED") {
- void syncSession(session, "refresh");
- return;
- }
-
- if (event === "SIGNED_OUT") {
- void syncSession(null, "full");
- }
- });
-
- return () => {
- cancelled = true;
- subscription.unsubscribe();
- };
- }, [supabase]);
-
useEffect(() => {
selectedIdRef.current = selectedId;
}, [selectedId]);
@@ -845,21 +764,25 @@ export default function ApplicationReviewWorkspace({
const broadcastReviewUpdate = useCallback(
async (applicationId: string) => {
- await reviewSyncChannel.current?.send({
- type: "broadcast",
- event: REVIEW_SYNC_EVENT,
- payload: {
- applicationId,
- sourceUserId: organizer?.id ?? "",
- },
+ if (!organizer?.id) return;
+
+ await sendPrivateBroadcast(reviewSyncChannel.current, REVIEW_SYNC_EVENT, {
+ applicationId,
+ sourceUserId: organizer.id,
});
},
- [organizer?.id],
+ [organizer],
);
const refreshReviewFromServer = useCallback(
async (applicationId: string) => {
- const detail = await getApplicationReviewDetail(applicationId);
+ const isSelected = selectedIdRef.current === applicationId;
+ const [detail, events] = await Promise.all([
+ getApplicationReviewDetail(applicationId),
+ isSelected
+ ? getApplicationReviewEvents(applicationId)
+ : Promise.resolve(null),
+ ]);
setItems((current) =>
current.map((item) =>
@@ -869,7 +792,7 @@ export default function ApplicationReviewWorkspace({
),
);
- if (selectedIdRef.current !== applicationId) return;
+ if (!isSelected || selectedIdRef.current !== applicationId) return;
setSelectedDetail(detail);
if (form.formState.isDirty) {
@@ -878,14 +801,40 @@ export default function ApplicationReviewWorkspace({
applyReviewForm(detail);
}
- const events = await getApplicationReviewEvents(applicationId);
- if (selectedIdRef.current === applicationId) {
+ if (events && selectedIdRef.current === applicationId) {
setReviewEvents(events);
}
},
[applyReviewForm, form.formState.isDirty, markReviewConflict],
);
+ const scheduleRefreshReview = useCoalescedAsync(
+ async (applicationId: string) => {
+ try {
+ await refreshReviewFromServer(applicationId);
+ } catch (error) {
+ console.error(
+ "Unable to refresh application after review sync:",
+ error,
+ );
+ }
+ },
+ );
+
+ usePrivateBroadcastChannel({
+ supabase,
+ channelName: REVIEW_SYNC_CHANNEL,
+ event: REVIEW_SYNC_EVENT,
+ payloadSchema: reviewSyncPayloadSchema,
+ organizerId: organizer?.id,
+ realtimeReady,
+ channelRef: reviewSyncChannel,
+ onRemoteMessage: (payload) => {
+ scheduleRefreshReview(payload.applicationId);
+ },
+ logLabel: "review sync channel",
+ });
+
function applyApplicationSwitch(item: ReviewListSummaryItem) {
setSelectedId(item.application.id);
setSelectedDetail(undefined);
@@ -956,47 +905,6 @@ export default function ApplicationReviewWorkspace({
if (reviewEventsLoadedId !== null) setReviewEventsLoadedId(null);
}
- useEffect(() => {
- if (!realtimeReady || !organizer) return;
-
- let active = true;
- let channel: ReviewSyncChannel | null = null;
-
- channel = supabase.channel(REVIEW_SYNC_CHANNEL, {
- config: { private: true },
- });
- if (!active) {
- supabase.removeChannel(channel);
- return;
- }
- reviewSyncChannel.current = channel;
-
- channel.on("broadcast", { event: REVIEW_SYNC_EVENT }, ({ payload }) => {
- const parsed = reviewSyncPayloadSchema.safeParse(payload);
- if (!parsed.success) return;
- if (parsed.data.sourceUserId === organizer.id) return;
-
- void refreshReviewFromServer(parsed.data.applicationId).catch((error) => {
- console.error(
- "Unable to refresh application after review sync:",
- error,
- );
- });
- });
-
- channel.subscribe((status, err) => {
- if (!active || status !== "CHANNEL_ERROR") return;
- if (isBenignRealtimeChannelError(err)) return;
- console.error("Unable to subscribe to review sync channel:", err);
- });
-
- return () => {
- active = false;
- reviewSyncChannel.current = null;
- if (channel) supabase.removeChannel(channel);
- };
- }, [organizer, realtimeReady, refreshReviewFromServer, supabase]);
-
const clearResumeExpiryTimer = useCallback(() => {
if (resumeExpiryTimer.current) {
clearTimeout(resumeExpiryTimer.current);
@@ -1279,15 +1187,11 @@ export default function ApplicationReviewWorkspace({
-
-
- setQuery(event.target.value)}
- placeholder="Search applications"
- className="pl-8"
- />
-
+ setQuery(event.target.value)}
+ />
{filteredItems.length === 0 ? (
@@ -1313,19 +1217,15 @@ export default function ApplicationReviewWorkspace({
{applicantName(item)}
- {new Date(item.application.createdAt).toLocaleDateString(
- undefined,
- {
- month: "short",
- day: "numeric",
- },
- )}
+ {formatMonthDay(item.application.createdAt)}
{applicationStatusLabel(item.application.status)}
@@ -1383,7 +1283,9 @@ export default function ApplicationReviewWorkspace({
{activeItem && (
{applicationStatusLabel(activeItem.application.status)}
@@ -1406,7 +1308,7 @@ export default function ApplicationReviewWorkspace({
@@ -1417,9 +1319,7 @@ export default function ApplicationReviewWorkspace({
{selectedDetail.application.applicantEmail ??
"No applicant email"}{" "}
· submitted{" "}
- {new Date(
- selectedDetail.application.createdAt,
- ).toLocaleDateString()}
+ {formatShortDate(selectedDetail.application.createdAt)}
{activeReviewers.length > 0 && (
-
-
-
- Currently viewing
-
-
- {activeReviewers
- .map((reviewer) => reviewer.email)
- .join(", ")}
-
-
+
+ {activeReviewers.map((reviewer) => reviewer.email).join(", ")}
+
)}
@@ -1969,15 +1861,13 @@ function ScorecardForm({
/>
{activeReviewers.length > 0 && (
-
-
-
- Another organizer is here
-
-
- {activeReviewers.map((reviewer) => reviewer.email).join(", ")}
-
-
+
+ {activeReviewers.map((reviewer) => reviewer.email).join(", ")}
+
)}