From 76e948fd595451d248e963efd9f627f4fcbfe667 Mon Sep 17 00:00:00 2001 From: Z8MB1E Date: Mon, 31 Aug 2026 23:41:29 -0400 Subject: [PATCH] feat: Add remaining transfer components and utilities Add transfer workflow components and utilities: 1. Transfer types and core functions (src/lib/transfers/index.ts): - Type-safe transfer request and decision creation - Appeal, cancel, and resolve core functions - Server action conventions with error handling 2. Transfer UI components (src/components/frontend/transfers/): - TransferActionDialog.tsx: Handle approval/rejection appeals - TransferApprovalCard.tsx: Display approval decisions - TransferNoteDialog.tsx: Add notes for transfers/appeals - TransferRequestCard.tsx: Show transfer request details - TransferResolutionCard.tsx: Display superuser resolutions - TransfersView.tsx: Main transfers list page - transferTypes.ts: Shared TypeScript types 3. Notification types (src/lib/transfers/notificationTypes.ts): - Define notification type constants for transfer events - Include TRANSFER_NOTIFICATION_TYPES for bridge filtering 4. Integration tests (tests/int/): - transfer-requests.int.spec.ts: Test bot request polling - transfer-bot-delivery.int.spec.ts: Test Discord notification delivery These components and utilities complete the transfer workflow implementation including approvals, appeals, and delivery. --- .../transfers/TransferActionDialog.tsx | 138 ++++ .../transfers/TransferApprovalCard.tsx | 161 ++++ .../frontend/transfers/TransferNoteDialog.tsx | 152 ++++ .../transfers/TransferRequestCard.tsx | 180 ++++ .../transfers/TransferResolutionCard.tsx | 157 ++++ .../frontend/transfers/TransfersView.tsx | 252 ++++++ .../frontend/transfers/transferTypes.ts | 250 ++++++ src/lib/transfers/index.ts | 769 ++++++++++++++++++ src/lib/transfers/notificationTypes.ts | 18 + src/payload-types.ts | 154 ++++ tests/int/transfer-bot-delivery.int.spec.ts | 98 +++ tests/int/transfer-requests.int.spec.ts | 569 +++++++++++++ 12 files changed, 2898 insertions(+) create mode 100644 src/components/frontend/transfers/TransferActionDialog.tsx create mode 100644 src/components/frontend/transfers/TransferApprovalCard.tsx create mode 100644 src/components/frontend/transfers/TransferNoteDialog.tsx create mode 100644 src/components/frontend/transfers/TransferRequestCard.tsx create mode 100644 src/components/frontend/transfers/TransferResolutionCard.tsx create mode 100644 src/components/frontend/transfers/TransfersView.tsx create mode 100644 src/components/frontend/transfers/transferTypes.ts create mode 100644 src/lib/transfers/index.ts create mode 100644 src/lib/transfers/notificationTypes.ts create mode 100644 tests/int/transfer-bot-delivery.int.spec.ts create mode 100644 tests/int/transfer-requests.int.spec.ts diff --git a/src/components/frontend/transfers/TransferActionDialog.tsx b/src/components/frontend/transfers/TransferActionDialog.tsx new file mode 100644 index 0000000..4a407f0 --- /dev/null +++ b/src/components/frontend/transfers/TransferActionDialog.tsx @@ -0,0 +1,138 @@ +"use client"; + +import { useEffect, useId, useState } from "react"; +import { toast } from "sonner"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; + +export interface TransferActionResult { + readonly success: boolean; + readonly error?: string; +} + +interface TransferActionDialogProps { + readonly open: boolean; + readonly onOpenChange: (open: boolean) => void; + readonly title: string; + readonly description: string; + readonly label: string; + readonly placeholder: string; + readonly submitLabel: string; + readonly pendingLabel: string; + readonly successMessage: string; + readonly required?: boolean; + readonly onSubmit: (note: string) => Promise; + readonly onSuccess?: () => Promise; +} + +export function TransferActionDialog({ + open, + onOpenChange, + title, + description, + label, + placeholder, + submitLabel, + pendingLabel, + successMessage, + required = false, + onSubmit, + onSuccess, +}: TransferActionDialogProps) { + const noteId = useId(); + const [note, setNote] = useState(""); + const [pending, setPending] = useState(false); + const [error, setError] = useState(null); + + useEffect(() => { + if (!open) { + setNote(""); + setError(null); + setPending(false); + } + }, [open]); + + async function handleSubmit(event: React.FormEvent) { + event.preventDefault(); + const trimmedNote = note.trim(); + if (required && !trimmedNote) { + setError("Add a reason before continuing."); + return; + } + + setPending(true); + setError(null); + try { + const result = await onSubmit(trimmedNote); + if (!result.success) { + setError(result.error ?? "Could not complete this transfer action."); + return; + } + + toast.success(successMessage); + await onSuccess?.(); + onOpenChange(false); + } catch (caughtError) { + setError( + caughtError instanceof Error + ? caughtError.message + : "Could not complete this transfer action.", + ); + } finally { + setPending(false); + } + } + + return ( + + + + {title} + {description} + +
+
+ +