import type { Meta, StoryObj } from "@storybook/nextjs-vite"; import type React from "react"; import { EscalationOfferMessage } from "../components/chat/escalation-offer-message"; /** * `escalation_offer` message segment — Fae asking the client to confirm a * handoff to a human technician (Figma: fae chat › "escalation to admin"). * * Renders THROUGH the client approval card rather than reimplementing it, so * it is pixel-identical to `Chat/Client/ApprovalRequestMessage` down to the * Approve/Reject labels and the resolved pill. What differs is the wiring: * approve/reject call the ticket-escalation GraphQL mutations, never the * tool-approval endpoint — which is why it is a distinct segment type. * * The body copy is backend-fixed; the client never composes it. `origin` * (TOOL / MANUAL / a trigger reason) is decoded for telemetry only — every * origin renders the same card. */ const OFFER_TEXT = "Having issues with the AI assistant? This ticket can be handed off to a technician. Fae will no longer respond in this conversation. A technician will review the ticket and reply when available."; const baseData = { offerId: "offer-1", text: OFFER_TEXT, origin: "MANUAL" }; const plainDecorator = (Story: React.ComponentType) => (
); const meta = { title: "Chat/Client/EscalationOfferMessage", component: EscalationOfferMessage, tags: ["autodocs"], parameters: { docs: { description: { component: "Renders an `escalation_offer` segment: the backend-fixed handoff prompt with Approve/Reject, or a resolved status pill. Approve performs the handoff (ticket → Tech Required, Fae goes silent); Reject just records the choice.", }, }, }, decorators: [plainDecorator], } satisfies Meta; export default meta; type Story = StoryObj; /** Pending — the client has not decided yet. */ export const Pending: Story = { args: { data: baseData, status: "pending", onApprove: () => {}, onReject: () => {}, }, }; /** Approved — the handoff ran. Paired in the thread with `TicketEscalatedMessage`. */ export const ApprovedByUser: Story = { args: { data: baseData, status: "approved", resolvedByName: "John Smith" }, }; /** Declined — the client chose to keep working with Fae. */ export const DeclinedByUser: Story = { args: { data: baseData, status: "rejected", resolvedByName: "John Smith" }, }; /** * Superseded — the client typed a new message over the pending offer, which * the backend treats as declining it. Maps onto the shared `cancelled` status. */ export const Superseded: Story = { args: { data: baseData, status: "cancelled", resolvedByName: "John Smith" }, }; /** Raised by Fae's own `escalateToHuman` tool call rather than the header action. */ export const FromToolCall: Story = { args: { data: { ...baseData, origin: "TOOL" }, status: "pending", onApprove: () => {}, onReject: () => {}, }, };