import { useState } from "react"; import { toast } from "sonner"; import { Toaster } from "@/components/ui/sonner"; import { FeedbackIdeationA } from "./ideation-a"; import { MOCK_FEEDBACK } from "./mock-data"; import { FeedbackNewRequest } from "./new-request"; import type { FeedbackNewRequestPayload } from "./new-request"; import type { FeedbackCurrentUser, FeedbackItem, FeedbackRole } from "./types"; export interface FeedbackBoardProps { items?: FeedbackItem[]; /** * Viewer role. Drives the filter set and the detail-drawer triage actions. * Creation is open to every role — only triage (approve/decline) is admin-only. */ role?: FeedbackRole; /** Signed-in user — scopes the "My requests" filter. Defaults to the role's mock user. */ currentUser?: FeedbackCurrentUser; /** Render the built-in page top bar. Pass `false` when a host supplies its own. */ showTopBar?: boolean; /** Fires when the "Load more" button is pressed (host fetches the next page). */ onLoadMore?: () => void; /** Whether more items can be loaded — shows the "Load more" button. */ hasMore?: boolean; /** Whether a load-more fetch is in flight — disables the button. */ isLoadingMore?: boolean; /** Controlled status filter. */ status?: string; onStatusChange?: (value: string) => void; /** Controlled type filter. */ type?: string; onTypeChange?: (value: string) => void; /** Controlled search query. */ query?: string; onQueryChange?: (value: string) => void; /** * Fired when a new request is submitted — receives the full intake payload. * May return a promise: the drawer only closes, resets, and shows the success * toast once it resolves. If it rejects, the drawer stays open and `onError` * fires — the board never assumes the submission succeeded. */ onSubmit?: (payload: FeedbackNewRequestPayload) => void | Promise; /** * Fired when admin confirms approval. May return a promise — the detail * drawer stays open until it resolves and closes on success only; a rejection * fires `onError` and keeps the drawer open. */ onApprove?: (item: FeedbackItem, priority: string) => void | Promise; /** * Fired when admin clicks Decline. May return a promise — the detail drawer * stays open until it resolves and closes on success only; a rejection fires * `onError` and keeps the drawer open. */ onDecline?: (item: FeedbackItem) => void | Promise; /** * Fired when any async action (`onSubmit` / `onApprove` / `onDecline`) * rejects. The originating drawer stays open; the host decides how to surface * the failure (e.g. an error toast). The board shows no failure UI itself. */ onError?: (error: unknown) => void; /** Fired when the upvote button is toggled. */ onUpvote?: (item: FeedbackItem) => void; } /** * The complete back-office feedback page. An upvote board (Ideation A) wired to a * right-side intake form: both roles get the "New request" action. Submitting * forwards the payload via `onSubmit`; the form only closes, resets, and confirms * with a toast once the host resolves it. A rejected submission leaves the drawer * open and fires `onError` instead — the host owns failure feedback. Role drives * triage only — System Admin additionally gets approve / decline actions in the * detail drawer, which likewise close on success and fire `onError` on failure. */ export function FeedbackBoard({ items = MOCK_FEEDBACK, role = "staff", currentUser, showTopBar = true, onLoadMore, hasMore = false, isLoadingMore = false, status, onStatusChange, type, onTypeChange, query, onQueryChange, onSubmit, onApprove, onDecline, onError, onUpvote, }: FeedbackBoardProps) { const [newOpen, setNewOpen] = useState(false); // Remounts the form on each successful submit so the next request starts blank. const [formKey, setFormKey] = useState(0); return ( <> setNewOpen(true)} onLoadMore={onLoadMore} hasMore={hasMore} isLoadingMore={isLoadingMore} status={status} onStatusChange={onStatusChange} type={type} onTypeChange={onTypeChange} query={query} onQueryChange={onQueryChange} onApprove={onApprove} onDecline={onDecline} onError={onError} onUpvote={onUpvote} /> {/* Creation is open to every role — always mount the intake form. */} { // Await the host first. A rejection propagates to the form, which // fires onError and stays open — so we only reach the close + reset + // success toast when the submission actually succeeds. await onSubmit?.(payload); setNewOpen(false); setFormKey((k) => k + 1); toast.success("Request submitted", { description: "Thanks, your idea is now in the queue.", }); }} onError={onError} /> ); }