@burdenoff/microfe-subscriberbot 2026.1031.1 → 2026.1102.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/stories/pages/NegotiationDetailPage.stories.d.ts +11 -0
- package/dist/stories/pages/NegotiationDetailPage.stories.d.ts.map +1 -0
- package/dist/stories/pages/NegotiationNewPage.stories.d.ts +9 -0
- package/dist/stories/pages/NegotiationNewPage.stories.d.ts.map +1 -0
- package/dist/stories/pages/NegotiationsPage.stories.d.ts +11 -0
- package/dist/stories/pages/NegotiationsPage.stories.d.ts.map +1 -0
- package/dist/stories/support/negotiationFixtures.d.ts +14 -0
- package/dist/stories/support/negotiationFixtures.d.ts.map +1 -0
- package/dist/subscriberbot/SubscriberBotRoutes.d.ts.map +1 -1
- package/dist/subscriberbot/SubscriberBotRoutes.js +18 -6
- package/dist/subscriberbot/SubscriberBotRoutes.js.map +1 -1
- package/dist/subscriberbot/admin/AdminDashboardPage.d.ts.map +1 -1
- package/dist/subscriberbot/admin/AdminDashboardPage.js +61 -51
- package/dist/subscriberbot/admin/AdminDashboardPage.js.map +1 -1
- package/dist/subscriberbot/admin/NegotiationDeskPanel.d.ts +4 -0
- package/dist/subscriberbot/admin/NegotiationDeskPanel.d.ts.map +1 -0
- package/dist/subscriberbot/admin/NegotiationDeskPanel.js +210 -0
- package/dist/subscriberbot/admin/NegotiationDeskPanel.js.map +1 -0
- package/dist/subscriberbot/components/negotiations/NegotiationStatusPill.d.ts +8 -0
- package/dist/subscriberbot/components/negotiations/NegotiationStatusPill.d.ts.map +1 -0
- package/dist/subscriberbot/components/negotiations/NegotiationStatusPill.js +18 -0
- package/dist/subscriberbot/components/negotiations/NegotiationStatusPill.js.map +1 -0
- package/dist/subscriberbot/components/negotiations/NegotiationTimeline.d.ts +8 -0
- package/dist/subscriberbot/components/negotiations/NegotiationTimeline.d.ts.map +1 -0
- package/dist/subscriberbot/components/negotiations/NegotiationTimeline.js +49 -0
- package/dist/subscriberbot/components/negotiations/NegotiationTimeline.js.map +1 -0
- package/dist/subscriberbot/components/negotiations/OutcomeForm.d.ts +13 -0
- package/dist/subscriberbot/components/negotiations/OutcomeForm.d.ts.map +1 -0
- package/dist/subscriberbot/components/negotiations/OutcomeForm.js +112 -0
- package/dist/subscriberbot/components/negotiations/OutcomeForm.js.map +1 -0
- package/dist/subscriberbot/components/negotiations/index.d.ts +5 -0
- package/dist/subscriberbot/components/negotiations/index.d.ts.map +1 -0
- package/dist/subscriberbot/components/negotiations/index.js +4 -0
- package/dist/subscriberbot/components/negotiations/labels.d.ts +16 -0
- package/dist/subscriberbot/components/negotiations/labels.d.ts.map +1 -0
- package/dist/subscriberbot/components/negotiations/labels.js +179 -0
- package/dist/subscriberbot/components/negotiations/labels.js.map +1 -0
- package/dist/subscriberbot/hooks/useNegotiationApi.d.ts +51 -0
- package/dist/subscriberbot/hooks/useNegotiationApi.d.ts.map +1 -0
- package/dist/subscriberbot/hooks/useNegotiationApi.js +106 -0
- package/dist/subscriberbot/hooks/useNegotiationApi.js.map +1 -0
- package/dist/subscriberbot/hooks/useSubscriberBotApi.d.ts +1 -0
- package/dist/subscriberbot/hooks/useSubscriberBotApi.d.ts.map +1 -1
- package/dist/subscriberbot/hooks/useSubscriberBotApi.js +1 -1
- package/dist/subscriberbot/hooks/useSubscriberBotApi.js.map +1 -1
- package/dist/subscriberbot/pages/negotiations/NegotiationDetailPage.d.ts +11 -0
- package/dist/subscriberbot/pages/negotiations/NegotiationDetailPage.d.ts.map +1 -0
- package/dist/subscriberbot/pages/negotiations/NegotiationDetailPage.js +312 -0
- package/dist/subscriberbot/pages/negotiations/NegotiationDetailPage.js.map +1 -0
- package/dist/subscriberbot/pages/negotiations/NegotiationNewPage.d.ts +11 -0
- package/dist/subscriberbot/pages/negotiations/NegotiationNewPage.d.ts.map +1 -0
- package/dist/subscriberbot/pages/negotiations/NegotiationNewPage.js +375 -0
- package/dist/subscriberbot/pages/negotiations/NegotiationNewPage.js.map +1 -0
- package/dist/subscriberbot/pages/negotiations/NegotiationsPage.d.ts +10 -0
- package/dist/subscriberbot/pages/negotiations/NegotiationsPage.d.ts.map +1 -0
- package/dist/subscriberbot/pages/negotiations/NegotiationsPage.js +155 -0
- package/dist/subscriberbot/pages/negotiations/NegotiationsPage.js.map +1 -0
- package/dist/subscriberbot/pages/relationships/RelationshipDetailPage.d.ts.map +1 -1
- package/dist/subscriberbot/pages/relationships/RelationshipDetailPage.js +321 -307
- package/dist/subscriberbot/pages/relationships/RelationshipDetailPage.js.map +1 -1
- package/dist/subscriberbot/types/negotiation.d.ts +101 -0
- package/dist/subscriberbot/types/negotiation.d.ts.map +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminDashboardPage.js","names":[],"sources":["../../../src/subscriberbot/admin/AdminDashboardPage.tsx"],"sourcesContent":["/**\n * SubscriberBot Admin Dashboard\n * @module subscriberbot/admin\n *\n * Product-specific admin surface mounted inside `admin-app`. It exposes the\n * three governance operations requested for SubscriberBot, all wired to the\n * real `wspace-subscriberbot-svc` admin governance schema (BOFF-3237):\n * 1. Provider registry curation — `subListProviders` / `subUpdateProvider`.\n * 2. Adapter approval queue — `subListAdapterSubmissions` /\n * `subApproveAdapter` / `subRejectAdapter`.\n * 3. Marketplace moderation queue — `subListMarketplaceReports` /\n * `subModerateListing`.\n */\n\nimport { Fragment, useState, type FC } from 'react';\nimport {\n Button,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n EmptyState,\n Spinner,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n Tabs,\n TabsContent,\n TabsList,\n TabsTrigger,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\nimport { CheckCircle2, Plug, Settings2, Store, XCircle } from 'lucide-react';\n\nimport { PageLayout } from '../components/PageLayout';\nimport { StatusBadge } from '../components/StatusBadge';\nimport {\n useAdapterSubmissions,\n useApproveAdapter,\n useMarketplaceReports,\n useModerateListing,\n useProviders,\n useRejectAdapter,\n useUpdateProvider,\n} from '../hooks';\nimport { formatDate } from '../utils/formatters';\nimport { useTr } from '../hooks/useTr';\nimport type { SubModerationAction, SubProvider } from '../types';\n\nexport const AdminDashboardPage: FC = () => {\n const tr = useTr();\n const [activeTab, setActiveTab] = useState('providers');\n\n // --- Provider registry ----------------------------------------------\n const { data: providersData, isLoading: providersLoading } = useProviders({\n pagination: { limit: 50 },\n });\n const updateProvider = useUpdateProvider();\n\n const handleVerify = (id: string) => {\n updateProvider.mutate({ id, input: { status: 'VERIFIED' } });\n };\n\n const handleDeprecate = (id: string) => {\n updateProvider.mutate({ id, input: { status: 'DEPRECATED' } });\n };\n\n const providers = providersData?.items ?? [];\n\n // --- Adapter approvals -------------------------------------------------\n const { data: adapterSubmissionsData, isLoading: adaptersLoading } = useAdapterSubmissions({\n pagination: { limit: 50 },\n });\n const approveAdapter = useApproveAdapter();\n const rejectAdapter = useRejectAdapter();\n const [rejectingAdapterId, setRejectingAdapterId] = useState<string | null>(null);\n const [adapterRejectReason, setAdapterRejectReason] = useState('');\n\n const adapterSubmissions = adapterSubmissionsData?.items ?? [];\n\n const handleApproveAdapter = (id: string) => {\n approveAdapter.mutate({ id });\n };\n\n const handleStartRejectAdapter = (id: string) => {\n setRejectingAdapterId(id);\n setAdapterRejectReason('');\n };\n\n const handleConfirmRejectAdapter = (id: string) => {\n if (!adapterRejectReason.trim()) return;\n rejectAdapter.mutate(\n { id, reason: adapterRejectReason.trim() },\n {\n onSuccess: () => {\n setRejectingAdapterId(null);\n setAdapterRejectReason('');\n },\n }\n );\n };\n\n // --- Marketplace moderation ---------------------------------------------\n const { data: marketplaceReportsData, isLoading: reportsLoading } = useMarketplaceReports({\n pagination: { limit: 50 },\n });\n const moderateListing = useModerateListing();\n const [moderatingReportId, setModeratingReportId] = useState<string | null>(null);\n const [moderateAction, setModerateAction] = useState<SubModerationAction | null>(null);\n const [moderateReason, setModerateReason] = useState('');\n\n const marketplaceReports = marketplaceReportsData?.items ?? [];\n\n const handleStartModerate = (id: string, action: SubModerationAction) => {\n setModeratingReportId(id);\n setModerateAction(action);\n setModerateReason('');\n };\n\n const handleConfirmModerate = (id: string) => {\n if (!moderateAction || !moderateReason.trim()) return;\n moderateListing.mutate(\n { id, action: moderateAction, reason: moderateReason.trim() },\n {\n onSuccess: () => {\n setModeratingReportId(null);\n setModerateAction(null);\n setModerateReason('');\n },\n }\n );\n };\n\n return (\n <PageLayout\n title={tr('admin.title', 'SubscriberBot Admin')}\n description={tr(\n 'admin.description',\n 'Provider registry curation, adapter approvals, and marketplace moderation.'\n )}\n >\n <Tabs value={activeTab} onValueChange={setActiveTab} className=\"w-full\">\n <TabsList className=\"mb-4 grid w-full grid-cols-3 sm:w-auto sm:inline-flex\">\n <TabsTrigger value=\"providers\" className=\"gap-2\">\n <Settings2 size={16} />\n {tr('admin.tabs.providers', 'Provider Registry')}\n </TabsTrigger>\n <TabsTrigger value=\"adapters\" className=\"gap-2\">\n <Plug size={16} />\n {tr('admin.tabs.adapters', 'Adapter Approvals')}\n </TabsTrigger>\n <TabsTrigger value=\"marketplace\" className=\"gap-2\">\n <Store size={16} />\n {tr('admin.tabs.marketplace', 'Marketplace Moderation')}\n </TabsTrigger>\n </TabsList>\n\n {/* Provider registry curation — backed by real GraphQL queries. */}\n <TabsContent value=\"providers\">\n <Card>\n <CardHeader>\n <CardTitle>{tr('admin.providers.title', 'Provider registry')}</CardTitle>\n <CardDescription>\n {tr(\n 'admin.providers.description',\n 'Curate the global provider catalog. Verify submitted providers or deprecate stale entries.'\n )}\n </CardDescription>\n </CardHeader>\n <CardContent>\n {providersLoading ? (\n <div className=\"flex min-h-[200px] items-center justify-center\">\n <Spinner size=\"lg\" />\n </div>\n ) : providers.length === 0 ? (\n <EmptyState\n icon={<Store className=\"size-6 text-action-primary-bg\" />}\n title={tr('admin.providers.emptyTitle', 'No providers yet')}\n description={tr(\n 'admin.providers.emptyDescription',\n 'Once providers are created or claimed they will appear here for curation.'\n )}\n />\n ) : (\n <div className=\"overflow-x-auto\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>{tr('admin.providers.table.name', 'Name')}</TableHead>\n <TableHead>{tr('admin.providers.table.category', 'Category')}</TableHead>\n <TableHead>{tr('admin.providers.table.status', 'Status')}</TableHead>\n <TableHead>{tr('admin.providers.table.created', 'Created')}</TableHead>\n <TableHead className=\"text-right\">\n {tr('admin.providers.table.actions', 'Actions')}\n </TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {providers.map((provider: SubProvider) => (\n <TableRow key={provider.id}>\n <TableCell className=\"font-medium text-text-primary\">\n {provider.name}\n </TableCell>\n <TableCell className=\"text-text-secondary\">{provider.category}</TableCell>\n <TableCell>\n <StatusBadge status={provider.status} />\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {formatDate(provider.createdAt)}\n </TableCell>\n <TableCell className=\"text-right\">\n <div className=\"flex justify-end gap-2\">\n {provider.status !== 'VERIFIED' && (\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => handleVerify(provider.id)}\n disabled={updateProvider.isPending}\n >\n <CheckCircle2 size={14} className=\"mr-1\" />\n {tr('admin.providers.verify', 'Verify')}\n </Button>\n )}\n {provider.status !== 'DEPRECATED' && (\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onClick={() => handleDeprecate(provider.id)}\n disabled={updateProvider.isPending}\n >\n {tr('admin.providers.deprecate', 'Deprecate')}\n </Button>\n )}\n </div>\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n )}\n </CardContent>\n </Card>\n </TabsContent>\n\n {/* Adapter approval queue — backed by real GraphQL queries. */}\n <TabsContent value=\"adapters\">\n <Card>\n <CardHeader>\n <CardTitle>{tr('admin.adapters.title', 'Adapter approvals')}</CardTitle>\n <CardDescription>\n {tr(\n 'admin.adapters.description',\n 'Review submitted integration adapters before they are published to the SubscriberBot adapter marketplace.'\n )}\n </CardDescription>\n </CardHeader>\n <CardContent>\n {adaptersLoading ? (\n <div className=\"flex min-h-[200px] items-center justify-center\">\n <Spinner size=\"lg\" />\n </div>\n ) : adapterSubmissions.length === 0 ? (\n <EmptyState\n icon={<Plug className=\"size-6 text-action-primary-bg\" />}\n title={tr('admin.adapters.emptyTitle', 'No adapter submissions')}\n description={tr(\n 'admin.adapters.emptyDescription',\n 'Submitted integration adapters will appear here for review.'\n )}\n />\n ) : (\n <div className=\"overflow-x-auto\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>{tr('admin.adapters.table.adapter', 'Adapter')}</TableHead>\n <TableHead>\n {tr('admin.adapters.table.workspace', 'Submitting workspace')}\n </TableHead>\n <TableHead>{tr('admin.adapters.table.submitted', 'Submitted')}</TableHead>\n <TableHead>{tr('admin.adapters.table.status', 'Status')}</TableHead>\n <TableHead className=\"text-right\">\n {tr('admin.adapters.table.actions', 'Actions')}\n </TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {adapterSubmissions.map((submission) => (\n <Fragment key={submission.id}>\n <TableRow>\n <TableCell className=\"font-medium text-text-primary\">\n {submission.adapterId}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {submission.submitterWorkspaceId}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {formatDate(submission.createdAt)}\n </TableCell>\n <TableCell>\n <StatusBadge status={submission.status} />\n </TableCell>\n <TableCell className=\"text-right\">\n {submission.status === 'PENDING' && (\n <div className=\"flex justify-end gap-2\">\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => handleApproveAdapter(submission.id)}\n disabled={approveAdapter.isPending || rejectAdapter.isPending}\n >\n <CheckCircle2 size={14} className=\"mr-1\" />\n {tr('admin.adapters.approve', 'Approve')}\n </Button>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onClick={() => handleStartRejectAdapter(submission.id)}\n disabled={approveAdapter.isPending || rejectAdapter.isPending}\n >\n <XCircle size={14} className=\"mr-1\" />\n {tr('admin.adapters.reject', 'Reject')}\n </Button>\n </div>\n )}\n </TableCell>\n </TableRow>\n {rejectingAdapterId === submission.id && (\n <TableRow key={`${submission.id}-reject`}>\n <TableCell colSpan={5} className=\"bg-bg-sunken\">\n <div className=\"flex flex-col gap-2 py-1 sm:flex-row sm:items-start\">\n <Textarea\n value={adapterRejectReason}\n onChange={(event) => setAdapterRejectReason(event.target.value)}\n placeholder={tr(\n 'admin.adapters.rejectPlaceholder',\n 'Reason for rejecting this adapter submission (required)'\n )}\n className=\"min-h-[60px] flex-1\"\n autoFocus\n />\n <div className=\"flex gap-2 sm:flex-col\">\n <Button\n size=\"sm\"\n variant=\"destructive\"\n onClick={() => handleConfirmRejectAdapter(submission.id)}\n disabled={\n !adapterRejectReason.trim() || rejectAdapter.isPending\n }\n >\n {tr('admin.adapters.confirmReject', 'Confirm reject')}\n </Button>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => setRejectingAdapterId(null)}\n disabled={rejectAdapter.isPending}\n >\n {tr('admin.adapters.cancel', 'Cancel')}\n </Button>\n </div>\n </div>\n </TableCell>\n </TableRow>\n )}\n </Fragment>\n ))}\n </TableBody>\n </Table>\n </div>\n )}\n </CardContent>\n </Card>\n </TabsContent>\n\n {/* Marketplace moderation — backed by real GraphQL queries. */}\n <TabsContent value=\"marketplace\">\n <Card>\n <CardHeader>\n <CardTitle>{tr('admin.marketplace.title', 'Marketplace moderation')}</CardTitle>\n <CardDescription>\n {tr(\n 'admin.marketplace.description',\n 'Review reported marketplace listings and take moderation actions.'\n )}\n </CardDescription>\n </CardHeader>\n <CardContent>\n {reportsLoading ? (\n <div className=\"flex min-h-[200px] items-center justify-center\">\n <Spinner size=\"lg\" />\n </div>\n ) : marketplaceReports.length === 0 ? (\n <EmptyState\n icon={<Store className=\"size-6 text-action-primary-bg\" />}\n title={tr('admin.marketplace.emptyTitle', 'No reported listings')}\n description={tr(\n 'admin.marketplace.emptyDescription',\n 'Reported marketplace listings will appear here for moderation.'\n )}\n />\n ) : (\n <div className=\"overflow-x-auto\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>{tr('admin.marketplace.table.listing', 'Listing')}</TableHead>\n <TableHead>\n {tr('admin.marketplace.table.workspace', 'Reporting workspace')}\n </TableHead>\n <TableHead>{tr('admin.marketplace.table.reported', 'Reported')}</TableHead>\n <TableHead>{tr('admin.marketplace.table.reason', 'Reason')}</TableHead>\n <TableHead>{tr('admin.marketplace.table.status', 'Status')}</TableHead>\n <TableHead className=\"text-right\">\n {tr('admin.marketplace.table.actions', 'Actions')}\n </TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {marketplaceReports.map((report) => (\n <Fragment key={report.id}>\n <TableRow>\n <TableCell className=\"font-medium text-text-primary\">\n {report.listingId}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {report.reporterWorkspaceId}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {formatDate(report.createdAt)}\n </TableCell>\n <TableCell className=\"text-text-secondary\">{report.reason}</TableCell>\n <TableCell>\n <StatusBadge status={report.status} />\n </TableCell>\n <TableCell className=\"text-right\">\n {report.status === 'PENDING' && (\n <div className=\"flex justify-end gap-2\">\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => handleStartModerate(report.id, 'DISMISS')}\n disabled={moderateListing.isPending}\n >\n {tr('admin.marketplace.dismiss', 'Dismiss')}\n </Button>\n <Button\n size=\"sm\"\n variant=\"destructive\"\n onClick={() => handleStartModerate(report.id, 'REMOVE_LISTING')}\n disabled={moderateListing.isPending}\n >\n {tr('admin.marketplace.remove', 'Remove')}\n </Button>\n </div>\n )}\n </TableCell>\n </TableRow>\n {moderatingReportId === report.id && (\n <TableRow key={`${report.id}-moderate`}>\n <TableCell colSpan={6} className=\"bg-bg-sunken\">\n <div className=\"flex flex-col gap-2 py-1 sm:flex-row sm:items-start\">\n <Textarea\n value={moderateReason}\n onChange={(event) => setModerateReason(event.target.value)}\n placeholder={\n moderateAction === 'REMOVE_LISTING'\n ? tr(\n 'admin.marketplace.reasonPlaceholderRemove',\n 'Reason to remove this listing (required)'\n )\n : tr(\n 'admin.marketplace.reasonPlaceholderDismiss',\n 'Reason to dismiss this report (required)'\n )\n }\n className=\"min-h-[60px] flex-1\"\n autoFocus\n />\n <div className=\"flex gap-2 sm:flex-col\">\n <Button\n size=\"sm\"\n variant={\n moderateAction === 'REMOVE_LISTING'\n ? 'destructive'\n : 'outline'\n }\n onClick={() => handleConfirmModerate(report.id)}\n disabled={!moderateReason.trim() || moderateListing.isPending}\n >\n {moderateAction === 'REMOVE_LISTING'\n ? tr('admin.marketplace.confirmRemoval', 'Confirm removal')\n : tr(\n 'admin.marketplace.confirmDismissal',\n 'Confirm dismissal'\n )}\n </Button>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => setModeratingReportId(null)}\n disabled={moderateListing.isPending}\n >\n {tr('admin.marketplace.cancel', 'Cancel')}\n </Button>\n </div>\n </div>\n </TableCell>\n </TableRow>\n )}\n </Fragment>\n ))}\n </TableBody>\n </Table>\n </div>\n )}\n </CardContent>\n </Card>\n </TabsContent>\n </Tabs>\n </PageLayout>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AdminDashboardPage.js","names":[],"sources":["../../../src/subscriberbot/admin/AdminDashboardPage.tsx"],"sourcesContent":["/**\n * SubscriberBot Admin Dashboard\n * @module subscriberbot/admin\n *\n * Product-specific admin surface mounted inside `admin-app`. It exposes the\n * three governance operations requested for SubscriberBot, all wired to the\n * real `wspace-subscriberbot-svc` admin governance schema (BOFF-3237):\n * 1. Provider registry curation — `subListProviders` / `subUpdateProvider`.\n * 2. Adapter approval queue — `subListAdapterSubmissions` /\n * `subApproveAdapter` / `subRejectAdapter`.\n * 3. Marketplace moderation queue — `subListMarketplaceReports` /\n * `subModerateListing`.\n */\n\nimport { Fragment, useState, type FC } from 'react';\nimport {\n Button,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n EmptyState,\n Spinner,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n Tabs,\n TabsContent,\n TabsList,\n TabsTrigger,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\nimport { CheckCircle2, Handshake, Plug, Settings2, Store, XCircle } from 'lucide-react';\n\nimport { PageLayout } from '../components/PageLayout';\nimport { StatusBadge } from '../components/StatusBadge';\nimport { NegotiationDeskPanel } from './NegotiationDeskPanel';\nimport {\n useAdapterSubmissions,\n useApproveAdapter,\n useMarketplaceReports,\n useModerateListing,\n useProviders,\n useRejectAdapter,\n useUpdateProvider,\n} from '../hooks';\nimport { formatDate } from '../utils/formatters';\nimport { useTr } from '../hooks/useTr';\nimport type { SubModerationAction, SubProvider } from '../types';\n\nexport const AdminDashboardPage: FC = () => {\n const tr = useTr();\n const [activeTab, setActiveTab] = useState('providers');\n\n // --- Provider registry ----------------------------------------------\n const { data: providersData, isLoading: providersLoading } = useProviders({\n pagination: { limit: 50 },\n });\n const updateProvider = useUpdateProvider();\n\n const handleVerify = (id: string) => {\n updateProvider.mutate({ id, input: { status: 'VERIFIED' } });\n };\n\n const handleDeprecate = (id: string) => {\n updateProvider.mutate({ id, input: { status: 'DEPRECATED' } });\n };\n\n const providers = providersData?.items ?? [];\n\n // --- Adapter approvals -------------------------------------------------\n const { data: adapterSubmissionsData, isLoading: adaptersLoading } = useAdapterSubmissions({\n pagination: { limit: 50 },\n });\n const approveAdapter = useApproveAdapter();\n const rejectAdapter = useRejectAdapter();\n const [rejectingAdapterId, setRejectingAdapterId] = useState<string | null>(null);\n const [adapterRejectReason, setAdapterRejectReason] = useState('');\n\n const adapterSubmissions = adapterSubmissionsData?.items ?? [];\n\n const handleApproveAdapter = (id: string) => {\n approveAdapter.mutate({ id });\n };\n\n const handleStartRejectAdapter = (id: string) => {\n setRejectingAdapterId(id);\n setAdapterRejectReason('');\n };\n\n const handleConfirmRejectAdapter = (id: string) => {\n if (!adapterRejectReason.trim()) return;\n rejectAdapter.mutate(\n { id, reason: adapterRejectReason.trim() },\n {\n onSuccess: () => {\n setRejectingAdapterId(null);\n setAdapterRejectReason('');\n },\n }\n );\n };\n\n // --- Marketplace moderation ---------------------------------------------\n const { data: marketplaceReportsData, isLoading: reportsLoading } = useMarketplaceReports({\n pagination: { limit: 50 },\n });\n const moderateListing = useModerateListing();\n const [moderatingReportId, setModeratingReportId] = useState<string | null>(null);\n const [moderateAction, setModerateAction] = useState<SubModerationAction | null>(null);\n const [moderateReason, setModerateReason] = useState('');\n\n const marketplaceReports = marketplaceReportsData?.items ?? [];\n\n const handleStartModerate = (id: string, action: SubModerationAction) => {\n setModeratingReportId(id);\n setModerateAction(action);\n setModerateReason('');\n };\n\n const handleConfirmModerate = (id: string) => {\n if (!moderateAction || !moderateReason.trim()) return;\n moderateListing.mutate(\n { id, action: moderateAction, reason: moderateReason.trim() },\n {\n onSuccess: () => {\n setModeratingReportId(null);\n setModerateAction(null);\n setModerateReason('');\n },\n }\n );\n };\n\n return (\n <PageLayout\n title={tr('admin.title', 'SubscriberBot Admin')}\n description={tr(\n 'admin.description',\n 'Provider registry curation, adapter approvals, and marketplace moderation.'\n )}\n >\n <Tabs value={activeTab} onValueChange={setActiveTab} className=\"w-full\">\n <TabsList className=\"mb-4 grid w-full grid-cols-2 sm:w-auto sm:inline-flex\">\n <TabsTrigger value=\"providers\" className=\"gap-2\">\n <Settings2 size={16} />\n {tr('admin.tabs.providers', 'Provider Registry')}\n </TabsTrigger>\n <TabsTrigger value=\"adapters\" className=\"gap-2\">\n <Plug size={16} />\n {tr('admin.tabs.adapters', 'Adapter Approvals')}\n </TabsTrigger>\n <TabsTrigger value=\"marketplace\" className=\"gap-2\">\n <Store size={16} />\n {tr('admin.tabs.marketplace', 'Marketplace Moderation')}\n </TabsTrigger>\n <TabsTrigger value=\"negotiations\" className=\"gap-2\">\n <Handshake size={16} />\n {tr('admin.tabs.negotiations', 'Negotiation Desk')}\n </TabsTrigger>\n </TabsList>\n\n {/* Provider registry curation — backed by real GraphQL queries. */}\n <TabsContent value=\"providers\">\n <Card>\n <CardHeader>\n <CardTitle>{tr('admin.providers.title', 'Provider registry')}</CardTitle>\n <CardDescription>\n {tr(\n 'admin.providers.description',\n 'Curate the global provider catalog. Verify submitted providers or deprecate stale entries.'\n )}\n </CardDescription>\n </CardHeader>\n <CardContent>\n {providersLoading ? (\n <div className=\"flex min-h-[200px] items-center justify-center\">\n <Spinner size=\"lg\" />\n </div>\n ) : providers.length === 0 ? (\n <EmptyState\n icon={<Store className=\"size-6 text-action-primary-bg\" />}\n title={tr('admin.providers.emptyTitle', 'No providers yet')}\n description={tr(\n 'admin.providers.emptyDescription',\n 'Once providers are created or claimed they will appear here for curation.'\n )}\n />\n ) : (\n <div className=\"overflow-x-auto\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>{tr('admin.providers.table.name', 'Name')}</TableHead>\n <TableHead>{tr('admin.providers.table.category', 'Category')}</TableHead>\n <TableHead>{tr('admin.providers.table.status', 'Status')}</TableHead>\n <TableHead>{tr('admin.providers.table.created', 'Created')}</TableHead>\n <TableHead className=\"text-right\">\n {tr('admin.providers.table.actions', 'Actions')}\n </TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {providers.map((provider: SubProvider) => (\n <TableRow key={provider.id}>\n <TableCell className=\"font-medium text-text-primary\">\n {provider.name}\n </TableCell>\n <TableCell className=\"text-text-secondary\">{provider.category}</TableCell>\n <TableCell>\n <StatusBadge status={provider.status} />\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {formatDate(provider.createdAt)}\n </TableCell>\n <TableCell className=\"text-right\">\n <div className=\"flex justify-end gap-2\">\n {provider.status !== 'VERIFIED' && (\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => handleVerify(provider.id)}\n disabled={updateProvider.isPending}\n >\n <CheckCircle2 size={14} className=\"mr-1\" />\n {tr('admin.providers.verify', 'Verify')}\n </Button>\n )}\n {provider.status !== 'DEPRECATED' && (\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onClick={() => handleDeprecate(provider.id)}\n disabled={updateProvider.isPending}\n >\n {tr('admin.providers.deprecate', 'Deprecate')}\n </Button>\n )}\n </div>\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n )}\n </CardContent>\n </Card>\n </TabsContent>\n\n {/* Adapter approval queue — backed by real GraphQL queries. */}\n <TabsContent value=\"adapters\">\n <Card>\n <CardHeader>\n <CardTitle>{tr('admin.adapters.title', 'Adapter approvals')}</CardTitle>\n <CardDescription>\n {tr(\n 'admin.adapters.description',\n 'Review submitted integration adapters before they are published to the SubscriberBot adapter marketplace.'\n )}\n </CardDescription>\n </CardHeader>\n <CardContent>\n {adaptersLoading ? (\n <div className=\"flex min-h-[200px] items-center justify-center\">\n <Spinner size=\"lg\" />\n </div>\n ) : adapterSubmissions.length === 0 ? (\n <EmptyState\n icon={<Plug className=\"size-6 text-action-primary-bg\" />}\n title={tr('admin.adapters.emptyTitle', 'No adapter submissions')}\n description={tr(\n 'admin.adapters.emptyDescription',\n 'Submitted integration adapters will appear here for review.'\n )}\n />\n ) : (\n <div className=\"overflow-x-auto\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>{tr('admin.adapters.table.adapter', 'Adapter')}</TableHead>\n <TableHead>\n {tr('admin.adapters.table.workspace', 'Submitting workspace')}\n </TableHead>\n <TableHead>{tr('admin.adapters.table.submitted', 'Submitted')}</TableHead>\n <TableHead>{tr('admin.adapters.table.status', 'Status')}</TableHead>\n <TableHead className=\"text-right\">\n {tr('admin.adapters.table.actions', 'Actions')}\n </TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {adapterSubmissions.map((submission) => (\n <Fragment key={submission.id}>\n <TableRow>\n <TableCell className=\"font-medium text-text-primary\">\n {submission.adapterId}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {submission.submitterWorkspaceId}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {formatDate(submission.createdAt)}\n </TableCell>\n <TableCell>\n <StatusBadge status={submission.status} />\n </TableCell>\n <TableCell className=\"text-right\">\n {submission.status === 'PENDING' && (\n <div className=\"flex justify-end gap-2\">\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => handleApproveAdapter(submission.id)}\n disabled={approveAdapter.isPending || rejectAdapter.isPending}\n >\n <CheckCircle2 size={14} className=\"mr-1\" />\n {tr('admin.adapters.approve', 'Approve')}\n </Button>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onClick={() => handleStartRejectAdapter(submission.id)}\n disabled={approveAdapter.isPending || rejectAdapter.isPending}\n >\n <XCircle size={14} className=\"mr-1\" />\n {tr('admin.adapters.reject', 'Reject')}\n </Button>\n </div>\n )}\n </TableCell>\n </TableRow>\n {rejectingAdapterId === submission.id && (\n <TableRow key={`${submission.id}-reject`}>\n <TableCell colSpan={5} className=\"bg-bg-sunken\">\n <div className=\"flex flex-col gap-2 py-1 sm:flex-row sm:items-start\">\n <Textarea\n value={adapterRejectReason}\n onChange={(event) => setAdapterRejectReason(event.target.value)}\n placeholder={tr(\n 'admin.adapters.rejectPlaceholder',\n 'Reason for rejecting this adapter submission (required)'\n )}\n className=\"min-h-[60px] flex-1\"\n autoFocus\n />\n <div className=\"flex gap-2 sm:flex-col\">\n <Button\n size=\"sm\"\n variant=\"destructive\"\n onClick={() => handleConfirmRejectAdapter(submission.id)}\n disabled={\n !adapterRejectReason.trim() || rejectAdapter.isPending\n }\n >\n {tr('admin.adapters.confirmReject', 'Confirm reject')}\n </Button>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => setRejectingAdapterId(null)}\n disabled={rejectAdapter.isPending}\n >\n {tr('admin.adapters.cancel', 'Cancel')}\n </Button>\n </div>\n </div>\n </TableCell>\n </TableRow>\n )}\n </Fragment>\n ))}\n </TableBody>\n </Table>\n </div>\n )}\n </CardContent>\n </Card>\n </TabsContent>\n\n {/* Marketplace moderation — backed by real GraphQL queries. */}\n <TabsContent value=\"marketplace\">\n <Card>\n <CardHeader>\n <CardTitle>{tr('admin.marketplace.title', 'Marketplace moderation')}</CardTitle>\n <CardDescription>\n {tr(\n 'admin.marketplace.description',\n 'Review reported marketplace listings and take moderation actions.'\n )}\n </CardDescription>\n </CardHeader>\n <CardContent>\n {reportsLoading ? (\n <div className=\"flex min-h-[200px] items-center justify-center\">\n <Spinner size=\"lg\" />\n </div>\n ) : marketplaceReports.length === 0 ? (\n <EmptyState\n icon={<Store className=\"size-6 text-action-primary-bg\" />}\n title={tr('admin.marketplace.emptyTitle', 'No reported listings')}\n description={tr(\n 'admin.marketplace.emptyDescription',\n 'Reported marketplace listings will appear here for moderation.'\n )}\n />\n ) : (\n <div className=\"overflow-x-auto\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>{tr('admin.marketplace.table.listing', 'Listing')}</TableHead>\n <TableHead>\n {tr('admin.marketplace.table.workspace', 'Reporting workspace')}\n </TableHead>\n <TableHead>{tr('admin.marketplace.table.reported', 'Reported')}</TableHead>\n <TableHead>{tr('admin.marketplace.table.reason', 'Reason')}</TableHead>\n <TableHead>{tr('admin.marketplace.table.status', 'Status')}</TableHead>\n <TableHead className=\"text-right\">\n {tr('admin.marketplace.table.actions', 'Actions')}\n </TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {marketplaceReports.map((report) => (\n <Fragment key={report.id}>\n <TableRow>\n <TableCell className=\"font-medium text-text-primary\">\n {report.listingId}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {report.reporterWorkspaceId}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {formatDate(report.createdAt)}\n </TableCell>\n <TableCell className=\"text-text-secondary\">{report.reason}</TableCell>\n <TableCell>\n <StatusBadge status={report.status} />\n </TableCell>\n <TableCell className=\"text-right\">\n {report.status === 'PENDING' && (\n <div className=\"flex justify-end gap-2\">\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => handleStartModerate(report.id, 'DISMISS')}\n disabled={moderateListing.isPending}\n >\n {tr('admin.marketplace.dismiss', 'Dismiss')}\n </Button>\n <Button\n size=\"sm\"\n variant=\"destructive\"\n onClick={() => handleStartModerate(report.id, 'REMOVE_LISTING')}\n disabled={moderateListing.isPending}\n >\n {tr('admin.marketplace.remove', 'Remove')}\n </Button>\n </div>\n )}\n </TableCell>\n </TableRow>\n {moderatingReportId === report.id && (\n <TableRow key={`${report.id}-moderate`}>\n <TableCell colSpan={6} className=\"bg-bg-sunken\">\n <div className=\"flex flex-col gap-2 py-1 sm:flex-row sm:items-start\">\n <Textarea\n value={moderateReason}\n onChange={(event) => setModerateReason(event.target.value)}\n placeholder={\n moderateAction === 'REMOVE_LISTING'\n ? tr(\n 'admin.marketplace.reasonPlaceholderRemove',\n 'Reason to remove this listing (required)'\n )\n : tr(\n 'admin.marketplace.reasonPlaceholderDismiss',\n 'Reason to dismiss this report (required)'\n )\n }\n className=\"min-h-[60px] flex-1\"\n autoFocus\n />\n <div className=\"flex gap-2 sm:flex-col\">\n <Button\n size=\"sm\"\n variant={\n moderateAction === 'REMOVE_LISTING'\n ? 'destructive'\n : 'outline'\n }\n onClick={() => handleConfirmModerate(report.id)}\n disabled={!moderateReason.trim() || moderateListing.isPending}\n >\n {moderateAction === 'REMOVE_LISTING'\n ? tr('admin.marketplace.confirmRemoval', 'Confirm removal')\n : tr(\n 'admin.marketplace.confirmDismissal',\n 'Confirm dismissal'\n )}\n </Button>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => setModeratingReportId(null)}\n disabled={moderateListing.isPending}\n >\n {tr('admin.marketplace.cancel', 'Cancel')}\n </Button>\n </div>\n </div>\n </TableCell>\n </TableRow>\n )}\n </Fragment>\n ))}\n </TableBody>\n </Table>\n </div>\n )}\n </CardContent>\n </Card>\n </TabsContent>\n\n {/* Bill negotiation desk (BOFF-4154). */}\n <TabsContent value=\"negotiations\">\n <Card>\n <CardHeader>\n <CardTitle>{tr('admin.desk.title', 'Negotiation desk')}</CardTitle>\n <CardDescription>\n {tr(\n 'admin.desk.description',\n 'Managed bill negotiations customers authorised, across all workspaces. Recording a successful outcome bills the success fee in global billing.'\n )}\n </CardDescription>\n </CardHeader>\n <CardContent>\n <NegotiationDeskPanel />\n </CardContent>\n </Card>\n </TabsContent>\n </Tabs>\n </PageLayout>\n );\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NegotiationDeskPanel.d.ts","sourceRoot":"","sources":["../../../src/subscriberbot/admin/NegotiationDeskPanel.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAY,KAAK,EAAE,EAAE,MAAM,OAAO,CAAC;AAoN1C,eAAO,MAAM,oBAAoB,EAAE,EA6ClC,CAAC"}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { cn as e } from "../utils/cn.js";
|
|
2
|
+
import { formatCurrency as t, formatDateTime as n, formatIntervalSuffix as r } from "../utils/formatters.js";
|
|
3
|
+
import { useTr as i } from "../hooks/useTr.js";
|
|
4
|
+
import { BILL_TYPE_LABEL as a, FEE_STATUS_LABEL as o, STATUS_LABEL as s } from "../components/negotiations/labels.js";
|
|
5
|
+
import { NegotiationStatusPill as c } from "../components/negotiations/NegotiationStatusPill.js";
|
|
6
|
+
import { NegotiationTimeline as l } from "../components/negotiations/NegotiationTimeline.js";
|
|
7
|
+
import { OutcomeForm as u } from "../components/negotiations/OutcomeForm.js";
|
|
8
|
+
import "../components/negotiations/index.js";
|
|
9
|
+
import { useDeskNegotiations as d, useDeskRecordOutcome as f, useDeskRetryFeeBilling as p, useDeskSetStatus as m, useDeskWaiveFee as h } from "../hooks/useNegotiationApi.js";
|
|
10
|
+
import { useState as g } from "react";
|
|
11
|
+
import { Fragment as _, jsx as v, jsxs as y } from "react/jsx-runtime";
|
|
12
|
+
//#region src/subscriberbot/admin/NegotiationDeskPanel.tsx
|
|
13
|
+
var b = [
|
|
14
|
+
null,
|
|
15
|
+
"SUBMITTED",
|
|
16
|
+
"IN_PROGRESS",
|
|
17
|
+
"AWAITING_CUSTOMER",
|
|
18
|
+
"SUCCEEDED",
|
|
19
|
+
"NO_SAVINGS",
|
|
20
|
+
"CANCELLED"
|
|
21
|
+
], x = "w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]", S = "rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken disabled:opacity-50", C = (e) => e ? e instanceof Error ? e.message : String(e) : null, w = ({ negotiation: e }) => {
|
|
22
|
+
let s = i(), d = m(), b = f(), w = p(), T = h(), [E, D] = g(""), [O, k] = g(!1), A = [
|
|
23
|
+
"SUBMITTED",
|
|
24
|
+
"IN_PROGRESS",
|
|
25
|
+
"AWAITING_CUSTOMER"
|
|
26
|
+
].includes(e.status), j = o[e.feeStatus], M = C(d.error) ?? C(w.error) ?? C(T.error);
|
|
27
|
+
return /* @__PURE__ */ y("li", {
|
|
28
|
+
className: "rounded-xl border border-border-subtle bg-bg-surface p-4",
|
|
29
|
+
"data-testid": "desk-item",
|
|
30
|
+
children: [
|
|
31
|
+
/* @__PURE__ */ y("div", {
|
|
32
|
+
className: "flex flex-wrap items-start justify-between gap-3",
|
|
33
|
+
children: [/* @__PURE__ */ y("div", {
|
|
34
|
+
className: "min-w-0",
|
|
35
|
+
children: [/* @__PURE__ */ y("p", {
|
|
36
|
+
className: "text-sm font-semibold text-text-primary",
|
|
37
|
+
children: [
|
|
38
|
+
e.providerName,
|
|
39
|
+
" ·",
|
|
40
|
+
" ",
|
|
41
|
+
s(a[e.billType].key, a[e.billType].fallback)
|
|
42
|
+
]
|
|
43
|
+
}), /* @__PURE__ */ y("p", {
|
|
44
|
+
className: "mt-0.5 text-xs text-text-secondary",
|
|
45
|
+
children: [
|
|
46
|
+
t(e.currentAmount, e.currency),
|
|
47
|
+
r(e.interval),
|
|
48
|
+
e.targetAmount != null && ` → ${s("admin.desk.target", "target")} ${t(e.targetAmount, e.currency)}`,
|
|
49
|
+
" · ",
|
|
50
|
+
e.accountHolderName ?? "—",
|
|
51
|
+
e.accountHint ? ` ··${e.accountHint}` : "",
|
|
52
|
+
e.submittedAt ? ` · ${n(e.submittedAt)}` : ""
|
|
53
|
+
]
|
|
54
|
+
})]
|
|
55
|
+
}), /* @__PURE__ */ y("div", {
|
|
56
|
+
className: "flex items-center gap-2",
|
|
57
|
+
children: [e.feeStatus !== "NONE" && /* @__PURE__ */ y("span", {
|
|
58
|
+
className: "text-xs text-text-secondary",
|
|
59
|
+
children: [
|
|
60
|
+
t(e.feeAmount, e.currency),
|
|
61
|
+
" · ",
|
|
62
|
+
s(j.key, j.fallback)
|
|
63
|
+
]
|
|
64
|
+
}), /* @__PURE__ */ v(c, { status: e.status })]
|
|
65
|
+
})]
|
|
66
|
+
}),
|
|
67
|
+
/* @__PURE__ */ y("div", {
|
|
68
|
+
className: "mt-3 flex flex-wrap gap-2",
|
|
69
|
+
children: [
|
|
70
|
+
(e.status === "SUBMITTED" || e.status === "AWAITING_CUSTOMER") && /* @__PURE__ */ v("button", {
|
|
71
|
+
type: "button",
|
|
72
|
+
className: S,
|
|
73
|
+
disabled: d.isPending,
|
|
74
|
+
onClick: () => d.mutate({
|
|
75
|
+
id: e.id,
|
|
76
|
+
status: "IN_PROGRESS"
|
|
77
|
+
}),
|
|
78
|
+
children: e.status === "SUBMITTED" ? s("admin.desk.pickUp", "Pick up") : s("admin.desk.resume", "Resume")
|
|
79
|
+
}),
|
|
80
|
+
e.feeStatus === "PENDING_BILLING" && /* @__PURE__ */ y(_, { children: [/* @__PURE__ */ v("button", {
|
|
81
|
+
type: "button",
|
|
82
|
+
className: S,
|
|
83
|
+
disabled: w.isPending,
|
|
84
|
+
onClick: () => w.mutate({ id: e.id }),
|
|
85
|
+
children: s("admin.desk.retryFee", "Retry fee billing")
|
|
86
|
+
}), /* @__PURE__ */ v("button", {
|
|
87
|
+
type: "button",
|
|
88
|
+
className: S,
|
|
89
|
+
disabled: T.isPending || !E.trim(),
|
|
90
|
+
onClick: () => T.mutate({
|
|
91
|
+
id: e.id,
|
|
92
|
+
reason: E.trim()
|
|
93
|
+
}),
|
|
94
|
+
children: s("admin.desk.waiveFee", "Waive fee (reason below)")
|
|
95
|
+
})] }),
|
|
96
|
+
/* @__PURE__ */ v("button", {
|
|
97
|
+
type: "button",
|
|
98
|
+
className: S,
|
|
99
|
+
onClick: () => k((e) => !e),
|
|
100
|
+
children: O ? s("admin.desk.hide", "Hide details") : s("admin.desk.show", "Playbook, notes and timeline")
|
|
101
|
+
})
|
|
102
|
+
]
|
|
103
|
+
}),
|
|
104
|
+
(A || e.feeStatus === "PENDING_BILLING") && /* @__PURE__ */ y("div", {
|
|
105
|
+
className: "mt-3 space-y-2",
|
|
106
|
+
children: [/* @__PURE__ */ v("textarea", {
|
|
107
|
+
rows: 2,
|
|
108
|
+
value: E,
|
|
109
|
+
onChange: (e) => D(e.target.value),
|
|
110
|
+
"aria-label": s("admin.desk.message", "Message to the customer or reason"),
|
|
111
|
+
placeholder: s("admin.desk.messagePlaceholder", "Message to the customer, or reason"),
|
|
112
|
+
className: x
|
|
113
|
+
}), A && /* @__PURE__ */ y("div", {
|
|
114
|
+
className: "flex flex-wrap gap-2",
|
|
115
|
+
children: [e.status === "IN_PROGRESS" && /* @__PURE__ */ v("button", {
|
|
116
|
+
type: "button",
|
|
117
|
+
className: S,
|
|
118
|
+
disabled: d.isPending || !E.trim(),
|
|
119
|
+
onClick: () => d.mutate({
|
|
120
|
+
id: e.id,
|
|
121
|
+
status: "AWAITING_CUSTOMER",
|
|
122
|
+
message: E.trim()
|
|
123
|
+
}, { onSuccess: () => D("") }),
|
|
124
|
+
children: s("admin.desk.askCustomer", "Ask the customer")
|
|
125
|
+
}), /* @__PURE__ */ v("button", {
|
|
126
|
+
type: "button",
|
|
127
|
+
className: S,
|
|
128
|
+
disabled: d.isPending,
|
|
129
|
+
onClick: () => d.mutate({
|
|
130
|
+
id: e.id,
|
|
131
|
+
status: "CANCELLED",
|
|
132
|
+
message: E.trim() || null
|
|
133
|
+
}),
|
|
134
|
+
children: s("admin.desk.close", "Close request")
|
|
135
|
+
})]
|
|
136
|
+
})]
|
|
137
|
+
}),
|
|
138
|
+
M && /* @__PURE__ */ v("p", {
|
|
139
|
+
role: "alert",
|
|
140
|
+
className: "mt-2 text-sm text-status-error-text",
|
|
141
|
+
children: M
|
|
142
|
+
}),
|
|
143
|
+
A && /* @__PURE__ */ y("div", {
|
|
144
|
+
className: "mt-4 rounded-lg border border-border-subtle p-3",
|
|
145
|
+
children: [/* @__PURE__ */ v("p", {
|
|
146
|
+
className: "mb-2 text-xs font-medium text-text-secondary",
|
|
147
|
+
children: s("admin.desk.recordOutcome", "Record the outcome")
|
|
148
|
+
}), /* @__PURE__ */ v(u, {
|
|
149
|
+
negotiation: e,
|
|
150
|
+
pending: b.isPending,
|
|
151
|
+
error: C(b.error),
|
|
152
|
+
submitLabel: s("admin.desk.saveOutcome", "Save outcome and bill fee"),
|
|
153
|
+
onSubmit: (t) => b.mutate({
|
|
154
|
+
id: e.id,
|
|
155
|
+
input: t
|
|
156
|
+
})
|
|
157
|
+
})]
|
|
158
|
+
}),
|
|
159
|
+
O && /* @__PURE__ */ y("div", {
|
|
160
|
+
className: "mt-4 space-y-3",
|
|
161
|
+
children: [
|
|
162
|
+
e.notes && /* @__PURE__ */ v("p", {
|
|
163
|
+
className: "whitespace-pre-wrap rounded-lg bg-bg-sunken p-3 text-sm text-text-primary",
|
|
164
|
+
children: e.notes
|
|
165
|
+
}),
|
|
166
|
+
e.playbook && /* @__PURE__ */ v("pre", {
|
|
167
|
+
className: "whitespace-pre-wrap break-words rounded-lg bg-bg-sunken p-3 font-sans text-xs text-text-primary",
|
|
168
|
+
children: e.playbook
|
|
169
|
+
}),
|
|
170
|
+
/* @__PURE__ */ v(l, {
|
|
171
|
+
events: e.events ?? [],
|
|
172
|
+
customerLabel: s("admin.desk.customer", "Customer")
|
|
173
|
+
})
|
|
174
|
+
]
|
|
175
|
+
})
|
|
176
|
+
]
|
|
177
|
+
});
|
|
178
|
+
}, T = () => {
|
|
179
|
+
let t = i(), [n, r] = g(null), { data: a, isLoading: o, isError: c, error: l } = d(n), u = a?.items ?? [];
|
|
180
|
+
return /* @__PURE__ */ y("div", {
|
|
181
|
+
"data-testid": "negotiation-desk",
|
|
182
|
+
children: [/* @__PURE__ */ v("div", {
|
|
183
|
+
className: "mb-4 flex flex-wrap gap-1.5",
|
|
184
|
+
children: b.map((i) => /* @__PURE__ */ v("button", {
|
|
185
|
+
type: "button",
|
|
186
|
+
"aria-pressed": n === i,
|
|
187
|
+
onClick: () => r(i),
|
|
188
|
+
className: e("rounded-lg px-3 py-1.5 text-xs font-medium", n === i ? "bg-action-primary-bg text-action-primary-text" : "border border-border-subtle text-text-primary hover:bg-bg-sunken"),
|
|
189
|
+
children: i ? t(s[i].key, s[i].fallback) : t("admin.desk.all", "All")
|
|
190
|
+
}, i ?? "ALL"))
|
|
191
|
+
}), o ? /* @__PURE__ */ v("p", {
|
|
192
|
+
className: "text-sm text-text-secondary",
|
|
193
|
+
children: t("admin.desk.loading", "Loading queue…")
|
|
194
|
+
}) : c ? /* @__PURE__ */ v("p", {
|
|
195
|
+
role: "alert",
|
|
196
|
+
className: "text-sm text-status-error-text",
|
|
197
|
+
children: C(l)
|
|
198
|
+
}) : u.length === 0 ? /* @__PURE__ */ v("p", {
|
|
199
|
+
className: "text-sm text-text-secondary",
|
|
200
|
+
children: t("admin.desk.empty", "No managed negotiations in this view.")
|
|
201
|
+
}) : /* @__PURE__ */ v("ul", {
|
|
202
|
+
className: "space-y-3",
|
|
203
|
+
children: u.map((e) => /* @__PURE__ */ v(w, { negotiation: e }, e.id))
|
|
204
|
+
})]
|
|
205
|
+
});
|
|
206
|
+
};
|
|
207
|
+
//#endregion
|
|
208
|
+
export { T as NegotiationDeskPanel };
|
|
209
|
+
|
|
210
|
+
//# sourceMappingURL=NegotiationDeskPanel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NegotiationDeskPanel.js","names":[],"sources":["../../../src/subscriberbot/admin/NegotiationDeskPanel.tsx"],"sourcesContent":["/**\n * Negotiation desk (super admin, admin-app) — BOFF-4154.\n *\n * The queue of managed bill negotiations across all workspaces, oldest\n * submission first. A negotiator picks a request up, messages the customer\n * when information is missing, records the outcome (which bills the success\n * fee in global billing) and can retry or waive an unbilled fee.\n */\n\nimport { useState, type FC } from 'react';\nimport {\n NegotiationStatusPill,\n NegotiationTimeline,\n OutcomeForm,\n STATUS_LABEL,\n BILL_TYPE_LABEL,\n FEE_STATUS_LABEL,\n} from '../components/negotiations';\nimport {\n useDeskNegotiations,\n useDeskRecordOutcome,\n useDeskRetryFeeBilling,\n useDeskSetStatus,\n useDeskWaiveFee,\n} from '../hooks/useNegotiationApi';\nimport { useTr } from '../hooks/useTr';\nimport { cn } from '../utils/cn';\nimport { formatCurrency, formatDateTime, formatIntervalSuffix } from '../utils/formatters';\nimport type { SubNegotiation, SubNegotiationStatus } from '../types/negotiation';\n\nconst QUEUE_FILTERS: Array<SubNegotiationStatus | null> = [\n null,\n 'SUBMITTED',\n 'IN_PROGRESS',\n 'AWAITING_CUSTOMER',\n 'SUCCEEDED',\n 'NO_SAVINGS',\n 'CANCELLED',\n];\n\nconst inputCls =\n 'w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]';\nconst secondaryBtn =\n 'rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken disabled:opacity-50';\n\nconst errorText = (err: unknown): string | null =>\n err ? (err instanceof Error ? err.message : String(err)) : null;\n\nconst DeskItem: FC<{ negotiation: SubNegotiation }> = ({ negotiation: n }) => {\n const tr = useTr();\n const setStatus = useDeskSetStatus();\n const record = useDeskRecordOutcome();\n const retry = useDeskRetryFeeBilling();\n const waive = useDeskWaiveFee();\n const [message, setMessage] = useState('');\n const [expanded, setExpanded] = useState(false);\n const working = ['SUBMITTED', 'IN_PROGRESS', 'AWAITING_CUSTOMER'].includes(n.status);\n const fee = FEE_STATUS_LABEL[n.feeStatus];\n const actionError =\n errorText(setStatus.error) ?? errorText(retry.error) ?? errorText(waive.error);\n\n return (\n <li\n className=\"rounded-xl border border-border-subtle bg-bg-surface p-4\"\n data-testid=\"desk-item\"\n >\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"text-sm font-semibold text-text-primary\">\n {n.providerName} ·{' '}\n {tr(BILL_TYPE_LABEL[n.billType].key, BILL_TYPE_LABEL[n.billType].fallback)}\n </p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n {formatCurrency(n.currentAmount, n.currency)}\n {formatIntervalSuffix(n.interval)}\n {n.targetAmount != null &&\n ` → ${tr('admin.desk.target', 'target')} ${formatCurrency(n.targetAmount, n.currency)}`}\n {' · '}\n {n.accountHolderName ?? '—'}\n {n.accountHint ? ` ··${n.accountHint}` : ''}\n {n.submittedAt ? ` · ${formatDateTime(n.submittedAt)}` : ''}\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n {n.feeStatus !== 'NONE' && (\n <span className=\"text-xs text-text-secondary\">\n {formatCurrency(n.feeAmount, n.currency)} · {tr(fee.key, fee.fallback)}\n </span>\n )}\n <NegotiationStatusPill status={n.status} />\n </div>\n </div>\n\n <div className=\"mt-3 flex flex-wrap gap-2\">\n {(n.status === 'SUBMITTED' || n.status === 'AWAITING_CUSTOMER') && (\n <button\n type=\"button\"\n className={secondaryBtn}\n disabled={setStatus.isPending}\n onClick={() => setStatus.mutate({ id: n.id, status: 'IN_PROGRESS' })}\n >\n {n.status === 'SUBMITTED'\n ? tr('admin.desk.pickUp', 'Pick up')\n : tr('admin.desk.resume', 'Resume')}\n </button>\n )}\n {n.feeStatus === 'PENDING_BILLING' && (\n <>\n <button\n type=\"button\"\n className={secondaryBtn}\n disabled={retry.isPending}\n onClick={() => retry.mutate({ id: n.id })}\n >\n {tr('admin.desk.retryFee', 'Retry fee billing')}\n </button>\n <button\n type=\"button\"\n className={secondaryBtn}\n disabled={waive.isPending || !message.trim()}\n onClick={() => waive.mutate({ id: n.id, reason: message.trim() })}\n >\n {tr('admin.desk.waiveFee', 'Waive fee (reason below)')}\n </button>\n </>\n )}\n <button type=\"button\" className={secondaryBtn} onClick={() => setExpanded((v) => !v)}>\n {expanded\n ? tr('admin.desk.hide', 'Hide details')\n : tr('admin.desk.show', 'Playbook, notes and timeline')}\n </button>\n </div>\n\n {(working || n.feeStatus === 'PENDING_BILLING') && (\n <div className=\"mt-3 space-y-2\">\n <textarea\n rows={2}\n value={message}\n onChange={(e) => setMessage(e.target.value)}\n aria-label={tr('admin.desk.message', 'Message to the customer or reason')}\n placeholder={tr('admin.desk.messagePlaceholder', 'Message to the customer, or reason')}\n className={inputCls}\n />\n {working && (\n <div className=\"flex flex-wrap gap-2\">\n {n.status === 'IN_PROGRESS' && (\n <button\n type=\"button\"\n className={secondaryBtn}\n disabled={setStatus.isPending || !message.trim()}\n onClick={() =>\n setStatus.mutate(\n { id: n.id, status: 'AWAITING_CUSTOMER', message: message.trim() },\n { onSuccess: () => setMessage('') }\n )\n }\n >\n {tr('admin.desk.askCustomer', 'Ask the customer')}\n </button>\n )}\n <button\n type=\"button\"\n className={secondaryBtn}\n disabled={setStatus.isPending}\n onClick={() =>\n setStatus.mutate({\n id: n.id,\n status: 'CANCELLED',\n message: message.trim() || null,\n })\n }\n >\n {tr('admin.desk.close', 'Close request')}\n </button>\n </div>\n )}\n </div>\n )}\n {actionError && (\n <p role=\"alert\" className=\"mt-2 text-sm text-status-error-text\">\n {actionError}\n </p>\n )}\n\n {working && (\n <div className=\"mt-4 rounded-lg border border-border-subtle p-3\">\n <p className=\"mb-2 text-xs font-medium text-text-secondary\">\n {tr('admin.desk.recordOutcome', 'Record the outcome')}\n </p>\n <OutcomeForm\n negotiation={n}\n pending={record.isPending}\n error={errorText(record.error)}\n submitLabel={tr('admin.desk.saveOutcome', 'Save outcome and bill fee')}\n onSubmit={(input) => record.mutate({ id: n.id, input })}\n />\n </div>\n )}\n\n {expanded && (\n <div className=\"mt-4 space-y-3\">\n {n.notes && (\n <p className=\"whitespace-pre-wrap rounded-lg bg-bg-sunken p-3 text-sm text-text-primary\">\n {n.notes}\n </p>\n )}\n {n.playbook && (\n <pre className=\"whitespace-pre-wrap break-words rounded-lg bg-bg-sunken p-3 font-sans text-xs text-text-primary\">\n {n.playbook}\n </pre>\n )}\n <NegotiationTimeline\n events={n.events ?? []}\n customerLabel={tr('admin.desk.customer', 'Customer')}\n />\n </div>\n )}\n </li>\n );\n};\n\nexport const NegotiationDeskPanel: FC = () => {\n const tr = useTr();\n const [status, setStatus] = useState<SubNegotiationStatus | null>(null);\n const { data, isLoading, isError, error } = useDeskNegotiations(status);\n const items = data?.items ?? [];\n\n return (\n <div data-testid=\"negotiation-desk\">\n <div className=\"mb-4 flex flex-wrap gap-1.5\">\n {QUEUE_FILTERS.map((f) => (\n <button\n key={f ?? 'ALL'}\n type=\"button\"\n aria-pressed={status === f}\n onClick={() => setStatus(f)}\n className={cn(\n 'rounded-lg px-3 py-1.5 text-xs font-medium',\n status === f\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'border border-border-subtle text-text-primary hover:bg-bg-sunken'\n )}\n >\n {f ? tr(STATUS_LABEL[f].key, STATUS_LABEL[f].fallback) : tr('admin.desk.all', 'All')}\n </button>\n ))}\n </div>\n {isLoading ? (\n <p className=\"text-sm text-text-secondary\">{tr('admin.desk.loading', 'Loading queue…')}</p>\n ) : isError ? (\n <p role=\"alert\" className=\"text-sm text-status-error-text\">\n {errorText(error)}\n </p>\n ) : items.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n {tr('admin.desk.empty', 'No managed negotiations in this view.')}\n </p>\n ) : (\n <ul className=\"space-y-3\">\n {items.map((n) => (\n <DeskItem key={n.id} negotiation={n} />\n ))}\n </ul>\n )}\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NegotiationStatusPill.d.ts","sourceRoot":"","sources":["../../../../src/subscriberbot/components/negotiations/NegotiationStatusPill.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAGhC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAGpE,eAAO,MAAM,qBAAqB,EAAE,EAAE,CAAC;IAAE,MAAM,EAAE,oBAAoB,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAmB1F,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { cn as e } from "../../utils/cn.js";
|
|
2
|
+
import { useTr as t } from "../../hooks/useTr.js";
|
|
3
|
+
import { STATUS_LABEL as n, statusTone as r } from "./labels.js";
|
|
4
|
+
import { jsx as i } from "react/jsx-runtime";
|
|
5
|
+
//#region src/subscriberbot/components/negotiations/NegotiationStatusPill.tsx
|
|
6
|
+
var a = ({ status: a, className: o }) => {
|
|
7
|
+
let s = t(), c = n[a];
|
|
8
|
+
return /* @__PURE__ */ i("span", {
|
|
9
|
+
"data-testid": "negotiation-status",
|
|
10
|
+
"data-status": a,
|
|
11
|
+
className: e("inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium", r(a), o),
|
|
12
|
+
children: s(c.key, c.fallback)
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
//#endregion
|
|
16
|
+
export { a as NegotiationStatusPill };
|
|
17
|
+
|
|
18
|
+
//# sourceMappingURL=NegotiationStatusPill.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NegotiationStatusPill.js","names":[],"sources":["../../../../src/subscriberbot/components/negotiations/NegotiationStatusPill.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useTr } from '../../hooks/useTr';\nimport { cn } from '../../utils/cn';\nimport type { SubNegotiationStatus } from '../../types/negotiation';\nimport { STATUS_LABEL, statusTone } from './labels';\n\nexport const NegotiationStatusPill: FC<{ status: SubNegotiationStatus; className?: string }> = ({\n status,\n className,\n}) => {\n const tr = useTr();\n const label = STATUS_LABEL[status];\n return (\n <span\n data-testid=\"negotiation-status\"\n data-status={status}\n className={cn(\n 'inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium',\n statusTone(status),\n className\n )}\n >\n {tr(label.key, label.fallback)}\n </span>\n );\n};\n"],"mappings":";;;;;AAMA,IAAa,KAAmF,EAC9F,WACA,mBACI;CACJ,IAAM,IAAK,EAAM,GACX,IAAQ,EAAa;CAC3B,OACE,kBAAC,QAAD;EACE,eAAY;EACZ,eAAa;EACb,WAAW,EACT,2EACA,EAAW,CAAM,GACjB,CACF;YAEC,EAAG,EAAM,KAAK,EAAM,QAAQ;CACzB,CAAA;AAEV"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NegotiationTimeline.d.ts","sourceRoot":"","sources":["../../../../src/subscriberbot/components/negotiations/NegotiationTimeline.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAGhC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AASnE,eAAO,MAAM,mBAAmB,EAAE,EAAE,CAAC;IAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,CAqC7F,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { formatDateTime as e } from "../../utils/formatters.js";
|
|
2
|
+
import { useTr as t } from "../../hooks/useTr.js";
|
|
3
|
+
import { EVENT_LABEL as n, STATUS_LABEL as r } from "./labels.js";
|
|
4
|
+
import { jsx as i, jsxs as a } from "react/jsx-runtime";
|
|
5
|
+
//#region src/subscriberbot/components/negotiations/NegotiationTimeline.tsx
|
|
6
|
+
var o = {
|
|
7
|
+
customer: {
|
|
8
|
+
key: "negotiations.role.customer",
|
|
9
|
+
fallback: "You"
|
|
10
|
+
},
|
|
11
|
+
negotiator: {
|
|
12
|
+
key: "negotiations.role.negotiator",
|
|
13
|
+
fallback: "Negotiation desk"
|
|
14
|
+
},
|
|
15
|
+
system: {
|
|
16
|
+
key: "negotiations.role.system",
|
|
17
|
+
fallback: "Subscriber Bot"
|
|
18
|
+
}
|
|
19
|
+
}, s = ({ events: s, customerLabel: c }) => {
|
|
20
|
+
let l = t();
|
|
21
|
+
return s.length === 0 ? null : /* @__PURE__ */ i("ol", {
|
|
22
|
+
className: "space-y-3",
|
|
23
|
+
"data-testid": "negotiation-timeline",
|
|
24
|
+
children: s.map((t) => {
|
|
25
|
+
let s = o[t.actorRole] ?? o.system, u = t.actorRole === "customer" && c ? c : l(s.key, s.fallback), d = n[t.kind], f = t.toStatus ? r[t.toStatus] : null;
|
|
26
|
+
return /* @__PURE__ */ a("li", {
|
|
27
|
+
className: "rounded-lg border border-border-subtle bg-bg-surface p-3",
|
|
28
|
+
children: [/* @__PURE__ */ a("div", {
|
|
29
|
+
className: "flex flex-wrap items-center justify-between gap-2 text-xs text-text-secondary",
|
|
30
|
+
children: [/* @__PURE__ */ a("span", {
|
|
31
|
+
className: "font-medium text-text-primary",
|
|
32
|
+
children: [l(d.key, d.fallback), f && t.kind !== "CREATED" ? ` → ${l(f.key, f.fallback)}` : ""]
|
|
33
|
+
}), /* @__PURE__ */ a("span", { children: [
|
|
34
|
+
u,
|
|
35
|
+
" · ",
|
|
36
|
+
e(t.createdAt)
|
|
37
|
+
] })]
|
|
38
|
+
}), t.message && /* @__PURE__ */ i("p", {
|
|
39
|
+
className: "mt-1.5 whitespace-pre-wrap text-sm text-text-primary",
|
|
40
|
+
children: t.message
|
|
41
|
+
})]
|
|
42
|
+
}, t.id);
|
|
43
|
+
})
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
//#endregion
|
|
47
|
+
export { s as NegotiationTimeline };
|
|
48
|
+
|
|
49
|
+
//# sourceMappingURL=NegotiationTimeline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NegotiationTimeline.js","names":[],"sources":["../../../../src/subscriberbot/components/negotiations/NegotiationTimeline.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useTr } from '../../hooks/useTr';\nimport { formatDateTime } from '../../utils/formatters';\nimport type { SubNegotiationEvent } from '../../types/negotiation';\nimport { EVENT_LABEL, STATUS_LABEL } from './labels';\n\nconst ROLE_LABEL: Record<string, { key: string; fallback: string }> = {\n customer: { key: 'negotiations.role.customer', fallback: 'You' },\n negotiator: { key: 'negotiations.role.negotiator', fallback: 'Negotiation desk' },\n system: { key: 'negotiations.role.system', fallback: 'Subscriber Bot' },\n};\n\nexport const NegotiationTimeline: FC<{ events: SubNegotiationEvent[]; customerLabel?: string }> = ({\n events,\n customerLabel,\n}) => {\n const tr = useTr();\n if (events.length === 0) return null;\n return (\n <ol className=\"space-y-3\" data-testid=\"negotiation-timeline\">\n {events.map((event) => {\n const role = ROLE_LABEL[event.actorRole] ?? ROLE_LABEL.system!;\n const who =\n event.actorRole === 'customer' && customerLabel\n ? customerLabel\n : tr(role.key, role.fallback);\n const label = EVENT_LABEL[event.kind];\n const to = event.toStatus ? STATUS_LABEL[event.toStatus] : null;\n return (\n <li key={event.id} className=\"rounded-lg border border-border-subtle bg-bg-surface p-3\">\n <div className=\"flex flex-wrap items-center justify-between gap-2 text-xs text-text-secondary\">\n <span className=\"font-medium text-text-primary\">\n {tr(label.key, label.fallback)}\n {to && event.kind !== 'CREATED' ? ` → ${tr(to.key, to.fallback)}` : ''}\n </span>\n <span>\n {who} · {formatDateTime(event.createdAt)}\n </span>\n </div>\n {event.message && (\n <p className=\"mt-1.5 whitespace-pre-wrap text-sm text-text-primary\">\n {event.message}\n </p>\n )}\n </li>\n );\n })}\n </ol>\n );\n};\n"],"mappings":";;;;;AAMA,IAAM,IAAgE;CACpE,UAAU;EAAE,KAAK;EAA8B,UAAU;CAAM;CAC/D,YAAY;EAAE,KAAK;EAAgC,UAAU;CAAmB;CAChF,QAAQ;EAAE,KAAK;EAA4B,UAAU;CAAiB;AACxE,GAEa,KAAsF,EACjG,WACA,uBACI;CACJ,IAAM,IAAK,EAAM;CAEjB,OADI,EAAO,WAAW,IAAU,OAE9B,kBAAC,MAAD;EAAI,WAAU;EAAY,eAAY;YACnC,EAAO,KAAK,MAAU;GACrB,IAAM,IAAO,EAAW,EAAM,cAAc,EAAW,QACjD,IACJ,EAAM,cAAc,cAAc,IAC9B,IACA,EAAG,EAAK,KAAK,EAAK,QAAQ,GAC1B,IAAQ,EAAY,EAAM,OAC1B,IAAK,EAAM,WAAW,EAAa,EAAM,YAAY;GAC3D,OACE,kBAAC,MAAD;IAAmB,WAAU;cAA7B,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,QAAD;MAAM,WAAU;gBAAhB,CACG,EAAG,EAAM,KAAK,EAAM,QAAQ,GAC5B,KAAM,EAAM,SAAS,YAAY,MAAM,EAAG,EAAG,KAAK,EAAG,QAAQ,MAAM,EAChE;SACN,kBAAC,QAAD,EAAA,UAAA;MACG;MAAI;MAAI,EAAe,EAAM,SAAS;KACnC,EAAA,CAAA,CACH;QACJ,EAAM,WACL,kBAAC,KAAD;KAAG,WAAU;eACV,EAAM;IACN,CAAA,CAEH;MAfK,EAAM,EAeX;EAER,CAAC;CACC,CAAA;AAER"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { FC } from 'react';
|
|
2
|
+
import { SubNegotiation, SubRecordNegotiationOutcomeInput } from '../../types/negotiation';
|
|
3
|
+
|
|
4
|
+
interface OutcomeFormProps {
|
|
5
|
+
negotiation: SubNegotiation;
|
|
6
|
+
pending: boolean;
|
|
7
|
+
error?: string | null;
|
|
8
|
+
submitLabel: string;
|
|
9
|
+
onSubmit: (input: SubRecordNegotiationOutcomeInput) => void;
|
|
10
|
+
}
|
|
11
|
+
export declare const OutcomeForm: FC<OutcomeFormProps>;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=OutcomeForm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OutcomeForm.d.ts","sourceRoot":"","sources":["../../../../src/subscriberbot/components/negotiations/OutcomeForm.tsx"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAY,KAAK,EAAE,EAAkB,MAAM,OAAO,CAAC;AAG1D,OAAO,KAAK,EACV,cAAc,EAEd,gCAAgC,EACjC,MAAM,yBAAyB,CAAC;AAMjC,UAAU,gBAAgB;IACxB,WAAW,EAAE,cAAc,CAAC;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,CAAC,KAAK,EAAE,gCAAgC,KAAK,IAAI,CAAC;CAC7D;AAED,eAAO,MAAM,WAAW,EAAE,EAAE,CAAC,gBAAgB,CAoH5C,CAAC"}
|