@burdenoff/microfe-subscriberbot 2026.706.3 → 2026.710.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/generated/global-operations.d.ts +2 -0
- package/dist/generated/global-operations.d.ts.map +1 -0
- package/dist/generated/global-types.d.ts +23527 -0
- package/dist/generated/global-types.d.ts.map +1 -0
- package/dist/generated/wspace-operations.d.ts +2 -0
- package/dist/generated/wspace-operations.d.ts.map +1 -0
- package/dist/generated/wspace-types.d.ts +57851 -0
- package/dist/generated/wspace-types.d.ts.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -1
- package/dist/subscriberbot/SubscriberBotRoot.d.ts +5 -0
- package/dist/subscriberbot/SubscriberBotRoot.d.ts.map +1 -0
- package/dist/subscriberbot/SubscriberBotRoot.js +56 -40
- package/dist/subscriberbot/SubscriberBotRoot.js.map +1 -1
- package/dist/subscriberbot/SubscriberBotRoutes.d.ts +4 -0
- package/dist/subscriberbot/SubscriberBotRoutes.d.ts.map +1 -0
- package/dist/subscriberbot/SubscriberBotRoutes.js.map +1 -1
- package/dist/subscriberbot/components/EmptyState.d.ts +18 -0
- package/dist/subscriberbot/components/EmptyState.d.ts.map +1 -0
- package/dist/subscriberbot/components/MetricsCard.d.ts +16 -0
- package/dist/subscriberbot/components/MetricsCard.d.ts.map +1 -0
- package/dist/subscriberbot/components/Modal.d.ts +13 -0
- package/dist/subscriberbot/components/Modal.d.ts.map +1 -0
- package/dist/subscriberbot/components/PageLayout.d.ts +23 -0
- package/dist/subscriberbot/components/PageLayout.d.ts.map +1 -0
- package/dist/subscriberbot/components/StateViews.d.ts +24 -0
- package/dist/subscriberbot/components/StateViews.d.ts.map +1 -0
- package/dist/subscriberbot/components/StatusBadge.d.ts +9 -0
- package/dist/subscriberbot/components/StatusBadge.d.ts.map +1 -0
- package/dist/subscriberbot/components/index.d.ts +13 -0
- package/dist/subscriberbot/components/index.d.ts.map +1 -0
- package/dist/subscriberbot/components/viz.d.ts +58 -0
- package/dist/subscriberbot/components/viz.d.ts.map +1 -0
- package/dist/subscriberbot/components/viz.js.map +1 -1
- package/dist/subscriberbot/config/assistantUsecases.d.ts +13 -0
- package/dist/subscriberbot/config/assistantUsecases.d.ts.map +1 -0
- package/dist/subscriberbot/context/SubscriberBotContext.d.ts +16 -0
- package/dist/subscriberbot/context/SubscriberBotContext.d.ts.map +1 -0
- package/dist/subscriberbot/context/index.d.ts +6 -0
- package/dist/subscriberbot/context/index.d.ts.map +1 -0
- package/dist/subscriberbot/hooks/index.d.ts +7 -0
- package/dist/subscriberbot/hooks/index.d.ts.map +1 -0
- package/dist/subscriberbot/hooks/useSubscriberBotApi.d.ts +105 -0
- package/dist/subscriberbot/hooks/useSubscriberBotApi.d.ts.map +1 -0
- package/dist/subscriberbot/index.d.ts +12 -0
- package/dist/subscriberbot/index.d.ts.map +1 -0
- package/dist/subscriberbot/index.js +1 -0
- package/dist/subscriberbot/pages/AssetVaultPage.d.ts +4 -0
- package/dist/subscriberbot/pages/AssetVaultPage.d.ts.map +1 -0
- package/dist/subscriberbot/pages/AssetVaultPage.js.map +1 -1
- package/dist/subscriberbot/pages/DashboardPage.d.ts +12 -0
- package/dist/subscriberbot/pages/DashboardPage.d.ts.map +1 -0
- package/dist/subscriberbot/pages/DashboardPage.js.map +1 -1
- package/dist/subscriberbot/pages/InboxPage.d.ts +12 -0
- package/dist/subscriberbot/pages/InboxPage.d.ts.map +1 -0
- package/dist/subscriberbot/pages/InboxPage.js.map +1 -1
- package/dist/subscriberbot/pages/InsightsPage.d.ts +4 -0
- package/dist/subscriberbot/pages/InsightsPage.d.ts.map +1 -0
- package/dist/subscriberbot/pages/InsightsPage.js.map +1 -1
- package/dist/subscriberbot/pages/InvoicesPage.d.ts +13 -0
- package/dist/subscriberbot/pages/InvoicesPage.d.ts.map +1 -0
- package/dist/subscriberbot/pages/InvoicesPage.js.map +1 -1
- package/dist/subscriberbot/pages/WorkflowsPage.d.ts +15 -0
- package/dist/subscriberbot/pages/WorkflowsPage.d.ts.map +1 -0
- package/dist/subscriberbot/pages/WorkflowsPage.js.map +1 -1
- package/dist/subscriberbot/pages/agents/AgentDetailPage.d.ts +11 -0
- package/dist/subscriberbot/pages/agents/AgentDetailPage.d.ts.map +1 -0
- package/dist/subscriberbot/pages/agents/AgentNewPage.d.ts +8 -0
- package/dist/subscriberbot/pages/agents/AgentNewPage.d.ts.map +1 -0
- package/dist/subscriberbot/pages/agents/AgentsListPage.d.ts +11 -0
- package/dist/subscriberbot/pages/agents/AgentsListPage.d.ts.map +1 -0
- package/dist/subscriberbot/pages/agents/AgentsListPage.js.map +1 -1
- package/dist/subscriberbot/pages/providers/ProviderDetailPage.d.ts +10 -0
- package/dist/subscriberbot/pages/providers/ProviderDetailPage.d.ts.map +1 -0
- package/dist/subscriberbot/pages/providers/ProviderDetailPage.js.map +1 -1
- package/dist/subscriberbot/pages/providers/ProviderNewPage.d.ts +10 -0
- package/dist/subscriberbot/pages/providers/ProviderNewPage.d.ts.map +1 -0
- package/dist/subscriberbot/pages/providers/ProvidersListPage.d.ts +12 -0
- package/dist/subscriberbot/pages/providers/ProvidersListPage.d.ts.map +1 -0
- package/dist/subscriberbot/pages/providers/ProvidersListPage.js.map +1 -1
- package/dist/subscriberbot/pages/relationships/RelationshipDetailPage.d.ts +13 -0
- package/dist/subscriberbot/pages/relationships/RelationshipDetailPage.d.ts.map +1 -0
- package/dist/subscriberbot/pages/relationships/RelationshipDetailPage.js.map +1 -1
- package/dist/subscriberbot/pages/relationships/RelationshipNewPage.d.ts +10 -0
- package/dist/subscriberbot/pages/relationships/RelationshipNewPage.d.ts.map +1 -0
- package/dist/subscriberbot/pages/relationships/RelationshipNewPage.js.map +1 -1
- package/dist/subscriberbot/pages/relationships/RelationshipsListPage.d.ts +10 -0
- package/dist/subscriberbot/pages/relationships/RelationshipsListPage.d.ts.map +1 -0
- package/dist/subscriberbot/pages/relationships/RelationshipsListPage.js.map +1 -1
- package/dist/subscriberbot/services/assistantApi.d.ts +66 -0
- package/dist/subscriberbot/services/assistantApi.d.ts.map +1 -0
- package/dist/subscriberbot/services/assistantApi.js +260 -0
- package/dist/subscriberbot/services/assistantApi.js.map +1 -0
- package/dist/subscriberbot/services/createSandboxAssistantTransport.d.ts +13 -0
- package/dist/subscriberbot/services/createSandboxAssistantTransport.d.ts.map +1 -0
- package/dist/subscriberbot/services/createSandboxAssistantTransport.js +120 -0
- package/dist/subscriberbot/services/createSandboxAssistantTransport.js.map +1 -0
- package/dist/subscriberbot/store/index.d.ts +6 -0
- package/dist/subscriberbot/store/index.d.ts.map +1 -0
- package/dist/subscriberbot/store/subscriberbotStore.d.ts +60 -0
- package/dist/subscriberbot/store/subscriberbotStore.d.ts.map +1 -0
- package/dist/subscriberbot/types/assistant.d.ts +57 -0
- package/dist/subscriberbot/types/assistant.d.ts.map +1 -0
- package/dist/subscriberbot/types/index.d.ts +465 -0
- package/dist/subscriberbot/types/index.d.ts.map +1 -0
- package/dist/subscriberbot/utils/cn.d.ts +4 -0
- package/dist/subscriberbot/utils/cn.d.ts.map +1 -0
- package/dist/subscriberbot/utils/formatters.d.ts +53 -0
- package/dist/subscriberbot/utils/formatters.d.ts.map +1 -0
- package/dist/subscriberbot/utils/index.d.ts +8 -0
- package/dist/subscriberbot/utils/index.d.ts.map +1 -0
- package/dist/subscriberbot/utils/navigation.d.ts +20 -0
- package/dist/subscriberbot/utils/navigation.d.ts.map +1 -0
- package/dist/subscriberbot/utils/pageContext.d.ts +21 -0
- package/dist/subscriberbot/utils/pageContext.d.ts.map +1 -0
- package/dist/subscriberbot/utils/pageContext.js +47 -0
- package/dist/subscriberbot/utils/pageContext.js.map +1 -0
- package/package.json +5 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InvoicesPage.js","names":[],"sources":["../../../src/subscriberbot/pages/InvoicesPage.tsx"],"sourcesContent":["/**\n * Invoices\n * @module subscriberbot/pages\n *\n * The Universal Billing Layer's invoice ledger across every relationship, plus\n * the passive payment-method annotations that pay for them. Filter by status,\n * mark issued/overdue invoices paid, create invoices, and manage payment\n * methods. Reads `subListInvoices` / `subListPaymentInstruments`; writes\n * `subMarkInvoicePaid`, `subCreateInvoice`, `subCreatePaymentInstrument`,\n * `subRemovePaymentInstrument`.\n */\n\nimport { useMemo, useState } from 'react';\nimport {\n Receipt,\n FileText,\n Plus,\n CreditCard,\n Trash2,\n Star,\n AlertTriangle,\n CheckCircle2,\n} from 'lucide-react';\nimport { PagePurpose, GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../components/PageLayout';\nimport { MetricsCard } from '../components/MetricsCard';\nimport { StatusBadge } from '../components/StatusBadge';\nimport { EmptyState } from '../components/EmptyState';\nimport { Modal } from '../components/Modal';\nimport { LoadingView, ErrorView, Section } from '../components/StateViews';\nimport { Donut, Legend, CHART_COLORS } from '../components/viz';\nimport type { Segment } from '../components/viz';\nimport { useSubscriberBot } from '../context/SubscriberBotContext';\nimport {\n useInvoices,\n useMarkInvoicePaid,\n useCreateInvoice,\n useRelationships,\n usePaymentInstruments,\n useCreatePaymentInstrument,\n useRemovePaymentInstrument,\n} from '../hooks/useSubscriberBotApi';\nimport { cn } from '../utils/cn';\nimport { formatCurrency, formatDate } from '../utils/formatters';\nimport type { SubInvoiceStatus, SubPaymentInstrumentType } from '../types';\n\nconst PAGE_SIZE = 25;\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 labelCls = 'mb-1 block text-xs font-medium text-text-secondary';\n\nconst STATUS_OPTIONS: Array<{ value: '' | SubInvoiceStatus; label: string }> = [\n { value: '', label: 'All statuses' },\n { value: 'ISSUED', label: 'Issued' },\n { value: 'OVERDUE', label: 'Overdue' },\n { value: 'PAID', label: 'Paid' },\n { value: 'FAILED', label: 'Failed' },\n { value: 'DRAFT', label: 'Draft' },\n { value: 'CANCELLED', label: 'Cancelled' },\n { value: 'REFUNDED', label: 'Refunded' },\n];\n\nconst INVOICE_STATUSES: SubInvoiceStatus[] = [\n 'ISSUED',\n 'DRAFT',\n 'OVERDUE',\n 'PAID',\n 'FAILED',\n 'CANCELLED',\n 'REFUNDED',\n];\n\nconst PAYMENT_TYPES: SubPaymentInstrumentType[] = [\n 'CARD',\n 'BANK_ACCOUNT',\n 'PAYPAL',\n 'UPI',\n 'WALLET',\n 'OTHER',\n];\n\nconst PAYABLE = new Set<SubInvoiceStatus>(['ISSUED', 'OVERDUE', 'FAILED']);\n\n// Semantic tints for the status-distribution donut (fall back to chart tokens).\nconst STATUS_TINT: Partial<Record<SubInvoiceStatus, string>> = {\n PAID: 'var(--color-status-success-text)',\n OVERDUE: 'var(--color-status-error-text)',\n FAILED: 'var(--color-status-error-text)',\n ISSUED: 'var(--color-chart-1)',\n DRAFT: 'var(--color-text-secondary)',\n CANCELLED: 'var(--color-chart-4)',\n REFUNDED: 'var(--color-status-warning-text)',\n};\n\n// ---------------------------------------------------------------------------\n// Create invoice modal → subCreateInvoice\n// ---------------------------------------------------------------------------\n\nfunction CreateInvoiceModal({ onClose }: { onClose: () => void }) {\n const create = useCreateInvoice();\n const relationshipsQ = useRelationships({ pagination: { limit: 100, offset: 0 } });\n const relationships = relationshipsQ.data?.items ?? [];\n\n const [relationshipId, setRelationshipId] = useState('');\n const [number, setNumber] = useState('');\n const [status, setStatus] = useState<SubInvoiceStatus>('ISSUED');\n const [currency, setCurrency] = useState('USD');\n const [subtotal, setSubtotal] = useState('');\n const [taxAmount, setTaxAmount] = useState('');\n const [total, setTotal] = useState('');\n const [issueDate, setIssueDate] = useState('');\n const [dueDate, setDueDate] = useState('');\n const [formError, setFormError] = useState<string | null>(null);\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n setFormError(null);\n if (!relationshipId) {\n setFormError('Select the relationship this invoice belongs to.');\n return;\n }\n create.mutate(\n {\n relationshipId,\n status,\n currency: currency.trim() || 'USD',\n ...(number.trim() ? { number: number.trim() } : {}),\n ...(subtotal.trim() ? { subtotal: Number(subtotal) } : {}),\n ...(taxAmount.trim() ? { taxAmount: Number(taxAmount) } : {}),\n ...(total.trim() ? { total: Number(total) } : {}),\n ...(issueDate ? { issueDate: new Date(issueDate).toISOString() } : {}),\n ...(dueDate ? { dueDate: new Date(dueDate).toISOString() } : {}),\n },\n {\n onSuccess: () => onClose(),\n onError: (err) =>\n setFormError(err instanceof Error ? err.message : 'Could not create invoice.'),\n }\n );\n };\n\n return (\n <Modal title=\"Create invoice\" onClose={onClose} maxWidthClassName=\"max-w-xl\">\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {formError && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n <div>\n <label htmlFor=\"inv-relationship\" className={labelCls}>\n Relationship <span className=\"text-status-error-text\">*</span>\n </label>\n <select\n id=\"inv-relationship\"\n value={relationshipId}\n onChange={(e) => setRelationshipId(e.target.value)}\n className={inputCls}\n required\n >\n <option value=\"\">— Select a relationship —</option>\n {relationships.map((r) => (\n <option key={r.id} value={r.id}>\n {r.label}\n </option>\n ))}\n </select>\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div>\n <label htmlFor=\"inv-number\" className={labelCls}>\n Number\n </label>\n <input\n id=\"inv-number\"\n type=\"text\"\n value={number}\n onChange={(e) => setNumber(e.target.value)}\n placeholder=\"INV-0001\"\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"inv-status\" className={labelCls}>\n Status\n </label>\n <select\n id=\"inv-status\"\n value={status}\n onChange={(e) => setStatus(e.target.value as SubInvoiceStatus)}\n className={inputCls}\n >\n {INVOICE_STATUSES.map((s) => (\n <option key={s} value={s}>\n {s.replace(/_/g, ' ')}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"inv-currency\" className={labelCls}>\n Currency\n </label>\n <input\n id=\"inv-currency\"\n type=\"text\"\n value={currency}\n onChange={(e) => setCurrency(e.target.value.toUpperCase())}\n maxLength={3}\n className={inputCls}\n />\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div>\n <label htmlFor=\"inv-subtotal\" className={labelCls}>\n Subtotal\n </label>\n <input\n id=\"inv-subtotal\"\n type=\"number\"\n min=\"0\"\n step=\"0.01\"\n value={subtotal}\n onChange={(e) => setSubtotal(e.target.value)}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"inv-tax\" className={labelCls}>\n Tax\n </label>\n <input\n id=\"inv-tax\"\n type=\"number\"\n min=\"0\"\n step=\"0.01\"\n value={taxAmount}\n onChange={(e) => setTaxAmount(e.target.value)}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"inv-total\" className={labelCls}>\n Total\n </label>\n <input\n id=\"inv-total\"\n type=\"number\"\n min=\"0\"\n step=\"0.01\"\n value={total}\n onChange={(e) => setTotal(e.target.value)}\n className={inputCls}\n />\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"inv-issue\" className={labelCls}>\n Issue date\n </label>\n <input\n id=\"inv-issue\"\n type=\"date\"\n value={issueDate}\n onChange={(e) => setIssueDate(e.target.value)}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"inv-due\" className={labelCls}>\n Due date\n </label>\n <input\n id=\"inv-due\"\n type=\"date\"\n value={dueDate}\n onChange={(e) => setDueDate(e.target.value)}\n className={inputCls}\n />\n </div>\n </div>\n <div className=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={create.isPending}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {create.isPending ? 'Creating…' : 'Create invoice'}\n </button>\n </div>\n </form>\n </Modal>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Add payment method modal → subCreatePaymentInstrument\n// ---------------------------------------------------------------------------\n\nfunction AddPaymentMethodModal({ onClose }: { onClose: () => void }) {\n const create = useCreatePaymentInstrument();\n const [label, setLabel] = useState('');\n const [type, setType] = useState<SubPaymentInstrumentType>('CARD');\n const [brand, setBrand] = useState('');\n const [last4, setLast4] = useState('');\n const [expiryMonth, setExpiryMonth] = useState('');\n const [expiryYear, setExpiryYear] = useState('');\n const [isDefault, setIsDefault] = useState(false);\n const [formError, setFormError] = useState<string | null>(null);\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n setFormError(null);\n if (!label.trim()) {\n setFormError('A label is required.');\n return;\n }\n create.mutate(\n {\n label: label.trim(),\n type,\n isDefault,\n ...(brand.trim() ? { brand: brand.trim() } : {}),\n ...(last4.trim() ? { last4: last4.trim() } : {}),\n ...(expiryMonth.trim() ? { expiryMonth: Number(expiryMonth) } : {}),\n ...(expiryYear.trim() ? { expiryYear: Number(expiryYear) } : {}),\n },\n {\n onSuccess: () => onClose(),\n onError: (err) =>\n setFormError(err instanceof Error ? err.message : 'Could not add payment method.'),\n }\n );\n };\n\n return (\n <Modal\n title=\"Add payment method\"\n description=\"A passive annotation of which external card/account pays for a subscription — no full card numbers or CVV.\"\n onClose={onClose}\n maxWidthClassName=\"max-w-xl\"\n >\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {formError && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"pm-label\" className={labelCls}>\n Label <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"pm-label\"\n type=\"text\"\n value={label}\n onChange={(e) => setLabel(e.target.value)}\n placeholder=\"e.g. Personal Visa\"\n className={inputCls}\n required\n />\n </div>\n <div>\n <label htmlFor=\"pm-type\" className={labelCls}>\n Type\n </label>\n <select\n id=\"pm-type\"\n value={type}\n onChange={(e) => setType(e.target.value as SubPaymentInstrumentType)}\n className={inputCls}\n >\n {PAYMENT_TYPES.map((t) => (\n <option key={t} value={t}>\n {t.replace(/_/g, ' ')}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div>\n <label htmlFor=\"pm-brand\" className={labelCls}>\n Brand\n </label>\n <input\n id=\"pm-brand\"\n type=\"text\"\n value={brand}\n onChange={(e) => setBrand(e.target.value)}\n placeholder=\"Visa, Amex…\"\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"pm-last4\" className={labelCls}>\n Last 4\n </label>\n <input\n id=\"pm-last4\"\n type=\"text\"\n inputMode=\"numeric\"\n maxLength={4}\n value={last4}\n onChange={(e) => setLast4(e.target.value.replace(/\\D/g, ''))}\n placeholder=\"4242\"\n className={inputCls}\n />\n </div>\n <div className=\"grid grid-cols-2 gap-2\">\n <div>\n <label htmlFor=\"pm-exp-month\" className={labelCls}>\n Exp. mo.\n </label>\n <input\n id=\"pm-exp-month\"\n type=\"number\"\n min=\"1\"\n max=\"12\"\n value={expiryMonth}\n onChange={(e) => setExpiryMonth(e.target.value)}\n placeholder=\"12\"\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"pm-exp-year\" className={labelCls}>\n Exp. yr.\n </label>\n <input\n id=\"pm-exp-year\"\n type=\"number\"\n min=\"2024\"\n max=\"2099\"\n value={expiryYear}\n onChange={(e) => setExpiryYear(e.target.value)}\n placeholder=\"2028\"\n className={inputCls}\n />\n </div>\n </div>\n </div>\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={isDefault}\n onChange={(e) => setIsDefault(e.target.checked)}\n className=\"size-4 rounded border-border-subtle\"\n />\n Set as default\n </label>\n <div className=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={create.isPending}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {create.isPending ? 'Adding…' : 'Add payment method'}\n </button>\n </div>\n </form>\n </Modal>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Payment methods panel\n// ---------------------------------------------------------------------------\n\nfunction PaymentMethodsSection() {\n const { data, isLoading, isError, refetch } = usePaymentInstruments();\n const remove = useRemovePaymentInstrument();\n const [showAdd, setShowAdd] = useState(false);\n\n const instruments = data ?? [];\n\n return (\n <Section\n title=\"Payment methods\"\n description=\"Which external cards and accounts pay for your subscriptions\"\n action={\n <button\n type=\"button\"\n onClick={() => setShowAdd(true)}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <Plus className=\"h-3.5 w-3.5\" /> Add payment method\n </button>\n }\n >\n {isLoading ? (\n <LoadingView label=\"Loading payment methods…\" />\n ) : isError ? (\n <ErrorView message=\"Could not load payment methods.\" onRetry={() => void refetch()} />\n ) : instruments.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<CreditCard className=\"h-12 w-12\" />}\n title=\"No payment methods\"\n description=\"Annotate which card or account pays for each subscription. No full card numbers are stored.\"\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-3\">\n {instruments.map((pi) => {\n const meta = [\n pi.expiryMonth && pi.expiryYear\n ? `Exp ${String(pi.expiryMonth).padStart(2, '0')}/${String(pi.expiryYear).slice(-2)}`\n : null,\n ].filter(Boolean);\n return (\n <GlassCard\n key={pi.id}\n treatment=\"metric\"\n className=\"flex flex-col gap-4 p-4\"\n >\n {/* Card face */}\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex items-center gap-3\">\n <span className=\"grid h-9 w-12 place-items-center rounded-md bg-[var(--color-accent-soft)] text-text-primary\">\n <CreditCard className=\"h-4 w-4\" aria-hidden />\n </span>\n <div className=\"min-w-0\">\n <p className=\"flex items-center gap-1.5 truncate text-sm font-semibold text-text-primary\">\n {pi.brand || pi.label}\n {pi.isDefault && (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-status-success-bg-subtle px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-status-success-text\">\n <Star className=\"h-2.5 w-2.5 fill-current\" aria-hidden />\n Default\n </span>\n )}\n </p>\n <p className=\"font-mono text-sm tracking-[0.15em] text-text-secondary\">\n {pi.last4 ? `•••• ${pi.last4}` : pi.type.replace(/_/g, ' ')}\n </p>\n </div>\n </div>\n <StatusBadge status={pi.status} />\n </div>\n\n <div className=\"mt-auto flex items-center justify-between border-t border-border-subtle pt-3\">\n <p className=\"truncate text-xs text-text-secondary\">\n {[pi.label, ...meta].filter(Boolean).join(' · ')}\n </p>\n <button\n type=\"button\"\n aria-label={`Remove ${pi.label}`}\n disabled={remove.isPending}\n onClick={() => remove.mutate(pi.id)}\n className=\"inline-flex shrink-0 items-center gap-1 rounded-lg border border-border-subtle px-2.5 py-1 text-xs font-medium text-text-secondary hover:border-status-error-text/40 hover:bg-status-error-bg-subtle hover:text-status-error-text disabled:opacity-50\"\n >\n <Trash2 className=\"h-3.5 w-3.5\" aria-hidden />\n Remove\n </button>\n </div>\n </GlassCard>\n );\n })}\n </div>\n )}\n\n {showAdd && <AddPaymentMethodModal onClose={() => setShowAdd(false)} />}\n </Section>\n );\n}\n\nexport function InvoicesPage() {\n const { navigateTo } = useSubscriberBot();\n const [page, setPage] = useState(0);\n const [status, setStatus] = useState<'' | SubInvoiceStatus>('');\n const [showCreate, setShowCreate] = useState(false);\n\n const { data, isLoading, isError, refetch, isFetching } = useInvoices({\n status: status || undefined,\n pagination: { limit: PAGE_SIZE, offset: page * PAGE_SIZE },\n });\n const markPaid = useMarkInvoicePaid();\n\n const items = useMemo(() => data?.items ?? [], [data]);\n const hasMore = data?.pageInfo.hasMore ?? false;\n\n const totals = useMemo(() => {\n const outstanding = items.filter((i) => PAYABLE.has(i.status));\n const currency = items[0]?.currency ?? 'USD';\n\n // Status distribution → donut segments (only statuses actually present).\n const byStatus = new Map<SubInvoiceStatus, number>();\n for (const i of items) byStatus.set(i.status, (byStatus.get(i.status) ?? 0) + 1);\n const distribution: Segment[] = Array.from(byStatus.entries())\n .sort((a, b) => b[1] - a[1])\n .map(([s, count], idx) => ({\n label: s.charAt(0) + s.slice(1).toLowerCase(),\n value: count,\n color: STATUS_TINT[s] ?? CHART_COLORS[idx % CHART_COLORS.length],\n }));\n\n return {\n outstandingCount: outstanding.length,\n outstandingTotal: outstanding.reduce((sum, i) => sum + i.total, 0),\n paidCount: items.filter((i) => i.status === 'PAID').length,\n overdueCount: items.filter((i) => i.status === 'OVERDUE').length,\n distribution,\n currency,\n };\n }, [items]);\n\n return (\n <PageLayout\n title=\"Invoices\"\n description=\"Your billing ledger across every relationship\"\n actions={\n <button\n type=\"button\"\n onClick={() => setShowCreate(true)}\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n <Plus className=\"h-4 w-4\" /> Create invoice\n </button>\n }\n >\n <PagePurpose className=\"mb-4\">\n Every invoice from your recurring relationships in one ledger. Filter by status and settle\n issued or overdue invoices directly.\n </PagePurpose>\n\n {!isLoading && !isError && items.length > 0 && (\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-3\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:col-span-2\">\n <MetricsCard\n featured\n icon={<Receipt />}\n value={formatCurrency(totals.outstandingTotal, totals.currency, {\n maximumFractionDigits: 0,\n })}\n label=\"Outstanding · this page\"\n />\n <MetricsCard\n icon={<FileText />}\n value={totals.outstandingCount}\n label=\"Open invoices\"\n />\n <MetricsCard\n icon={<AlertTriangle />}\n value={totals.overdueCount}\n label=\"Overdue\"\n {...(totals.overdueCount > 0 ? { trend: 'down' as const } : {})}\n />\n <MetricsCard\n icon={<CheckCircle2 />}\n value={totals.paidCount}\n label=\"Paid\"\n {...(totals.paidCount > 0 ? { trend: 'up' as const } : {})}\n />\n </div>\n\n <GlassCard treatment=\"insight\" className=\"p-6\">\n <p className=\"text-sm font-semibold text-text-primary\">Status breakdown</p>\n <p className=\"text-xs text-text-secondary\">Across the invoices on this page</p>\n <div className=\"mt-4 flex flex-col items-center gap-5 sm:flex-row sm:items-center lg:flex-col xl:flex-row\">\n <Donut\n data={totals.distribution}\n size={132}\n thickness={16}\n centerLabel={String(items.length)}\n centerSub=\"invoices\"\n className=\"shrink-0\"\n />\n <Legend\n data={totals.distribution}\n className=\"w-full min-w-0 flex-1\"\n valueFormat={(n) => String(n)}\n />\n </div>\n </GlassCard>\n </div>\n )}\n\n <div className=\"mt-6 flex flex-wrap items-center gap-3\">\n <select\n aria-label=\"Filter by status\"\n value={status}\n onChange={(e) => {\n setStatus(e.target.value as '' | SubInvoiceStatus);\n setPage(0);\n }}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-xs text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n {STATUS_OPTIONS.map((o) => (\n <option key={o.value} value={o.value}>\n {o.label}\n </option>\n ))}\n </select>\n </div>\n\n {isLoading ? (\n <LoadingView label=\"Loading invoices…\" />\n ) : isError ? (\n <ErrorView message=\"Could not load invoices.\" onRetry={() => void refetch()} />\n ) : items.length === 0 ? (\n <div className=\"mt-6\">\n <EmptyState\n illustration=\"empty-data\"\n icon={<Receipt className=\"h-12 w-12\" />}\n title=\"No invoices\"\n description={\n status\n ? 'No invoices match this status filter.'\n : 'Invoices from your relationships will appear here — or create one manually.'\n }\n action={\n <button\n type=\"button\"\n onClick={() => setShowCreate(true)}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n Create invoice\n </button>\n }\n />\n </div>\n ) : (\n <>\n <div className=\"mt-6 overflow-hidden rounded-lg border border-border-subtle bg-bg-surface\">\n <div className=\"hidden grid-cols-[1.5fr_1fr_1fr_1fr_auto] gap-4 border-b border-border-subtle bg-bg-sunken px-4 py-2.5 text-xs font-medium text-text-secondary sm:grid\">\n <span>Invoice</span>\n <span>Status</span>\n <span className=\"text-right\">Total</span>\n <span className=\"text-right\">Due</span>\n <span className=\"text-right\">Action</span>\n </div>\n <div className=\"divide-y divide-border-subtle\">\n {items.map((inv) => (\n <div\n key={inv.id}\n className=\"grid grid-cols-1 gap-2 px-4 py-3 transition-colors hover:bg-bg-sunken/50 sm:grid-cols-[1.5fr_1fr_1fr_1fr_auto] sm:items-center sm:gap-4\"\n >\n <button\n type=\"button\"\n onClick={() => navigateTo(`/relationships/${inv.relationshipId}`)}\n className=\"min-w-0 text-left\"\n >\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {inv.number || `Invoice ${inv.id.slice(0, 8)}`}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {inv.issueDate ? `Issued ${formatDate(inv.issueDate)}` : '—'}\n </p>\n </button>\n <span>\n <StatusBadge status={inv.status} />\n </span>\n <span className=\"text-sm font-semibold tabular-nums text-text-primary sm:text-right\">\n {formatCurrency(inv.total, inv.currency)}\n </span>\n <span className=\"text-xs text-text-secondary sm:text-right\">\n {inv.dueDate ? formatDate(inv.dueDate) : '—'}\n </span>\n <span className=\"sm:text-right\">\n {PAYABLE.has(inv.status) ? (\n <button\n type=\"button\"\n disabled={markPaid.isPending}\n onClick={() => markPaid.mutate(inv.id)}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-status-success-text/30 bg-status-success-bg-subtle px-3 py-1.5 text-xs font-medium text-status-success-text hover:opacity-90 disabled:opacity-50\"\n >\n <CheckCircle2 className=\"h-3.5 w-3.5\" aria-hidden />\n Mark paid\n </button>\n ) : inv.paidAt ? (\n <span className=\"text-xs text-text-secondary\">\n Paid {formatDate(inv.paidAt)}\n </span>\n ) : (\n <span className=\"text-xs text-text-secondary\">—</span>\n )}\n </span>\n </div>\n ))}\n </div>\n </div>\n\n <div className=\"mt-4 flex items-center justify-between\">\n <p className=\"text-xs text-text-secondary\">\n Page {page + 1}\n {isFetching ? ' · refreshing…' : ''}\n </p>\n <div className=\"flex gap-2\">\n <button\n type=\"button\"\n disabled={page === 0}\n onClick={() => setPage((p) => Math.max(0, p - 1))}\n className={cn(\n 'rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium',\n page === 0\n ? 'cursor-not-allowed text-text-secondary opacity-50'\n : 'text-text-primary hover:bg-bg-sunken'\n )}\n >\n Previous\n </button>\n <button\n type=\"button\"\n disabled={!hasMore}\n onClick={() => setPage((p) => p + 1)}\n className={cn(\n 'rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium',\n !hasMore\n ? 'cursor-not-allowed text-text-secondary opacity-50'\n : 'text-text-primary hover:bg-bg-sunken'\n )}\n >\n Next\n </button>\n </div>\n </div>\n </>\n )}\n\n <PaymentMethodsSection />\n\n {showCreate && <CreateInvoiceModal onClose={() => setShowCreate(false)} />}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"InvoicesPage.js","names":[],"sources":["../../../src/subscriberbot/pages/InvoicesPage.tsx"],"sourcesContent":["/**\n * Invoices\n * @module subscriberbot/pages\n *\n * The Universal Billing Layer's invoice ledger across every relationship, plus\n * the passive payment-method annotations that pay for them. Filter by status,\n * mark issued/overdue invoices paid, create invoices, and manage payment\n * methods. Reads `subListInvoices` / `subListPaymentInstruments`; writes\n * `subMarkInvoicePaid`, `subCreateInvoice`, `subCreatePaymentInstrument`,\n * `subRemovePaymentInstrument`.\n */\n\nimport { useMemo, useState } from 'react';\nimport {\n Receipt,\n FileText,\n Plus,\n CreditCard,\n Trash2,\n Star,\n AlertTriangle,\n CheckCircle2,\n} from 'lucide-react';\nimport { PagePurpose, GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../components/PageLayout';\nimport { MetricsCard } from '../components/MetricsCard';\nimport { StatusBadge } from '../components/StatusBadge';\nimport { EmptyState } from '../components/EmptyState';\nimport { Modal } from '../components/Modal';\nimport { LoadingView, ErrorView, Section } from '../components/StateViews';\nimport { Donut, Legend, CHART_COLORS } from '../components/viz';\nimport type { Segment } from '../components/viz';\nimport { useSubscriberBot } from '../context/SubscriberBotContext';\nimport {\n useInvoices,\n useMarkInvoicePaid,\n useCreateInvoice,\n useRelationships,\n usePaymentInstruments,\n useCreatePaymentInstrument,\n useRemovePaymentInstrument,\n} from '../hooks/useSubscriberBotApi';\nimport { cn } from '../utils/cn';\nimport { formatCurrency, formatDate } from '../utils/formatters';\nimport type { SubInvoiceStatus, SubPaymentInstrumentType } from '../types';\n\nconst PAGE_SIZE = 25;\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 labelCls = 'mb-1 block text-xs font-medium text-text-secondary';\n\nconst STATUS_OPTIONS: Array<{ value: '' | SubInvoiceStatus; label: string }> = [\n { value: '', label: 'All statuses' },\n { value: 'ISSUED', label: 'Issued' },\n { value: 'OVERDUE', label: 'Overdue' },\n { value: 'PAID', label: 'Paid' },\n { value: 'FAILED', label: 'Failed' },\n { value: 'DRAFT', label: 'Draft' },\n { value: 'CANCELLED', label: 'Cancelled' },\n { value: 'REFUNDED', label: 'Refunded' },\n];\n\nconst INVOICE_STATUSES: SubInvoiceStatus[] = [\n 'ISSUED',\n 'DRAFT',\n 'OVERDUE',\n 'PAID',\n 'FAILED',\n 'CANCELLED',\n 'REFUNDED',\n];\n\nconst PAYMENT_TYPES: SubPaymentInstrumentType[] = [\n 'CARD',\n 'BANK_ACCOUNT',\n 'PAYPAL',\n 'UPI',\n 'WALLET',\n 'OTHER',\n];\n\nconst PAYABLE = new Set<SubInvoiceStatus>(['ISSUED', 'OVERDUE', 'FAILED']);\n\n// Semantic tints for the status-distribution donut (fall back to chart tokens).\nconst STATUS_TINT: Partial<Record<SubInvoiceStatus, string>> = {\n PAID: 'var(--color-status-success-text)',\n OVERDUE: 'var(--color-status-error-text)',\n FAILED: 'var(--color-status-error-text)',\n ISSUED: 'var(--color-chart-1)',\n DRAFT: 'var(--color-text-secondary)',\n CANCELLED: 'var(--color-chart-4)',\n REFUNDED: 'var(--color-status-warning-text)',\n};\n\n// ---------------------------------------------------------------------------\n// Create invoice modal → subCreateInvoice\n// ---------------------------------------------------------------------------\n\nfunction CreateInvoiceModal({ onClose }: { onClose: () => void }) {\n const create = useCreateInvoice();\n const relationshipsQ = useRelationships({ pagination: { limit: 100, offset: 0 } });\n const relationships = relationshipsQ.data?.items ?? [];\n\n const [relationshipId, setRelationshipId] = useState('');\n const [number, setNumber] = useState('');\n const [status, setStatus] = useState<SubInvoiceStatus>('ISSUED');\n const [currency, setCurrency] = useState('USD');\n const [subtotal, setSubtotal] = useState('');\n const [taxAmount, setTaxAmount] = useState('');\n const [total, setTotal] = useState('');\n const [issueDate, setIssueDate] = useState('');\n const [dueDate, setDueDate] = useState('');\n const [formError, setFormError] = useState<string | null>(null);\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n setFormError(null);\n if (!relationshipId) {\n setFormError('Select the relationship this invoice belongs to.');\n return;\n }\n create.mutate(\n {\n relationshipId,\n status,\n currency: currency.trim() || 'USD',\n ...(number.trim() ? { number: number.trim() } : {}),\n ...(subtotal.trim() ? { subtotal: Number(subtotal) } : {}),\n ...(taxAmount.trim() ? { taxAmount: Number(taxAmount) } : {}),\n ...(total.trim() ? { total: Number(total) } : {}),\n ...(issueDate ? { issueDate: new Date(issueDate).toISOString() } : {}),\n ...(dueDate ? { dueDate: new Date(dueDate).toISOString() } : {}),\n },\n {\n onSuccess: () => onClose(),\n onError: (err) =>\n setFormError(err instanceof Error ? err.message : 'Could not create invoice.'),\n }\n );\n };\n\n return (\n <Modal title=\"Create invoice\" onClose={onClose} maxWidthClassName=\"max-w-xl\">\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {formError && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n <div>\n <label htmlFor=\"inv-relationship\" className={labelCls}>\n Relationship <span className=\"text-status-error-text\">*</span>\n </label>\n <select\n id=\"inv-relationship\"\n value={relationshipId}\n onChange={(e) => setRelationshipId(e.target.value)}\n className={inputCls}\n required\n >\n <option value=\"\">— Select a relationship —</option>\n {relationships.map((r) => (\n <option key={r.id} value={r.id}>\n {r.label}\n </option>\n ))}\n </select>\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div>\n <label htmlFor=\"inv-number\" className={labelCls}>\n Number\n </label>\n <input\n id=\"inv-number\"\n type=\"text\"\n value={number}\n onChange={(e) => setNumber(e.target.value)}\n placeholder=\"INV-0001\"\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"inv-status\" className={labelCls}>\n Status\n </label>\n <select\n id=\"inv-status\"\n value={status}\n onChange={(e) => setStatus(e.target.value as SubInvoiceStatus)}\n className={inputCls}\n >\n {INVOICE_STATUSES.map((s) => (\n <option key={s} value={s}>\n {s.replace(/_/g, ' ')}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"inv-currency\" className={labelCls}>\n Currency\n </label>\n <input\n id=\"inv-currency\"\n type=\"text\"\n value={currency}\n onChange={(e) => setCurrency(e.target.value.toUpperCase())}\n maxLength={3}\n className={inputCls}\n />\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div>\n <label htmlFor=\"inv-subtotal\" className={labelCls}>\n Subtotal\n </label>\n <input\n id=\"inv-subtotal\"\n type=\"number\"\n min=\"0\"\n step=\"0.01\"\n value={subtotal}\n onChange={(e) => setSubtotal(e.target.value)}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"inv-tax\" className={labelCls}>\n Tax\n </label>\n <input\n id=\"inv-tax\"\n type=\"number\"\n min=\"0\"\n step=\"0.01\"\n value={taxAmount}\n onChange={(e) => setTaxAmount(e.target.value)}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"inv-total\" className={labelCls}>\n Total\n </label>\n <input\n id=\"inv-total\"\n type=\"number\"\n min=\"0\"\n step=\"0.01\"\n value={total}\n onChange={(e) => setTotal(e.target.value)}\n className={inputCls}\n />\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"inv-issue\" className={labelCls}>\n Issue date\n </label>\n <input\n id=\"inv-issue\"\n type=\"date\"\n value={issueDate}\n onChange={(e) => setIssueDate(e.target.value)}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"inv-due\" className={labelCls}>\n Due date\n </label>\n <input\n id=\"inv-due\"\n type=\"date\"\n value={dueDate}\n onChange={(e) => setDueDate(e.target.value)}\n className={inputCls}\n />\n </div>\n </div>\n <div className=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={create.isPending}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {create.isPending ? 'Creating…' : 'Create invoice'}\n </button>\n </div>\n </form>\n </Modal>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Add payment method modal → subCreatePaymentInstrument\n// ---------------------------------------------------------------------------\n\nfunction AddPaymentMethodModal({ onClose }: { onClose: () => void }) {\n const create = useCreatePaymentInstrument();\n const [label, setLabel] = useState('');\n const [type, setType] = useState<SubPaymentInstrumentType>('CARD');\n const [brand, setBrand] = useState('');\n const [last4, setLast4] = useState('');\n const [expiryMonth, setExpiryMonth] = useState('');\n const [expiryYear, setExpiryYear] = useState('');\n const [isDefault, setIsDefault] = useState(false);\n const [formError, setFormError] = useState<string | null>(null);\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n setFormError(null);\n if (!label.trim()) {\n setFormError('A label is required.');\n return;\n }\n create.mutate(\n {\n label: label.trim(),\n type,\n isDefault,\n ...(brand.trim() ? { brand: brand.trim() } : {}),\n ...(last4.trim() ? { last4: last4.trim() } : {}),\n ...(expiryMonth.trim() ? { expiryMonth: Number(expiryMonth) } : {}),\n ...(expiryYear.trim() ? { expiryYear: Number(expiryYear) } : {}),\n },\n {\n onSuccess: () => onClose(),\n onError: (err) =>\n setFormError(err instanceof Error ? err.message : 'Could not add payment method.'),\n }\n );\n };\n\n return (\n <Modal\n title=\"Add payment method\"\n description=\"A passive annotation of which external card/account pays for a subscription — no full card numbers or CVV.\"\n onClose={onClose}\n maxWidthClassName=\"max-w-xl\"\n >\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {formError && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"pm-label\" className={labelCls}>\n Label <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"pm-label\"\n type=\"text\"\n value={label}\n onChange={(e) => setLabel(e.target.value)}\n placeholder=\"e.g. Personal Visa\"\n className={inputCls}\n required\n />\n </div>\n <div>\n <label htmlFor=\"pm-type\" className={labelCls}>\n Type\n </label>\n <select\n id=\"pm-type\"\n value={type}\n onChange={(e) => setType(e.target.value as SubPaymentInstrumentType)}\n className={inputCls}\n >\n {PAYMENT_TYPES.map((t) => (\n <option key={t} value={t}>\n {t.replace(/_/g, ' ')}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div>\n <label htmlFor=\"pm-brand\" className={labelCls}>\n Brand\n </label>\n <input\n id=\"pm-brand\"\n type=\"text\"\n value={brand}\n onChange={(e) => setBrand(e.target.value)}\n placeholder=\"Visa, Amex…\"\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"pm-last4\" className={labelCls}>\n Last 4\n </label>\n <input\n id=\"pm-last4\"\n type=\"text\"\n inputMode=\"numeric\"\n maxLength={4}\n value={last4}\n onChange={(e) => setLast4(e.target.value.replace(/\\D/g, ''))}\n placeholder=\"4242\"\n className={inputCls}\n />\n </div>\n <div className=\"grid grid-cols-2 gap-2\">\n <div>\n <label htmlFor=\"pm-exp-month\" className={labelCls}>\n Exp. mo.\n </label>\n <input\n id=\"pm-exp-month\"\n type=\"number\"\n min=\"1\"\n max=\"12\"\n value={expiryMonth}\n onChange={(e) => setExpiryMonth(e.target.value)}\n placeholder=\"12\"\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"pm-exp-year\" className={labelCls}>\n Exp. yr.\n </label>\n <input\n id=\"pm-exp-year\"\n type=\"number\"\n min=\"2024\"\n max=\"2099\"\n value={expiryYear}\n onChange={(e) => setExpiryYear(e.target.value)}\n placeholder=\"2028\"\n className={inputCls}\n />\n </div>\n </div>\n </div>\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={isDefault}\n onChange={(e) => setIsDefault(e.target.checked)}\n className=\"size-4 rounded border-border-subtle\"\n />\n Set as default\n </label>\n <div className=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={create.isPending}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {create.isPending ? 'Adding…' : 'Add payment method'}\n </button>\n </div>\n </form>\n </Modal>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Payment methods panel\n// ---------------------------------------------------------------------------\n\nfunction PaymentMethodsSection() {\n const { data, isLoading, isError, refetch } = usePaymentInstruments();\n const remove = useRemovePaymentInstrument();\n const [showAdd, setShowAdd] = useState(false);\n\n const instruments = data ?? [];\n\n return (\n <Section\n title=\"Payment methods\"\n description=\"Which external cards and accounts pay for your subscriptions\"\n action={\n <button\n type=\"button\"\n onClick={() => setShowAdd(true)}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <Plus className=\"h-3.5 w-3.5\" /> Add payment method\n </button>\n }\n >\n {isLoading ? (\n <LoadingView label=\"Loading payment methods…\" />\n ) : isError ? (\n <ErrorView message=\"Could not load payment methods.\" onRetry={() => void refetch()} />\n ) : instruments.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<CreditCard className=\"h-12 w-12\" />}\n title=\"No payment methods\"\n description=\"Annotate which card or account pays for each subscription. No full card numbers are stored.\"\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-3\">\n {instruments.map((pi) => {\n const meta = [\n pi.expiryMonth && pi.expiryYear\n ? `Exp ${String(pi.expiryMonth).padStart(2, '0')}/${String(pi.expiryYear).slice(-2)}`\n : null,\n ].filter(Boolean);\n return (\n <GlassCard key={pi.id} treatment=\"metric\" className=\"flex flex-col gap-4 p-4\">\n {/* Card face */}\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex items-center gap-3\">\n <span className=\"grid h-9 w-12 place-items-center rounded-md bg-[var(--color-accent-soft)] text-text-primary\">\n <CreditCard className=\"h-4 w-4\" aria-hidden />\n </span>\n <div className=\"min-w-0\">\n <p className=\"flex items-center gap-1.5 truncate text-sm font-semibold text-text-primary\">\n {pi.brand || pi.label}\n {pi.isDefault && (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-status-success-bg-subtle px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-status-success-text\">\n <Star className=\"h-2.5 w-2.5 fill-current\" aria-hidden />\n Default\n </span>\n )}\n </p>\n <p className=\"font-mono text-sm tracking-[0.15em] text-text-secondary\">\n {pi.last4 ? `•••• ${pi.last4}` : pi.type.replace(/_/g, ' ')}\n </p>\n </div>\n </div>\n <StatusBadge status={pi.status} />\n </div>\n\n <div className=\"mt-auto flex items-center justify-between border-t border-border-subtle pt-3\">\n <p className=\"truncate text-xs text-text-secondary\">\n {[pi.label, ...meta].filter(Boolean).join(' · ')}\n </p>\n <button\n type=\"button\"\n aria-label={`Remove ${pi.label}`}\n disabled={remove.isPending}\n onClick={() => remove.mutate(pi.id)}\n className=\"inline-flex shrink-0 items-center gap-1 rounded-lg border border-border-subtle px-2.5 py-1 text-xs font-medium text-text-secondary hover:border-status-error-text/40 hover:bg-status-error-bg-subtle hover:text-status-error-text disabled:opacity-50\"\n >\n <Trash2 className=\"h-3.5 w-3.5\" aria-hidden />\n Remove\n </button>\n </div>\n </GlassCard>\n );\n })}\n </div>\n )}\n\n {showAdd && <AddPaymentMethodModal onClose={() => setShowAdd(false)} />}\n </Section>\n );\n}\n\nexport function InvoicesPage() {\n const { navigateTo } = useSubscriberBot();\n const [page, setPage] = useState(0);\n const [status, setStatus] = useState<'' | SubInvoiceStatus>('');\n const [showCreate, setShowCreate] = useState(false);\n\n const { data, isLoading, isError, refetch, isFetching } = useInvoices({\n status: status || undefined,\n pagination: { limit: PAGE_SIZE, offset: page * PAGE_SIZE },\n });\n const markPaid = useMarkInvoicePaid();\n\n const items = useMemo(() => data?.items ?? [], [data]);\n const hasMore = data?.pageInfo.hasMore ?? false;\n\n const totals = useMemo(() => {\n const outstanding = items.filter((i) => PAYABLE.has(i.status));\n const currency = items[0]?.currency ?? 'USD';\n\n // Status distribution → donut segments (only statuses actually present).\n const byStatus = new Map<SubInvoiceStatus, number>();\n for (const i of items) byStatus.set(i.status, (byStatus.get(i.status) ?? 0) + 1);\n const distribution: Segment[] = Array.from(byStatus.entries())\n .sort((a, b) => b[1] - a[1])\n .map(([s, count], idx) => ({\n label: s.charAt(0) + s.slice(1).toLowerCase(),\n value: count,\n color: STATUS_TINT[s] ?? CHART_COLORS[idx % CHART_COLORS.length],\n }));\n\n return {\n outstandingCount: outstanding.length,\n outstandingTotal: outstanding.reduce((sum, i) => sum + i.total, 0),\n paidCount: items.filter((i) => i.status === 'PAID').length,\n overdueCount: items.filter((i) => i.status === 'OVERDUE').length,\n distribution,\n currency,\n };\n }, [items]);\n\n return (\n <PageLayout\n title=\"Invoices\"\n description=\"Your billing ledger across every relationship\"\n actions={\n <button\n type=\"button\"\n onClick={() => setShowCreate(true)}\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n <Plus className=\"h-4 w-4\" /> Create invoice\n </button>\n }\n >\n <PagePurpose className=\"mb-4\">\n Every invoice from your recurring relationships in one ledger. Filter by status and settle\n issued or overdue invoices directly.\n </PagePurpose>\n\n {!isLoading && !isError && items.length > 0 && (\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-3\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:col-span-2\">\n <MetricsCard\n featured\n icon={<Receipt />}\n value={formatCurrency(totals.outstandingTotal, totals.currency, {\n maximumFractionDigits: 0,\n })}\n label=\"Outstanding · this page\"\n />\n <MetricsCard\n icon={<FileText />}\n value={totals.outstandingCount}\n label=\"Open invoices\"\n />\n <MetricsCard\n icon={<AlertTriangle />}\n value={totals.overdueCount}\n label=\"Overdue\"\n {...(totals.overdueCount > 0 ? { trend: 'down' as const } : {})}\n />\n <MetricsCard\n icon={<CheckCircle2 />}\n value={totals.paidCount}\n label=\"Paid\"\n {...(totals.paidCount > 0 ? { trend: 'up' as const } : {})}\n />\n </div>\n\n <GlassCard treatment=\"insight\" className=\"p-6\">\n <p className=\"text-sm font-semibold text-text-primary\">Status breakdown</p>\n <p className=\"text-xs text-text-secondary\">Across the invoices on this page</p>\n <div className=\"mt-4 flex flex-col items-center gap-5 sm:flex-row sm:items-center lg:flex-col xl:flex-row\">\n <Donut\n data={totals.distribution}\n size={132}\n thickness={16}\n centerLabel={String(items.length)}\n centerSub=\"invoices\"\n className=\"shrink-0\"\n />\n <Legend\n data={totals.distribution}\n className=\"w-full min-w-0 flex-1\"\n valueFormat={(n) => String(n)}\n />\n </div>\n </GlassCard>\n </div>\n )}\n\n <div className=\"mt-6 flex flex-wrap items-center gap-3\">\n <select\n aria-label=\"Filter by status\"\n value={status}\n onChange={(e) => {\n setStatus(e.target.value as '' | SubInvoiceStatus);\n setPage(0);\n }}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-xs text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n {STATUS_OPTIONS.map((o) => (\n <option key={o.value} value={o.value}>\n {o.label}\n </option>\n ))}\n </select>\n </div>\n\n {isLoading ? (\n <LoadingView label=\"Loading invoices…\" />\n ) : isError ? (\n <ErrorView message=\"Could not load invoices.\" onRetry={() => void refetch()} />\n ) : items.length === 0 ? (\n <div className=\"mt-6\">\n <EmptyState\n illustration=\"empty-data\"\n icon={<Receipt className=\"h-12 w-12\" />}\n title=\"No invoices\"\n description={\n status\n ? 'No invoices match this status filter.'\n : 'Invoices from your relationships will appear here — or create one manually.'\n }\n action={\n <button\n type=\"button\"\n onClick={() => setShowCreate(true)}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n Create invoice\n </button>\n }\n />\n </div>\n ) : (\n <>\n <div className=\"mt-6 overflow-hidden rounded-lg border border-border-subtle bg-bg-surface\">\n <div className=\"hidden grid-cols-[1.5fr_1fr_1fr_1fr_auto] gap-4 border-b border-border-subtle bg-bg-sunken px-4 py-2.5 text-xs font-medium text-text-secondary sm:grid\">\n <span>Invoice</span>\n <span>Status</span>\n <span className=\"text-right\">Total</span>\n <span className=\"text-right\">Due</span>\n <span className=\"text-right\">Action</span>\n </div>\n <div className=\"divide-y divide-border-subtle\">\n {items.map((inv) => (\n <div\n key={inv.id}\n className=\"grid grid-cols-1 gap-2 px-4 py-3 transition-colors hover:bg-bg-sunken/50 sm:grid-cols-[1.5fr_1fr_1fr_1fr_auto] sm:items-center sm:gap-4\"\n >\n <button\n type=\"button\"\n onClick={() => navigateTo(`/relationships/${inv.relationshipId}`)}\n className=\"min-w-0 text-left\"\n >\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {inv.number || `Invoice ${inv.id.slice(0, 8)}`}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {inv.issueDate ? `Issued ${formatDate(inv.issueDate)}` : '—'}\n </p>\n </button>\n <span>\n <StatusBadge status={inv.status} />\n </span>\n <span className=\"text-sm font-semibold tabular-nums text-text-primary sm:text-right\">\n {formatCurrency(inv.total, inv.currency)}\n </span>\n <span className=\"text-xs text-text-secondary sm:text-right\">\n {inv.dueDate ? formatDate(inv.dueDate) : '—'}\n </span>\n <span className=\"sm:text-right\">\n {PAYABLE.has(inv.status) ? (\n <button\n type=\"button\"\n disabled={markPaid.isPending}\n onClick={() => markPaid.mutate(inv.id)}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-status-success-text/30 bg-status-success-bg-subtle px-3 py-1.5 text-xs font-medium text-status-success-text hover:opacity-90 disabled:opacity-50\"\n >\n <CheckCircle2 className=\"h-3.5 w-3.5\" aria-hidden />\n Mark paid\n </button>\n ) : inv.paidAt ? (\n <span className=\"text-xs text-text-secondary\">\n Paid {formatDate(inv.paidAt)}\n </span>\n ) : (\n <span className=\"text-xs text-text-secondary\">—</span>\n )}\n </span>\n </div>\n ))}\n </div>\n </div>\n\n <div className=\"mt-4 flex items-center justify-between\">\n <p className=\"text-xs text-text-secondary\">\n Page {page + 1}\n {isFetching ? ' · refreshing…' : ''}\n </p>\n <div className=\"flex gap-2\">\n <button\n type=\"button\"\n disabled={page === 0}\n onClick={() => setPage((p) => Math.max(0, p - 1))}\n className={cn(\n 'rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium',\n page === 0\n ? 'cursor-not-allowed text-text-secondary opacity-50'\n : 'text-text-primary hover:bg-bg-sunken'\n )}\n >\n Previous\n </button>\n <button\n type=\"button\"\n disabled={!hasMore}\n onClick={() => setPage((p) => p + 1)}\n className={cn(\n 'rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium',\n !hasMore\n ? 'cursor-not-allowed text-text-secondary opacity-50'\n : 'text-text-primary hover:bg-bg-sunken'\n )}\n >\n Next\n </button>\n </div>\n </div>\n </>\n )}\n\n <PaymentMethodsSection />\n\n {showCreate && <CreateInvoiceModal onClose={() => setShowCreate(false)} />}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflows (Automation)
|
|
3
|
+
* @module subscriberbot/pages
|
|
4
|
+
*
|
|
5
|
+
* The Workflow Engine — event-driven automation ("when invoice arrives →
|
|
6
|
+
* classify → update relationship → notify finance"). List + create + activate.
|
|
7
|
+
* Reads `subListWorkflows`; writes `subCreateWorkflow` / `subActivateWorkflow`.
|
|
8
|
+
*
|
|
9
|
+
* Opening a workflow renders a READ-ONLY horizontal step-flow derived from the
|
|
10
|
+
* workflow's real `triggerEvent`, `name` and `status` (the svc contract does
|
|
11
|
+
* not expose an editable step array — this is a truthful visualisation, not a
|
|
12
|
+
* builder).
|
|
13
|
+
*/
|
|
14
|
+
export declare function WorkflowsPage(): import("react").JSX.Element;
|
|
15
|
+
//# sourceMappingURL=WorkflowsPage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WorkflowsPage.d.ts","sourceRoot":"","sources":["../../../src/subscriberbot/pages/WorkflowsPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAoLH,wBAAgB,aAAa,gCA2Q5B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkflowsPage.js","names":[],"sources":["../../../src/subscriberbot/pages/WorkflowsPage.tsx"],"sourcesContent":["/**\n * Workflows (Automation)\n * @module subscriberbot/pages\n *\n * The Workflow Engine — event-driven automation (\"when invoice arrives →\n * classify → update relationship → notify finance\"). List + create + activate.\n * Reads `subListWorkflows`; writes `subCreateWorkflow` / `subActivateWorkflow`.\n *\n * Opening a workflow renders a READ-ONLY horizontal step-flow derived from the\n * workflow's real `triggerEvent`, `name` and `status` (the svc contract does\n * not expose an editable step array — this is a truthful visualisation, not a\n * builder).\n */\n\nimport { useState } from 'react';\nimport {\n Plus,\n Workflow as WorkflowIcon,\n Play,\n Zap,\n Sparkles,\n CheckCircle2,\n ArrowRight,\n Clock,\n X,\n} from 'lucide-react';\nimport { PagePurpose, GlassCard } from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n StatusBadge,\n EmptyState,\n Modal,\n MetricsCard,\n LoadingView,\n ErrorView,\n} from '../components';\nimport { useWorkflows, useCreateWorkflow, useActivateWorkflow } from '../hooks/useSubscriberBotApi';\nimport { formatRelativeTime } from '../utils/formatters';\nimport type { SubCreateWorkflowInput, SubWorkflow, SubWorkflowStatus } from '../types';\n\nconst STATUS_OPTIONS: Array<{ value: '' | SubWorkflowStatus; label: string }> = [\n { value: '', label: 'All statuses' },\n { value: 'ACTIVE', label: 'Active' },\n { value: 'DRAFT', label: 'Draft' },\n { value: 'PAUSED', label: 'Paused' },\n { value: 'ARCHIVED', label: 'Archived' },\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 labelCls = 'mb-1 block text-xs font-medium text-text-secondary';\n\n// ---------------------------------------------------------------------------\n// A single node in the read-only step flow.\n// ---------------------------------------------------------------------------\nfunction FlowNode({\n icon,\n kicker,\n title,\n sub,\n accent,\n}: {\n icon: React.ReactNode;\n kicker: string;\n title: string;\n sub?: string;\n /** Top accent bar tone. */\n accent: 'trigger' | 'run' | 'done';\n}) {\n const bar =\n accent === 'done'\n ? 'bg-status-success-text'\n : accent === 'run'\n ? 'bg-status-warning-text'\n : 'bg-[var(--color-accent-indigo)]';\n return (\n <div className=\"w-48 shrink-0 overflow-hidden rounded-xl border border-border-subtle bg-bg-surface shadow-sm\">\n <div className={`h-1 w-full ${bar}`} aria-hidden />\n <div className=\"p-3\">\n <span className=\"flex size-8 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary\">\n {icon}\n </span>\n <p className=\"mt-2 text-[10px] font-medium uppercase tracking-wide text-text-secondary\">\n {kicker}\n </p>\n <p className=\"truncate text-sm font-semibold text-text-primary\" title={title}>\n {title}\n </p>\n {sub && <p className=\"mt-0.5 truncate text-xs text-text-secondary\">{sub}</p>}\n </div>\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Read-only flow panel for the selected workflow.\n// ---------------------------------------------------------------------------\nfunction WorkflowFlowPanel({\n workflow,\n onClose,\n onActivate,\n activating,\n}: {\n workflow: SubWorkflow;\n onClose: () => void;\n onActivate: () => void;\n activating: boolean;\n}) {\n const connector = (\n <div className=\"flex shrink-0 items-center px-1 text-text-secondary\" aria-hidden>\n <span className=\"h-px w-4 bg-border-seam sm:w-6\" />\n <ArrowRight className=\"h-4 w-4\" />\n </div>\n );\n\n return (\n <GlassCard treatment=\"glass\" glow className=\"mt-6 p-5\">\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <p className=\"text-base font-semibold text-text-primary\">{workflow.name}</p>\n <StatusBadge status={workflow.status} />\n </div>\n <p className=\"mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-text-secondary\">\n <span className=\"tabular-nums\">Ran {workflow.runCount}×</span>\n {workflow.lastRunAt && (\n <span className=\"inline-flex items-center gap-1\">\n <Clock className=\"h-3 w-3\" aria-hidden /> last{' '}\n {formatRelativeTime(workflow.lastRunAt)}\n </span>\n )}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={onClose}\n aria-label=\"Close flow\"\n className=\"rounded-lg border border-border-subtle p-1.5 text-text-secondary hover:bg-bg-sunken hover:text-text-primary\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n </div>\n\n {workflow.description && (\n <p className=\"mt-2 max-w-3xl text-sm text-text-secondary\">{workflow.description}</p>\n )}\n\n {/* Horizontal read-only step flow (scrolls on small screens) */}\n <div className=\"mt-4 overflow-x-auto pb-2\">\n <div className=\"flex min-w-max items-stretch\">\n <FlowNode\n accent=\"trigger\"\n kicker=\"When\"\n icon={<Zap className=\"h-4 w-4\" />}\n title={workflow.triggerEvent}\n sub=\"Trigger event\"\n />\n {connector}\n <FlowNode\n accent=\"run\"\n kicker=\"Run\"\n icon={<Sparkles className=\"h-4 w-4\" />}\n title={workflow.name}\n sub=\"Automation\"\n />\n {connector}\n <FlowNode\n accent=\"done\"\n kicker=\"Then\"\n icon={<CheckCircle2 className=\"h-4 w-4\" />}\n title={workflow.status === 'ACTIVE' ? 'Live' : 'Awaiting activation'}\n sub={`Status: ${workflow.status.charAt(0) + workflow.status.slice(1).toLowerCase()}`}\n />\n </div>\n </div>\n\n {workflow.status !== 'ACTIVE' && (\n <div className=\"mt-3 flex justify-end\">\n <button\n type=\"button\"\n disabled={activating}\n onClick={onActivate}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n <Play className=\"h-4 w-4\" /> {activating ? 'Activating…' : 'Activate workflow'}\n </button>\n </div>\n )}\n </GlassCard>\n );\n}\n\nexport function WorkflowsPage() {\n const [statusFilter, setStatusFilter] = useState<'' | SubWorkflowStatus>('');\n const [showCreate, setShowCreate] = useState(false);\n const [selectedId, setSelectedId] = useState<string | null>(null);\n\n const { data, isLoading, isError, refetch } = useWorkflows(statusFilter || undefined);\n const create = useCreateWorkflow();\n const activate = useActivateWorkflow();\n\n const [name, setName] = useState('');\n const [triggerEvent, setTriggerEvent] = useState('');\n const [description, setDescription] = useState('');\n const [formError, setFormError] = useState<string | null>(null);\n\n const workflows = data ?? [];\n const selected = workflows.find((w) => w.id === selectedId) ?? null;\n\n // --- Real, computed aggregates -------------------------------------------\n const activeCount = workflows.filter((w) => w.status === 'ACTIVE').length;\n const totalRuns = workflows.reduce((s, w) => s + (w.runCount || 0), 0);\n\n const resetForm = () => {\n setName('');\n setTriggerEvent('');\n setDescription('');\n setFormError(null);\n };\n\n const closeCreate = () => {\n setShowCreate(false);\n resetForm();\n };\n\n const handleCreate = (e: React.FormEvent) => {\n e.preventDefault();\n setFormError(null);\n if (!name.trim() || !triggerEvent.trim()) {\n setFormError('Name and trigger event are required.');\n return;\n }\n const input: SubCreateWorkflowInput = {\n name: name.trim(),\n triggerEvent: triggerEvent.trim(),\n ...(description.trim() ? { description: description.trim() } : {}),\n };\n create.mutate(input, {\n onSuccess: () => {\n resetForm();\n setShowCreate(false);\n },\n onError: (err) =>\n setFormError(err instanceof Error ? err.message : 'Could not create workflow.'),\n });\n };\n\n return (\n <PageLayout\n title=\"Workflows\"\n description=\"Event-driven automation across your portfolio\"\n actions={\n <button\n type=\"button\"\n onClick={() => setShowCreate(true)}\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n <Plus className=\"h-4 w-4\" /> New workflow\n </button>\n }\n >\n <PagePurpose className=\"mb-4\">\n Workflows react to domain events — a new invoice, a detected renewal, a cancelled\n relationship — and run multi-step automations. Draft a workflow, then activate it when\n you're ready.\n </PagePurpose>\n\n <div className=\"flex flex-wrap items-center gap-3\">\n <select\n aria-label=\"Filter by status\"\n value={statusFilter}\n onChange={(e) => setStatusFilter(e.target.value as '' | SubWorkflowStatus)}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-xs text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n {STATUS_OPTIONS.map((o) => (\n <option key={o.value} value={o.value}>\n {o.label}\n </option>\n ))}\n </select>\n </div>\n\n {isLoading ? (\n <LoadingView label=\"Loading workflows…\" />\n ) : isError ? (\n <ErrorView message=\"Could not load workflows.\" onRetry={() => void refetch()} />\n ) : workflows.length === 0 ? (\n <div className=\"mt-6\">\n <EmptyState\n illustration=\"empty-data\"\n icon={<WorkflowIcon className=\"h-12 w-12\" />}\n title=\"No workflows yet\"\n description=\"Create your first event-driven workflow to automate your recurring portfolio.\"\n action={\n <button\n type=\"button\"\n onClick={() => setShowCreate(true)}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n New workflow\n </button>\n }\n />\n </div>\n ) : (\n <>\n {/* Overview (computed) */}\n <div className=\"mt-6 grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <MetricsCard\n icon={<WorkflowIcon className=\"h-5 w-5\" />}\n value={workflows.length}\n label=\"Workflows\"\n />\n <MetricsCard\n icon={<Zap className=\"h-5 w-5\" />}\n value={activeCount}\n label=\"Active\"\n />\n <MetricsCard\n icon={<Play className=\"h-5 w-5\" />}\n value={totalRuns}\n label=\"Total runs\"\n />\n </div>\n\n {/* Selected workflow flow */}\n {selected && (\n <WorkflowFlowPanel\n workflow={selected}\n onClose={() => setSelectedId(null)}\n onActivate={() => activate.mutate(selected.id)}\n activating={activate.isPending}\n />\n )}\n\n {/* Workflow cards */}\n <div className=\"mt-6 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {workflows.map((wf) => {\n const isSelected = wf.id === selectedId;\n return (\n <GlassCard\n key={wf.id}\n treatment=\"metric\"\n glow={isSelected || wf.status === 'ACTIVE'}\n role=\"button\"\n tabIndex={0}\n aria-pressed={isSelected}\n aria-label={`View flow for ${wf.name}`}\n onClick={() => setSelectedId(isSelected ? null : wf.id)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n setSelectedId(isSelected ? null : wf.id);\n }\n }}\n className=\"flex cursor-pointer flex-col p-5 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"flex size-10 items-center justify-center rounded-xl bg-[var(--color-accent-soft)] text-text-primary\">\n <WorkflowIcon className=\"h-5 w-5\" />\n </span>\n <StatusBadge status={wf.status} />\n </div>\n <p className=\"mt-3 truncate text-sm font-semibold text-text-primary\">{wf.name}</p>\n <p className=\"flex items-center gap-1 truncate text-xs text-text-secondary\">\n <Zap className=\"h-3 w-3 shrink-0\" aria-hidden /> on {wf.triggerEvent}\n </p>\n {wf.description && (\n <p className=\"mt-2 line-clamp-2 min-h-[2rem] text-xs text-text-secondary\">\n {wf.description}\n </p>\n )}\n <p className=\"mt-3 flex items-center gap-1.5 text-xs text-text-secondary tabular-nums\">\n <Play className=\"h-3.5 w-3.5\" aria-hidden /> Ran {wf.runCount}×\n {wf.lastRunAt ? ` · last ${formatRelativeTime(wf.lastRunAt)}` : ''}\n </p>\n {wf.status !== 'ACTIVE' && (\n <button\n type=\"button\"\n disabled={activate.isPending}\n onClick={(e) => {\n e.stopPropagation();\n activate.mutate(wf.id);\n }}\n className=\"mt-4 inline-flex items-center justify-center gap-1.5 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 >\n <Play className=\"h-3.5 w-3.5\" /> Activate\n </button>\n )}\n </GlassCard>\n );\n })}\n </div>\n </>\n )}\n\n {/* Create modal */}\n {showCreate && (\n <Modal title=\"New workflow\" onClose={closeCreate}>\n <form onSubmit={handleCreate} className=\"space-y-4\">\n {formError && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n <div>\n <label htmlFor=\"wf-name\" className={labelCls}>\n Name <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"wf-name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder=\"e.g. Invoice → notify finance\"\n className={inputCls}\n required\n />\n </div>\n <div>\n <label htmlFor=\"wf-trigger\" className={labelCls}>\n Trigger event <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"wf-trigger\"\n type=\"text\"\n value={triggerEvent}\n onChange={(e) => setTriggerEvent(e.target.value)}\n placeholder=\"e.g. InvoiceReceived, RenewalDetected\"\n className={inputCls}\n required\n />\n </div>\n <div>\n <label htmlFor=\"wf-desc\" className={labelCls}>\n Description\n </label>\n <textarea\n id=\"wf-desc\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n rows={3}\n placeholder=\"What should this workflow do when the event fires?\"\n className={inputCls}\n />\n </div>\n <div className=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={closeCreate}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={create.isPending}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {create.isPending ? 'Creating…' : 'Create workflow'}\n </button>\n </div>\n </form>\n </Modal>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"WorkflowsPage.js","names":[],"sources":["../../../src/subscriberbot/pages/WorkflowsPage.tsx"],"sourcesContent":["/**\n * Workflows (Automation)\n * @module subscriberbot/pages\n *\n * The Workflow Engine — event-driven automation (\"when invoice arrives →\n * classify → update relationship → notify finance\"). List + create + activate.\n * Reads `subListWorkflows`; writes `subCreateWorkflow` / `subActivateWorkflow`.\n *\n * Opening a workflow renders a READ-ONLY horizontal step-flow derived from the\n * workflow's real `triggerEvent`, `name` and `status` (the svc contract does\n * not expose an editable step array — this is a truthful visualisation, not a\n * builder).\n */\n\nimport { useState } from 'react';\nimport {\n Plus,\n Workflow as WorkflowIcon,\n Play,\n Zap,\n Sparkles,\n CheckCircle2,\n ArrowRight,\n Clock,\n X,\n} from 'lucide-react';\nimport { PagePurpose, GlassCard } from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n StatusBadge,\n EmptyState,\n Modal,\n MetricsCard,\n LoadingView,\n ErrorView,\n} from '../components';\nimport { useWorkflows, useCreateWorkflow, useActivateWorkflow } from '../hooks/useSubscriberBotApi';\nimport { formatRelativeTime } from '../utils/formatters';\nimport type { SubCreateWorkflowInput, SubWorkflow, SubWorkflowStatus } from '../types';\n\nconst STATUS_OPTIONS: Array<{ value: '' | SubWorkflowStatus; label: string }> = [\n { value: '', label: 'All statuses' },\n { value: 'ACTIVE', label: 'Active' },\n { value: 'DRAFT', label: 'Draft' },\n { value: 'PAUSED', label: 'Paused' },\n { value: 'ARCHIVED', label: 'Archived' },\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 labelCls = 'mb-1 block text-xs font-medium text-text-secondary';\n\n// ---------------------------------------------------------------------------\n// A single node in the read-only step flow.\n// ---------------------------------------------------------------------------\nfunction FlowNode({\n icon,\n kicker,\n title,\n sub,\n accent,\n}: {\n icon: React.ReactNode;\n kicker: string;\n title: string;\n sub?: string;\n /** Top accent bar tone. */\n accent: 'trigger' | 'run' | 'done';\n}) {\n const bar =\n accent === 'done'\n ? 'bg-status-success-text'\n : accent === 'run'\n ? 'bg-status-warning-text'\n : 'bg-[var(--color-accent-indigo)]';\n return (\n <div className=\"w-48 shrink-0 overflow-hidden rounded-xl border border-border-subtle bg-bg-surface shadow-sm\">\n <div className={`h-1 w-full ${bar}`} aria-hidden />\n <div className=\"p-3\">\n <span className=\"flex size-8 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary\">\n {icon}\n </span>\n <p className=\"mt-2 text-[10px] font-medium uppercase tracking-wide text-text-secondary\">\n {kicker}\n </p>\n <p className=\"truncate text-sm font-semibold text-text-primary\" title={title}>\n {title}\n </p>\n {sub && <p className=\"mt-0.5 truncate text-xs text-text-secondary\">{sub}</p>}\n </div>\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Read-only flow panel for the selected workflow.\n// ---------------------------------------------------------------------------\nfunction WorkflowFlowPanel({\n workflow,\n onClose,\n onActivate,\n activating,\n}: {\n workflow: SubWorkflow;\n onClose: () => void;\n onActivate: () => void;\n activating: boolean;\n}) {\n const connector = (\n <div className=\"flex shrink-0 items-center px-1 text-text-secondary\" aria-hidden>\n <span className=\"h-px w-4 bg-border-seam sm:w-6\" />\n <ArrowRight className=\"h-4 w-4\" />\n </div>\n );\n\n return (\n <GlassCard treatment=\"glass\" glow className=\"mt-6 p-5\">\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <p className=\"text-base font-semibold text-text-primary\">{workflow.name}</p>\n <StatusBadge status={workflow.status} />\n </div>\n <p className=\"mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-text-secondary\">\n <span className=\"tabular-nums\">Ran {workflow.runCount}×</span>\n {workflow.lastRunAt && (\n <span className=\"inline-flex items-center gap-1\">\n <Clock className=\"h-3 w-3\" aria-hidden /> last{' '}\n {formatRelativeTime(workflow.lastRunAt)}\n </span>\n )}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={onClose}\n aria-label=\"Close flow\"\n className=\"rounded-lg border border-border-subtle p-1.5 text-text-secondary hover:bg-bg-sunken hover:text-text-primary\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n </div>\n\n {workflow.description && (\n <p className=\"mt-2 max-w-3xl text-sm text-text-secondary\">{workflow.description}</p>\n )}\n\n {/* Horizontal read-only step flow (scrolls on small screens) */}\n <div className=\"mt-4 overflow-x-auto pb-2\">\n <div className=\"flex min-w-max items-stretch\">\n <FlowNode\n accent=\"trigger\"\n kicker=\"When\"\n icon={<Zap className=\"h-4 w-4\" />}\n title={workflow.triggerEvent}\n sub=\"Trigger event\"\n />\n {connector}\n <FlowNode\n accent=\"run\"\n kicker=\"Run\"\n icon={<Sparkles className=\"h-4 w-4\" />}\n title={workflow.name}\n sub=\"Automation\"\n />\n {connector}\n <FlowNode\n accent=\"done\"\n kicker=\"Then\"\n icon={<CheckCircle2 className=\"h-4 w-4\" />}\n title={workflow.status === 'ACTIVE' ? 'Live' : 'Awaiting activation'}\n sub={`Status: ${workflow.status.charAt(0) + workflow.status.slice(1).toLowerCase()}`}\n />\n </div>\n </div>\n\n {workflow.status !== 'ACTIVE' && (\n <div className=\"mt-3 flex justify-end\">\n <button\n type=\"button\"\n disabled={activating}\n onClick={onActivate}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n <Play className=\"h-4 w-4\" /> {activating ? 'Activating…' : 'Activate workflow'}\n </button>\n </div>\n )}\n </GlassCard>\n );\n}\n\nexport function WorkflowsPage() {\n const [statusFilter, setStatusFilter] = useState<'' | SubWorkflowStatus>('');\n const [showCreate, setShowCreate] = useState(false);\n const [selectedId, setSelectedId] = useState<string | null>(null);\n\n const { data, isLoading, isError, refetch } = useWorkflows(statusFilter || undefined);\n const create = useCreateWorkflow();\n const activate = useActivateWorkflow();\n\n const [name, setName] = useState('');\n const [triggerEvent, setTriggerEvent] = useState('');\n const [description, setDescription] = useState('');\n const [formError, setFormError] = useState<string | null>(null);\n\n const workflows = data ?? [];\n const selected = workflows.find((w) => w.id === selectedId) ?? null;\n\n // --- Real, computed aggregates -------------------------------------------\n const activeCount = workflows.filter((w) => w.status === 'ACTIVE').length;\n const totalRuns = workflows.reduce((s, w) => s + (w.runCount || 0), 0);\n\n const resetForm = () => {\n setName('');\n setTriggerEvent('');\n setDescription('');\n setFormError(null);\n };\n\n const closeCreate = () => {\n setShowCreate(false);\n resetForm();\n };\n\n const handleCreate = (e: React.FormEvent) => {\n e.preventDefault();\n setFormError(null);\n if (!name.trim() || !triggerEvent.trim()) {\n setFormError('Name and trigger event are required.');\n return;\n }\n const input: SubCreateWorkflowInput = {\n name: name.trim(),\n triggerEvent: triggerEvent.trim(),\n ...(description.trim() ? { description: description.trim() } : {}),\n };\n create.mutate(input, {\n onSuccess: () => {\n resetForm();\n setShowCreate(false);\n },\n onError: (err) =>\n setFormError(err instanceof Error ? err.message : 'Could not create workflow.'),\n });\n };\n\n return (\n <PageLayout\n title=\"Workflows\"\n description=\"Event-driven automation across your portfolio\"\n actions={\n <button\n type=\"button\"\n onClick={() => setShowCreate(true)}\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n <Plus className=\"h-4 w-4\" /> New workflow\n </button>\n }\n >\n <PagePurpose className=\"mb-4\">\n Workflows react to domain events — a new invoice, a detected renewal, a cancelled\n relationship — and run multi-step automations. Draft a workflow, then activate it when\n you're ready.\n </PagePurpose>\n\n <div className=\"flex flex-wrap items-center gap-3\">\n <select\n aria-label=\"Filter by status\"\n value={statusFilter}\n onChange={(e) => setStatusFilter(e.target.value as '' | SubWorkflowStatus)}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-xs text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n {STATUS_OPTIONS.map((o) => (\n <option key={o.value} value={o.value}>\n {o.label}\n </option>\n ))}\n </select>\n </div>\n\n {isLoading ? (\n <LoadingView label=\"Loading workflows…\" />\n ) : isError ? (\n <ErrorView message=\"Could not load workflows.\" onRetry={() => void refetch()} />\n ) : workflows.length === 0 ? (\n <div className=\"mt-6\">\n <EmptyState\n illustration=\"empty-data\"\n icon={<WorkflowIcon className=\"h-12 w-12\" />}\n title=\"No workflows yet\"\n description=\"Create your first event-driven workflow to automate your recurring portfolio.\"\n action={\n <button\n type=\"button\"\n onClick={() => setShowCreate(true)}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n New workflow\n </button>\n }\n />\n </div>\n ) : (\n <>\n {/* Overview (computed) */}\n <div className=\"mt-6 grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <MetricsCard\n icon={<WorkflowIcon className=\"h-5 w-5\" />}\n value={workflows.length}\n label=\"Workflows\"\n />\n <MetricsCard icon={<Zap className=\"h-5 w-5\" />} value={activeCount} label=\"Active\" />\n <MetricsCard icon={<Play className=\"h-5 w-5\" />} value={totalRuns} label=\"Total runs\" />\n </div>\n\n {/* Selected workflow flow */}\n {selected && (\n <WorkflowFlowPanel\n workflow={selected}\n onClose={() => setSelectedId(null)}\n onActivate={() => activate.mutate(selected.id)}\n activating={activate.isPending}\n />\n )}\n\n {/* Workflow cards */}\n <div className=\"mt-6 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {workflows.map((wf) => {\n const isSelected = wf.id === selectedId;\n return (\n <GlassCard\n key={wf.id}\n treatment=\"metric\"\n glow={isSelected || wf.status === 'ACTIVE'}\n role=\"button\"\n tabIndex={0}\n aria-pressed={isSelected}\n aria-label={`View flow for ${wf.name}`}\n onClick={() => setSelectedId(isSelected ? null : wf.id)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n setSelectedId(isSelected ? null : wf.id);\n }\n }}\n className=\"flex cursor-pointer flex-col p-5 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"flex size-10 items-center justify-center rounded-xl bg-[var(--color-accent-soft)] text-text-primary\">\n <WorkflowIcon className=\"h-5 w-5\" />\n </span>\n <StatusBadge status={wf.status} />\n </div>\n <p className=\"mt-3 truncate text-sm font-semibold text-text-primary\">{wf.name}</p>\n <p className=\"flex items-center gap-1 truncate text-xs text-text-secondary\">\n <Zap className=\"h-3 w-3 shrink-0\" aria-hidden /> on {wf.triggerEvent}\n </p>\n {wf.description && (\n <p className=\"mt-2 line-clamp-2 min-h-[2rem] text-xs text-text-secondary\">\n {wf.description}\n </p>\n )}\n <p className=\"mt-3 flex items-center gap-1.5 text-xs text-text-secondary tabular-nums\">\n <Play className=\"h-3.5 w-3.5\" aria-hidden /> Ran {wf.runCount}×\n {wf.lastRunAt ? ` · last ${formatRelativeTime(wf.lastRunAt)}` : ''}\n </p>\n {wf.status !== 'ACTIVE' && (\n <button\n type=\"button\"\n disabled={activate.isPending}\n onClick={(e) => {\n e.stopPropagation();\n activate.mutate(wf.id);\n }}\n className=\"mt-4 inline-flex items-center justify-center gap-1.5 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 >\n <Play className=\"h-3.5 w-3.5\" /> Activate\n </button>\n )}\n </GlassCard>\n );\n })}\n </div>\n </>\n )}\n\n {/* Create modal */}\n {showCreate && (\n <Modal title=\"New workflow\" onClose={closeCreate}>\n <form onSubmit={handleCreate} className=\"space-y-4\">\n {formError && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n <div>\n <label htmlFor=\"wf-name\" className={labelCls}>\n Name <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"wf-name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder=\"e.g. Invoice → notify finance\"\n className={inputCls}\n required\n />\n </div>\n <div>\n <label htmlFor=\"wf-trigger\" className={labelCls}>\n Trigger event <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"wf-trigger\"\n type=\"text\"\n value={triggerEvent}\n onChange={(e) => setTriggerEvent(e.target.value)}\n placeholder=\"e.g. InvoiceReceived, RenewalDetected\"\n className={inputCls}\n required\n />\n </div>\n <div>\n <label htmlFor=\"wf-desc\" className={labelCls}>\n Description\n </label>\n <textarea\n id=\"wf-desc\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n rows={3}\n placeholder=\"What should this workflow do when the event fires?\"\n className={inputCls}\n />\n </div>\n <div className=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={closeCreate}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={create.isPending}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {create.isPending ? 'Creating…' : 'Create workflow'}\n </button>\n </div>\n </form>\n </Modal>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent detail
|
|
3
|
+
* @module subscriberbot/pages/agents
|
|
4
|
+
*
|
|
5
|
+
* A single AI agent: its kind, posture, last run, and the autonomous policies
|
|
6
|
+
* that bound it. Reads `subAgent(id)`; changes posture via `subSetAgentStatus`.
|
|
7
|
+
* The "Run history" timeline is derived from the agent's real `lastRunAt` and
|
|
8
|
+
* each policy's real `lastTriggeredAt` — never mocked.
|
|
9
|
+
*/
|
|
10
|
+
export declare function AgentDetailPage(): import("react").JSX.Element;
|
|
11
|
+
//# sourceMappingURL=AgentDetailPage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AgentDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/subscriberbot/pages/agents/AgentDetailPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAwRH,wBAAgB,eAAe,gCAmP9B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AgentNewPage.d.ts","sourceRoot":"","sources":["../../../../src/subscriberbot/pages/agents/AgentNewPage.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AA4BH,wBAAgB,YAAY,gCA6I3B"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Agents list
|
|
3
|
+
* @module subscriberbot/pages/agents
|
|
4
|
+
*
|
|
5
|
+
* The AI Agents Framework — specialized agents (Discovery, Renewal, Cost,
|
|
6
|
+
* Newsletter, Vendor, Compliance, Negotiation, Migration, Concierge) that each
|
|
7
|
+
* own a slice of the recurring-relationship problem. Filter by kind / posture.
|
|
8
|
+
* Reads `subListAgents`; sets posture inline via `subSetAgentStatus`.
|
|
9
|
+
*/
|
|
10
|
+
export declare function AgentsListPage(): import("react").JSX.Element;
|
|
11
|
+
//# sourceMappingURL=AgentsListPage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AgentsListPage.d.ts","sourceRoot":"","sources":["../../../../src/subscriberbot/pages/agents/AgentsListPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA0EH,wBAAgB,cAAc,gCAmN7B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AgentsListPage.js","names":[],"sources":["../../../../src/subscriberbot/pages/agents/AgentsListPage.tsx"],"sourcesContent":["/**\n * AI Agents list\n * @module subscriberbot/pages/agents\n *\n * The AI Agents Framework — specialized agents (Discovery, Renewal, Cost,\n * Newsletter, Vendor, Compliance, Negotiation, Migration, Concierge) that each\n * own a slice of the recurring-relationship problem. Filter by kind / posture.\n * Reads `subListAgents`; sets posture inline via `subSetAgentStatus`.\n */\n\nimport { useState } from 'react';\nimport {\n Plus,\n Bot,\n Search,\n RefreshCw,\n DollarSign,\n Mail,\n Building2,\n ShieldCheck,\n Handshake,\n ArrowLeftRight,\n ConciergeBell,\n Clock,\n Zap,\n Sparkles,\n type LucideIcon,\n} from 'lucide-react';\nimport { PagePurpose, GlassCard } from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n StatusBadge,\n EmptyState,\n MetricsCard,\n Donut,\n Legend,\n LoadingView,\n ErrorView,\n type Segment,\n} from '../../components';\nimport { useSubscriberBot } from '../../context/SubscriberBotContext';\nimport { useAgents, useSetAgentStatus } from '../../hooks/useSubscriberBotApi';\nimport { formatEnumLabel, formatRelativeTime } from '../../utils/formatters';\nimport type { SubAgentKind, SubAgentStatus } from '../../types';\n\nconst KIND_OPTIONS: Array<{ value: '' | SubAgentKind; label: string }> = [\n { value: '', label: 'All kinds' },\n { value: 'DISCOVERY', label: 'Discovery' },\n { value: 'RENEWAL', label: 'Renewal' },\n { value: 'COST', label: 'Cost' },\n { value: 'NEWSLETTER', label: 'Newsletter' },\n { value: 'VENDOR', label: 'Vendor' },\n { value: 'COMPLIANCE', label: 'Compliance' },\n { value: 'NEGOTIATION', label: 'Negotiation' },\n { value: 'MIGRATION', label: 'Migration' },\n { value: 'CONCIERGE', label: 'Concierge' },\n];\n\nconst STATUS_OPTIONS: SubAgentStatus[] = ['DISABLED', 'SUGGEST_ONLY', 'AUTONOMOUS'];\n\n/** Kind → glyph. Purely presentational; falls back to the generic Bot mark. */\nconst KIND_ICON: Record<SubAgentKind, LucideIcon> = {\n DISCOVERY: Search,\n RENEWAL: RefreshCw,\n COST: DollarSign,\n NEWSLETTER: Mail,\n VENDOR: Building2,\n COMPLIANCE: ShieldCheck,\n NEGOTIATION: Handshake,\n MIGRATION: ArrowLeftRight,\n CONCIERGE: ConciergeBell,\n};\n\n/** Posture → semantic chart colour (tokens only) for the fleet donut. */\nconst POSTURE_COLOR: Record<SubAgentStatus, string> = {\n AUTONOMOUS: 'var(--color-status-success-text)',\n SUGGEST_ONLY: 'var(--color-accent-indigo)',\n DISABLED: 'var(--color-text-secondary)',\n};\n\nconst DAY_MS = 24 * 60 * 60 * 1000;\n\nexport function AgentsListPage() {\n const { navigateTo } = useSubscriberBot();\n const [kind, setKind] = useState<'' | SubAgentKind>('');\n const [status, setStatus] = useState<'' | SubAgentStatus>('');\n\n const { data, isLoading, isError, refetch } = useAgents({\n kind: kind || undefined,\n status: status || undefined,\n });\n const setAgentStatus = useSetAgentStatus();\n\n const agents = data ?? [];\n\n // --- Real, computed fleet aggregates (never hardcoded) --------------------\n const postureCounts: Record<SubAgentStatus, number> = {\n AUTONOMOUS: 0,\n SUGGEST_ONLY: 0,\n DISABLED: 0,\n };\n let recentlyRun = 0;\n const now = Date.now();\n for (const a of agents) {\n postureCounts[a.status] += 1;\n if (a.lastRunAt && now - new Date(a.lastRunAt).getTime() <= 7 * DAY_MS) recentlyRun += 1;\n }\n const postureData: Segment[] = STATUS_OPTIONS.map((s) => ({\n label: formatEnumLabel(s),\n value: postureCounts[s],\n color: POSTURE_COLOR[s],\n })).filter((d) => d.value > 0);\n\n return (\n <PageLayout\n title=\"AI Agents\"\n description=\"Specialized agents that automate your recurring portfolio\"\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/agents/new')}\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n <Plus className=\"h-4 w-4\" /> New agent\n </button>\n }\n >\n <PagePurpose className=\"mb-4\">\n Agents propose; your policies decide what they may do autonomously. Set each agent's posture\n — Disabled, Suggest-only, or Autonomous — and open one to review its policies.\n </PagePurpose>\n\n <div className=\"flex flex-wrap items-center gap-3\">\n <select\n aria-label=\"Filter by kind\"\n value={kind}\n onChange={(e) => setKind(e.target.value as '' | SubAgentKind)}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-xs text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n {KIND_OPTIONS.map((o) => (\n <option key={o.value} value={o.value}>\n {o.label}\n </option>\n ))}\n </select>\n <select\n aria-label=\"Filter by posture\"\n value={status}\n onChange={(e) => setStatus(e.target.value as '' | SubAgentStatus)}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-xs text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"\">Any posture</option>\n {STATUS_OPTIONS.map((s) => (\n <option key={s} value={s}>\n {formatEnumLabel(s)}\n </option>\n ))}\n </select>\n </div>\n\n {isLoading ? (\n <LoadingView label=\"Loading agents…\" />\n ) : isError ? (\n <ErrorView message=\"Could not load agents.\" onRetry={() => void refetch()} />\n ) : agents.length === 0 ? (\n <div className=\"mt-6\">\n <EmptyState\n illustration=\"empty-data\"\n icon={<Bot className=\"h-12 w-12\" />}\n title=\"No agents yet\"\n description=\"Create your first AI agent to start automating discovery, renewals, or cost optimization.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/agents/new')}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n New agent\n </button>\n }\n />\n </div>\n ) : (\n <>\n {/* --- Fleet overview: posture mix + live counts (computed) ------ */}\n <div className=\"mt-6 grid grid-cols-1 gap-4 lg:grid-cols-3\">\n <GlassCard treatment=\"insight\" className=\"p-5 lg:col-span-1\">\n <p className=\"text-sm font-semibold text-text-primary\">Fleet posture</p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n How your agents are allowed to act\n </p>\n <div className=\"mt-4 flex items-center gap-5\">\n <Donut\n data={postureData}\n size={132}\n thickness={16}\n centerLabel={String(agents.length)}\n centerSub={agents.length === 1 ? 'agent' : 'agents'}\n />\n <Legend className=\"flex-1\" data={postureData} />\n </div>\n </GlassCard>\n\n <div className=\"grid grid-cols-2 gap-4 lg:col-span-2\">\n <MetricsCard\n icon={<Bot className=\"h-5 w-5\" />}\n value={agents.length}\n label=\"Total agents\"\n />\n <MetricsCard\n icon={<Zap className=\"h-5 w-5\" />}\n value={postureCounts.AUTONOMOUS}\n label=\"Autonomous\"\n />\n <MetricsCard\n icon={<Sparkles className=\"h-5 w-5\" />}\n value={postureCounts.SUGGEST_ONLY}\n label=\"Suggest-only\"\n />\n <MetricsCard\n icon={<Clock className=\"h-5 w-5\" />}\n value={recentlyRun}\n label=\"Ran in last 7 days\"\n />\n </div>\n </div>\n\n {/* --- Agent cards ------------------------------------------------ */}\n <div className=\"mt-6 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {agents.map((a) => {\n const Icon = KIND_ICON[a.kind] ?? Bot;\n return (\n <GlassCard\n key={a.id}\n treatment=\"metric\"\n glow={a.status === 'AUTONOMOUS'}\n className=\"flex flex-col p-5\"\n >\n <button\n type=\"button\"\n onClick={() => navigateTo(`/agents/${a.id}`)}\n className=\"rounded-lg text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n aria-label={`Open ${a.name}`}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"flex size-10 items-center justify-center rounded-xl bg-[var(--color-accent-soft)] text-text-primary\">\n <Icon className=\"h-5 w-5\" />\n </span>\n <StatusBadge status={a.status} />\n </div>\n <p className=\"mt-3 truncate text-sm font-semibold text-text-primary\">\n {a.name}\n </p>\n <p className=\"text-xs text-text-secondary\">{formatEnumLabel(a.kind)} agent</p>\n <p className=\"mt-2 line-clamp-2 min-h-[2rem] text-xs text-text-secondary\">\n {a.description || 'No description provided.'}\n </p>\n <p className=\"mt-3 flex items-center gap-1.5 text-xs text-text-secondary\">\n <Clock className=\"h-3.5 w-3.5\" aria-hidden />\n {a.lastRunAt ? `Last run ${formatRelativeTime(a.lastRunAt)}` : 'Never run'}\n </p>\n </button>\n <div className=\"mt-4 border-t border-border-subtle pt-3\">\n <label className=\"mb-1 block text-[10px] font-medium uppercase tracking-wide text-text-secondary\">\n Posture\n </label>\n <select\n aria-label={`Posture for ${a.name}`}\n value={a.status}\n disabled={setAgentStatus.isPending}\n onChange={(e) =>\n setAgentStatus.mutate({ id: a.id, status: e.target.value as SubAgentStatus })\n }\n className=\"w-full rounded-lg border border-border-subtle bg-bg-surface px-2 py-1.5 text-xs text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] disabled:opacity-50\"\n >\n {STATUS_OPTIONS.map((s) => (\n <option key={s} value={s}>\n {formatEnumLabel(s)}\n </option>\n ))}\n </select>\n </div>\n </GlassCard>\n );\n })}\n </div>\n </>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AgentsListPage.js","names":[],"sources":["../../../../src/subscriberbot/pages/agents/AgentsListPage.tsx"],"sourcesContent":["/**\n * AI Agents list\n * @module subscriberbot/pages/agents\n *\n * The AI Agents Framework — specialized agents (Discovery, Renewal, Cost,\n * Newsletter, Vendor, Compliance, Negotiation, Migration, Concierge) that each\n * own a slice of the recurring-relationship problem. Filter by kind / posture.\n * Reads `subListAgents`; sets posture inline via `subSetAgentStatus`.\n */\n\nimport { useState } from 'react';\nimport {\n Plus,\n Bot,\n Search,\n RefreshCw,\n DollarSign,\n Mail,\n Building2,\n ShieldCheck,\n Handshake,\n ArrowLeftRight,\n ConciergeBell,\n Clock,\n Zap,\n Sparkles,\n type LucideIcon,\n} from 'lucide-react';\nimport { PagePurpose, GlassCard } from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n StatusBadge,\n EmptyState,\n MetricsCard,\n Donut,\n Legend,\n LoadingView,\n ErrorView,\n type Segment,\n} from '../../components';\nimport { useSubscriberBot } from '../../context/SubscriberBotContext';\nimport { useAgents, useSetAgentStatus } from '../../hooks/useSubscriberBotApi';\nimport { formatEnumLabel, formatRelativeTime } from '../../utils/formatters';\nimport type { SubAgentKind, SubAgentStatus } from '../../types';\n\nconst KIND_OPTIONS: Array<{ value: '' | SubAgentKind; label: string }> = [\n { value: '', label: 'All kinds' },\n { value: 'DISCOVERY', label: 'Discovery' },\n { value: 'RENEWAL', label: 'Renewal' },\n { value: 'COST', label: 'Cost' },\n { value: 'NEWSLETTER', label: 'Newsletter' },\n { value: 'VENDOR', label: 'Vendor' },\n { value: 'COMPLIANCE', label: 'Compliance' },\n { value: 'NEGOTIATION', label: 'Negotiation' },\n { value: 'MIGRATION', label: 'Migration' },\n { value: 'CONCIERGE', label: 'Concierge' },\n];\n\nconst STATUS_OPTIONS: SubAgentStatus[] = ['DISABLED', 'SUGGEST_ONLY', 'AUTONOMOUS'];\n\n/** Kind → glyph. Purely presentational; falls back to the generic Bot mark. */\nconst KIND_ICON: Record<SubAgentKind, LucideIcon> = {\n DISCOVERY: Search,\n RENEWAL: RefreshCw,\n COST: DollarSign,\n NEWSLETTER: Mail,\n VENDOR: Building2,\n COMPLIANCE: ShieldCheck,\n NEGOTIATION: Handshake,\n MIGRATION: ArrowLeftRight,\n CONCIERGE: ConciergeBell,\n};\n\n/** Posture → semantic chart colour (tokens only) for the fleet donut. */\nconst POSTURE_COLOR: Record<SubAgentStatus, string> = {\n AUTONOMOUS: 'var(--color-status-success-text)',\n SUGGEST_ONLY: 'var(--color-accent-indigo)',\n DISABLED: 'var(--color-text-secondary)',\n};\n\nconst DAY_MS = 24 * 60 * 60 * 1000;\n\nexport function AgentsListPage() {\n const { navigateTo } = useSubscriberBot();\n const [kind, setKind] = useState<'' | SubAgentKind>('');\n const [status, setStatus] = useState<'' | SubAgentStatus>('');\n\n const { data, isLoading, isError, refetch } = useAgents({\n kind: kind || undefined,\n status: status || undefined,\n });\n const setAgentStatus = useSetAgentStatus();\n\n const agents = data ?? [];\n\n // --- Real, computed fleet aggregates (never hardcoded) --------------------\n const postureCounts: Record<SubAgentStatus, number> = {\n AUTONOMOUS: 0,\n SUGGEST_ONLY: 0,\n DISABLED: 0,\n };\n let recentlyRun = 0;\n const now = Date.now();\n for (const a of agents) {\n postureCounts[a.status] += 1;\n if (a.lastRunAt && now - new Date(a.lastRunAt).getTime() <= 7 * DAY_MS) recentlyRun += 1;\n }\n const postureData: Segment[] = STATUS_OPTIONS.map((s) => ({\n label: formatEnumLabel(s),\n value: postureCounts[s],\n color: POSTURE_COLOR[s],\n })).filter((d) => d.value > 0);\n\n return (\n <PageLayout\n title=\"AI Agents\"\n description=\"Specialized agents that automate your recurring portfolio\"\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/agents/new')}\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n <Plus className=\"h-4 w-4\" /> New agent\n </button>\n }\n >\n <PagePurpose className=\"mb-4\">\n Agents propose; your policies decide what they may do autonomously. Set each agent's posture\n — Disabled, Suggest-only, or Autonomous — and open one to review its policies.\n </PagePurpose>\n\n <div className=\"flex flex-wrap items-center gap-3\">\n <select\n aria-label=\"Filter by kind\"\n value={kind}\n onChange={(e) => setKind(e.target.value as '' | SubAgentKind)}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-xs text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n {KIND_OPTIONS.map((o) => (\n <option key={o.value} value={o.value}>\n {o.label}\n </option>\n ))}\n </select>\n <select\n aria-label=\"Filter by posture\"\n value={status}\n onChange={(e) => setStatus(e.target.value as '' | SubAgentStatus)}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-xs text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"\">Any posture</option>\n {STATUS_OPTIONS.map((s) => (\n <option key={s} value={s}>\n {formatEnumLabel(s)}\n </option>\n ))}\n </select>\n </div>\n\n {isLoading ? (\n <LoadingView label=\"Loading agents…\" />\n ) : isError ? (\n <ErrorView message=\"Could not load agents.\" onRetry={() => void refetch()} />\n ) : agents.length === 0 ? (\n <div className=\"mt-6\">\n <EmptyState\n illustration=\"empty-data\"\n icon={<Bot className=\"h-12 w-12\" />}\n title=\"No agents yet\"\n description=\"Create your first AI agent to start automating discovery, renewals, or cost optimization.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/agents/new')}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n New agent\n </button>\n }\n />\n </div>\n ) : (\n <>\n {/* --- Fleet overview: posture mix + live counts (computed) ------ */}\n <div className=\"mt-6 grid grid-cols-1 gap-4 lg:grid-cols-3\">\n <GlassCard treatment=\"insight\" className=\"p-5 lg:col-span-1\">\n <p className=\"text-sm font-semibold text-text-primary\">Fleet posture</p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n How your agents are allowed to act\n </p>\n <div className=\"mt-4 flex items-center gap-5\">\n <Donut\n data={postureData}\n size={132}\n thickness={16}\n centerLabel={String(agents.length)}\n centerSub={agents.length === 1 ? 'agent' : 'agents'}\n />\n <Legend className=\"flex-1\" data={postureData} />\n </div>\n </GlassCard>\n\n <div className=\"grid grid-cols-2 gap-4 lg:col-span-2\">\n <MetricsCard\n icon={<Bot className=\"h-5 w-5\" />}\n value={agents.length}\n label=\"Total agents\"\n />\n <MetricsCard\n icon={<Zap className=\"h-5 w-5\" />}\n value={postureCounts.AUTONOMOUS}\n label=\"Autonomous\"\n />\n <MetricsCard\n icon={<Sparkles className=\"h-5 w-5\" />}\n value={postureCounts.SUGGEST_ONLY}\n label=\"Suggest-only\"\n />\n <MetricsCard\n icon={<Clock className=\"h-5 w-5\" />}\n value={recentlyRun}\n label=\"Ran in last 7 days\"\n />\n </div>\n </div>\n\n {/* --- Agent cards ------------------------------------------------ */}\n <div className=\"mt-6 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {agents.map((a) => {\n const Icon = KIND_ICON[a.kind] ?? Bot;\n return (\n <GlassCard\n key={a.id}\n treatment=\"metric\"\n glow={a.status === 'AUTONOMOUS'}\n className=\"flex flex-col p-5\"\n >\n <button\n type=\"button\"\n onClick={() => navigateTo(`/agents/${a.id}`)}\n className=\"rounded-lg text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n aria-label={`Open ${a.name}`}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"flex size-10 items-center justify-center rounded-xl bg-[var(--color-accent-soft)] text-text-primary\">\n <Icon className=\"h-5 w-5\" />\n </span>\n <StatusBadge status={a.status} />\n </div>\n <p className=\"mt-3 truncate text-sm font-semibold text-text-primary\">\n {a.name}\n </p>\n <p className=\"text-xs text-text-secondary\">{formatEnumLabel(a.kind)} agent</p>\n <p className=\"mt-2 line-clamp-2 min-h-[2rem] text-xs text-text-secondary\">\n {a.description || 'No description provided.'}\n </p>\n <p className=\"mt-3 flex items-center gap-1.5 text-xs text-text-secondary\">\n <Clock className=\"h-3.5 w-3.5\" aria-hidden />\n {a.lastRunAt ? `Last run ${formatRelativeTime(a.lastRunAt)}` : 'Never run'}\n </p>\n </button>\n <div className=\"mt-4 border-t border-border-subtle pt-3\">\n <label className=\"mb-1 block text-[10px] font-medium uppercase tracking-wide text-text-secondary\">\n Posture\n </label>\n <select\n aria-label={`Posture for ${a.name}`}\n value={a.status}\n disabled={setAgentStatus.isPending}\n onChange={(e) =>\n setAgentStatus.mutate({\n id: a.id,\n status: e.target.value as SubAgentStatus,\n })\n }\n className=\"w-full rounded-lg border border-border-subtle bg-bg-surface px-2 py-1.5 text-xs text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] disabled:opacity-50\"\n >\n {STATUS_OPTIONS.map((s) => (\n <option key={s} value={s}>\n {formatEnumLabel(s)}\n </option>\n ))}\n </select>\n </div>\n </GlassCard>\n );\n })}\n </div>\n </>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider detail
|
|
3
|
+
* @module subscriberbot/pages/providers
|
|
4
|
+
*
|
|
5
|
+
* A provider profile from the registry: category, status, region, scores,
|
|
6
|
+
* tags and its published plans. Reads `subProvider(id)`; writes
|
|
7
|
+
* `subUpdateProvider`, `subArchiveProvider`, `subCreatePlan`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function ProviderDetailPage(): import("react").JSX.Element;
|
|
10
|
+
//# sourceMappingURL=ProviderDetailPage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProviderDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/subscriberbot/pages/providers/ProviderDetailPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAkkBH,wBAAgB,kBAAkB,gCAiUjC"}
|