@burdenoff/microfe-subscriberbot 2026.626.1 → 2026.628.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/index.js +4 -4
- package/dist/subscriberbot/SubscriberBotRoot.js +61 -133
- package/dist/subscriberbot/SubscriberBotRoot.js.map +1 -1
- package/dist/subscriberbot/SubscriberBotRoutes.js +56 -187
- package/dist/subscriberbot/SubscriberBotRoutes.js.map +1 -1
- package/dist/subscriberbot/components/StateViews.js +49 -0
- package/dist/subscriberbot/components/StateViews.js.map +1 -0
- package/dist/subscriberbot/components/StatusBadge.js +45 -47
- package/dist/subscriberbot/components/StatusBadge.js.map +1 -1
- package/dist/subscriberbot/context/SubscriberBotContext.js +22 -30
- package/dist/subscriberbot/context/SubscriberBotContext.js.map +1 -1
- package/dist/subscriberbot/hooks/useSubscriberBotApi.js +342 -195
- package/dist/subscriberbot/hooks/useSubscriberBotApi.js.map +1 -1
- package/dist/subscriberbot/index.js +1 -1
- package/dist/subscriberbot/pages/DashboardPage.js +269 -0
- package/dist/subscriberbot/pages/DashboardPage.js.map +1 -0
- package/dist/subscriberbot/pages/InboxPage.js +186 -0
- package/dist/subscriberbot/pages/InboxPage.js.map +1 -0
- package/dist/subscriberbot/pages/InvoicesPage.js +211 -0
- package/dist/subscriberbot/pages/InvoicesPage.js.map +1 -0
- package/dist/subscriberbot/pages/WorkflowsPage.js +236 -0
- package/dist/subscriberbot/pages/WorkflowsPage.js.map +1 -0
- package/dist/subscriberbot/pages/agents/AgentDetailPage.js +160 -0
- package/dist/subscriberbot/pages/agents/AgentDetailPage.js.map +1 -0
- package/dist/subscriberbot/pages/agents/AgentNewPage.js +140 -0
- package/dist/subscriberbot/pages/agents/AgentNewPage.js.map +1 -0
- package/dist/subscriberbot/pages/agents/AgentsListPage.js +178 -0
- package/dist/subscriberbot/pages/agents/AgentsListPage.js.map +1 -0
- package/dist/subscriberbot/pages/providers/ProviderDetailPage.js +179 -0
- package/dist/subscriberbot/pages/providers/ProviderDetailPage.js.map +1 -0
- package/dist/subscriberbot/pages/providers/ProviderNewPage.js +185 -0
- package/dist/subscriberbot/pages/providers/ProviderNewPage.js.map +1 -0
- package/dist/subscriberbot/pages/providers/ProvidersListPage.js +254 -0
- package/dist/subscriberbot/pages/providers/ProvidersListPage.js.map +1 -0
- package/dist/subscriberbot/pages/relationships/RelationshipDetailPage.js +306 -0
- package/dist/subscriberbot/pages/relationships/RelationshipDetailPage.js.map +1 -0
- package/dist/subscriberbot/pages/relationships/RelationshipNewPage.js +253 -0
- package/dist/subscriberbot/pages/relationships/RelationshipNewPage.js.map +1 -0
- package/dist/subscriberbot/pages/relationships/RelationshipsListPage.js +248 -0
- package/dist/subscriberbot/pages/relationships/RelationshipsListPage.js.map +1 -0
- package/dist/subscriberbot/utils/formatters.js +50 -32
- package/dist/subscriberbot/utils/formatters.js.map +1 -1
- package/package.json +1 -1
- package/dist/subscriberbot/components/InvestmentCard.js +0 -97
- package/dist/subscriberbot/components/InvestmentCard.js.map +0 -1
- package/dist/subscriberbot/components/RatingDisplay.js +0 -35
- package/dist/subscriberbot/components/RatingDisplay.js.map +0 -1
- package/dist/subscriberbot/mocks/data.js +0 -1517
- package/dist/subscriberbot/mocks/data.js.map +0 -1
- package/dist/subscriberbot/mocks/users.js +0 -34
- package/dist/subscriberbot/mocks/users.js.map +0 -1
- package/dist/subscriberbot/pages/HomePage.js +0 -930
- package/dist/subscriberbot/pages/HomePage.js.map +0 -1
- package/dist/subscriberbot/pages/admin/AdminAnalyticsPage.js +0 -651
- package/dist/subscriberbot/pages/admin/AdminAnalyticsPage.js.map +0 -1
- package/dist/subscriberbot/pages/admin/AdminDashboard.js +0 -679
- package/dist/subscriberbot/pages/admin/AdminDashboard.js.map +0 -1
- package/dist/subscriberbot/pages/admin/AdminFinancialPage.js +0 -427
- package/dist/subscriberbot/pages/admin/AdminFinancialPage.js.map +0 -1
- package/dist/subscriberbot/pages/admin/AdminOrdersPage.js +0 -665
- package/dist/subscriberbot/pages/admin/AdminOrdersPage.js.map +0 -1
- package/dist/subscriberbot/pages/admin/AdminProfilePage.js +0 -261
- package/dist/subscriberbot/pages/admin/AdminProfilePage.js.map +0 -1
- package/dist/subscriberbot/pages/admin/AdminSettingsPage.js +0 -654
- package/dist/subscriberbot/pages/admin/AdminSettingsPage.js.map +0 -1
- package/dist/subscriberbot/pages/admin/CartManagementPage.js +0 -874
- package/dist/subscriberbot/pages/admin/CartManagementPage.js.map +0 -1
- package/dist/subscriberbot/pages/admin/UserManagementPage.js +0 -898
- package/dist/subscriberbot/pages/admin/UserManagementPage.js.map +0 -1
- package/dist/subscriberbot/pages/cart-owner/AnalyticsDashboard.js +0 -839
- package/dist/subscriberbot/pages/cart-owner/AnalyticsDashboard.js.map +0 -1
- package/dist/subscriberbot/pages/cart-owner/BusinessSettingsPage.js +0 -705
- package/dist/subscriberbot/pages/cart-owner/BusinessSettingsPage.js.map +0 -1
- package/dist/subscriberbot/pages/cart-owner/CartOwnerDashboard.js +0 -802
- package/dist/subscriberbot/pages/cart-owner/CartOwnerDashboard.js.map +0 -1
- package/dist/subscriberbot/pages/cart-owner/CartOwnerProfilePage.js +0 -891
- package/dist/subscriberbot/pages/cart-owner/CartOwnerProfilePage.js.map +0 -1
- package/dist/subscriberbot/pages/cart-owner/CartServiceConfigPage.js +0 -633
- package/dist/subscriberbot/pages/cart-owner/CartServiceConfigPage.js.map +0 -1
- package/dist/subscriberbot/pages/cart-owner/InventoryManagementPage.js +0 -910
- package/dist/subscriberbot/pages/cart-owner/InventoryManagementPage.js.map +0 -1
- package/dist/subscriberbot/pages/cart-owner/OrderManagementPage.js +0 -879
- package/dist/subscriberbot/pages/cart-owner/OrderManagementPage.js.map +0 -1
- package/dist/subscriberbot/pages/cart-owner/StaffManagementPage.js +0 -838
- package/dist/subscriberbot/pages/cart-owner/StaffManagementPage.js.map +0 -1
- package/dist/subscriberbot/pages/common/HelpSupportPage.js +0 -1323
- package/dist/subscriberbot/pages/common/HelpSupportPage.js.map +0 -1
- package/dist/subscriberbot/pages/common/NotificationCenterPage.js +0 -672
- package/dist/subscriberbot/pages/common/NotificationCenterPage.js.map +0 -1
- package/dist/subscriberbot/pages/common/SettingsPage.js +0 -973
- package/dist/subscriberbot/pages/common/SettingsPage.js.map +0 -1
- package/dist/subscriberbot/pages/customer/BookingDetailsPage.js +0 -626
- package/dist/subscriberbot/pages/customer/BookingDetailsPage.js.map +0 -1
- package/dist/subscriberbot/pages/customer/CartDetailPage.js +0 -654
- package/dist/subscriberbot/pages/customer/CartDetailPage.js.map +0 -1
- package/dist/subscriberbot/pages/customer/CartDiscoveryPage.js +0 -555
- package/dist/subscriberbot/pages/customer/CartDiscoveryPage.js.map +0 -1
- package/dist/subscriberbot/pages/customer/CheckoutPage.js +0 -529
- package/dist/subscriberbot/pages/customer/CheckoutPage.js.map +0 -1
- package/dist/subscriberbot/pages/customer/CustomerDashboard.js +0 -532
- package/dist/subscriberbot/pages/customer/CustomerDashboard.js.map +0 -1
- package/dist/subscriberbot/pages/customer/CustomerProfilePage.js +0 -630
- package/dist/subscriberbot/pages/customer/CustomerProfilePage.js.map +0 -1
- package/dist/subscriberbot/pages/customer/DirectServiceBookingPage.js +0 -633
- package/dist/subscriberbot/pages/customer/DirectServiceBookingPage.js.map +0 -1
- package/dist/subscriberbot/pages/customer/FavoritesPage.js +0 -484
- package/dist/subscriberbot/pages/customer/FavoritesPage.js.map +0 -1
- package/dist/subscriberbot/pages/customer/OrderHistoryPage.js +0 -446
- package/dist/subscriberbot/pages/customer/OrderHistoryPage.js.map +0 -1
- package/dist/subscriberbot/pages/customer/OrderTrackingPage.js +0 -394
- package/dist/subscriberbot/pages/customer/OrderTrackingPage.js.map +0 -1
- package/dist/subscriberbot/pages/customer/PaymentMethodsPage.js +0 -495
- package/dist/subscriberbot/pages/customer/PaymentMethodsPage.js.map +0 -1
- package/dist/subscriberbot/pages/customer/ReviewsPage.js +0 -613
- package/dist/subscriberbot/pages/customer/ReviewsPage.js.map +0 -1
- package/dist/subscriberbot/pages/customer/SavedAddressesPage.js +0 -450
- package/dist/subscriberbot/pages/customer/SavedAddressesPage.js.map +0 -1
- package/dist/subscriberbot/pages/investor/InvestmentDetailsPage.js +0 -886
- package/dist/subscriberbot/pages/investor/InvestmentDetailsPage.js.map +0 -1
- package/dist/subscriberbot/pages/investor/InvestmentOpportunitiesPage.js +0 -947
- package/dist/subscriberbot/pages/investor/InvestmentOpportunitiesPage.js.map +0 -1
- package/dist/subscriberbot/pages/investor/InvestorAnalyticsPage.js +0 -687
- package/dist/subscriberbot/pages/investor/InvestorAnalyticsPage.js.map +0 -1
- package/dist/subscriberbot/pages/investor/InvestorDashboard.js +0 -451
- package/dist/subscriberbot/pages/investor/InvestorDashboard.js.map +0 -1
- package/dist/subscriberbot/pages/investor/InvestorInvestmentsPage.js +0 -406
- package/dist/subscriberbot/pages/investor/InvestorInvestmentsPage.js.map +0 -1
- package/dist/subscriberbot/pages/investor/InvestorPortfolioPage.js +0 -575
- package/dist/subscriberbot/pages/investor/InvestorPortfolioPage.js.map +0 -1
- package/dist/subscriberbot/pages/investor/InvestorProfilePage.js +0 -1070
- package/dist/subscriberbot/pages/investor/InvestorProfilePage.js.map +0 -1
- package/dist/subscriberbot/pages/worker/TaskDetailsPage.js +0 -535
- package/dist/subscriberbot/pages/worker/TaskDetailsPage.js.map +0 -1
- package/dist/subscriberbot/pages/worker/WorkerDashboard.js +0 -666
- package/dist/subscriberbot/pages/worker/WorkerDashboard.js.map +0 -1
- package/dist/subscriberbot/pages/worker/WorkerOrdersPage.js +0 -681
- package/dist/subscriberbot/pages/worker/WorkerOrdersPage.js.map +0 -1
- package/dist/subscriberbot/pages/worker/WorkerProfilePage.js +0 -692
- package/dist/subscriberbot/pages/worker/WorkerProfilePage.js.map +0 -1
- package/dist/subscriberbot/pages/worker/WorkerSchedulePage.js +0 -588
- package/dist/subscriberbot/pages/worker/WorkerSchedulePage.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InboxPage.js","names":[],"sources":["../../../src/subscriberbot/pages/InboxPage.tsx"],"sourcesContent":["/**\n * Universal Inbox\n * @module subscriberbot/pages\n *\n * Every merged subscription communication in one timeline, AI-categorized.\n * Filter by category / unread, mark messages read. Reads\n * `subListInboxMessages` (a SubInboxMessageConnection); writes\n * `subMarkMessageRead`.\n */\n\nimport { useState } from 'react';\nimport { Mail, MailOpen, AlertTriangle, Inbox as InboxIcon } from 'lucide-react';\nimport { PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../components/PageLayout';\nimport { StatusBadge } from '../components/StatusBadge';\nimport { EmptyState } from '../components/EmptyState';\nimport { LoadingView, ErrorView } from '../components/StateViews';\nimport { useSubscriberBot } from '../context/SubscriberBotContext';\nimport { useInboxMessages, useMarkMessageRead } from '../hooks/useSubscriberBotApi';\nimport { cn } from '../utils/cn';\nimport { formatRelativeTime } from '../utils/formatters';\nimport type { SubMessageCategory } from '../types';\n\nconst PAGE_SIZE = 25;\n\nconst CATEGORY_OPTIONS: Array<{ value: '' | SubMessageCategory; label: string }> = [\n { value: '', label: 'All categories' },\n { value: 'BILLING', label: 'Billing' },\n { value: 'RENEWAL', label: 'Renewals' },\n { value: 'REQUIRED_ACTION', label: 'Required actions' },\n { value: 'SECURITY_ALERT', label: 'Security alerts' },\n { value: 'PRODUCT_UPDATE', label: 'Product updates' },\n { value: 'MARKETING', label: 'Marketing' },\n { value: 'OTHER', label: 'Other' },\n];\n\nexport function InboxPage() {\n const { navigateTo } = useSubscriberBot();\n const [page, setPage] = useState(0);\n const [category, setCategory] = useState<'' | SubMessageCategory>('');\n const [unreadOnly, setUnreadOnly] = useState(false);\n\n const { data, isLoading, isError, refetch, isFetching } = useInboxMessages({\n category: category || undefined,\n unreadOnly: unreadOnly || undefined,\n pagination: { limit: PAGE_SIZE, offset: page * PAGE_SIZE },\n });\n const markRead = useMarkMessageRead();\n\n const items = data?.items ?? [];\n const hasMore = data?.pageInfo.hasMore ?? false;\n\n return (\n <PageLayout\n title=\"Universal Inbox\"\n description=\"Every subscription communication, merged and categorized\"\n >\n <PagePurpose className=\"mb-4\">\n Subscription communication is noisy and scattered. The Universal Inbox merges every channel\n a provider uses and uses AI to categorize each message so you never miss a renewal,\n required action or security notice.\n </PagePurpose>\n\n <div className=\"flex flex-wrap items-center gap-3\">\n <select\n value={category}\n onChange={(e) => {\n setCategory(e.target.value as '' | SubMessageCategory);\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 {CATEGORY_OPTIONS.map((o) => (\n <option key={o.value} value={o.value}>\n {o.label}\n </option>\n ))}\n </select>\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={unreadOnly}\n onChange={(e) => {\n setUnreadOnly(e.target.checked);\n setPage(0);\n }}\n className=\"size-4 rounded border-border-subtle\"\n />\n Unread only\n </label>\n </div>\n\n {isLoading ? (\n <LoadingView label=\"Loading inbox…\" />\n ) : isError ? (\n <ErrorView message=\"Could not load the inbox.\" onRetry={() => void refetch()} />\n ) : items.length === 0 ? (\n <div className=\"mt-6\">\n <EmptyState\n illustration=\"empty-inbox\"\n icon={<InboxIcon className=\"h-12 w-12\" />}\n title=\"Inbox is empty\"\n description={\n unreadOnly || category\n ? 'No messages match these filters.'\n : 'Merged subscription communications will appear here once channels start ingesting.'\n }\n />\n </div>\n ) : (\n <>\n <div className=\"mt-6 divide-y divide-border-subtle rounded-lg border border-border-subtle bg-bg-surface\">\n {items.map((m) => (\n <div\n key={m.id}\n className={cn(\n 'flex items-start gap-3 p-4',\n !m.isRead && 'bg-accent-wash'\n )}\n >\n <span className=\"mt-0.5 shrink-0 text-text-secondary\">\n {m.isRead ? (\n <MailOpen className=\"h-4 w-4\" />\n ) : (\n <Mail className=\"h-4 w-4 text-text-link\" />\n )}\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-2\">\n <p\n className={cn(\n 'truncate text-sm',\n m.isRead ? 'font-normal text-text-primary' : 'font-semibold text-text-primary'\n )}\n >\n {m.subject || '(no subject)'}\n </p>\n {m.actionRequired && (\n <span\n title=\"Action required\"\n className=\"inline-flex items-center gap-1 rounded-full bg-status-warning-bg-subtle px-2 py-0.5 text-[10px] font-medium text-status-warning-text\"\n >\n <AlertTriangle className=\"h-3 w-3\" /> Action\n </span>\n )}\n </div>\n {m.preview && (\n <p className=\"mt-0.5 line-clamp-2 text-xs text-text-secondary\">{m.preview}</p>\n )}\n <div className=\"mt-1.5 flex flex-wrap items-center gap-2\">\n <StatusBadge status={m.category} />\n <span className=\"text-xs text-text-secondary\">\n {formatRelativeTime(m.receivedAt)}\n </span>\n {m.relationshipId && (\n <button\n type=\"button\"\n onClick={() => navigateTo(`/relationships/${m.relationshipId}`)}\n className=\"text-xs font-medium text-text-link hover:underline\"\n >\n View relationship\n </button>\n )}\n </div>\n </div>\n {!m.isRead && (\n <button\n type=\"button\"\n disabled={markRead.isPending}\n onClick={() => markRead.mutate(m.id)}\n className=\"shrink-0 rounded-lg border border-border-subtle px-2.5 py-1 text-xs font-medium text-text-primary hover:bg-bg-sunken disabled:opacity-50\"\n >\n Mark read\n </button>\n )}\n </div>\n ))}\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 </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { useSubscriberBot as e } from "../context/SubscriberBotContext.js";
|
|
2
|
+
import { cn as t } from "../utils/cn.js";
|
|
3
|
+
import { PageLayout as n } from "../components/PageLayout.js";
|
|
4
|
+
import { MetricsCard as r } from "../components/MetricsCard.js";
|
|
5
|
+
import { formatCurrency as i, formatDate as a } from "../utils/formatters.js";
|
|
6
|
+
import { StatusBadge as o } from "../components/StatusBadge.js";
|
|
7
|
+
import { EmptyState as s } from "../components/EmptyState.js";
|
|
8
|
+
import { ErrorView as c, LoadingView as l } from "../components/StateViews.js";
|
|
9
|
+
import { useInvoices as u, useMarkInvoicePaid as d } from "../hooks/useSubscriberBotApi.js";
|
|
10
|
+
import { useMemo as f, useState as p } from "react";
|
|
11
|
+
import { FileText as m, Receipt as h } from "lucide-react";
|
|
12
|
+
import { Fragment as g, jsx as _, jsxs as v } from "react/jsx-runtime";
|
|
13
|
+
import { PagePurpose as y } from "@burdenoff/fe-libs/ui";
|
|
14
|
+
//#region src/subscriberbot/pages/InvoicesPage.tsx
|
|
15
|
+
var b = 25, x = [
|
|
16
|
+
{
|
|
17
|
+
value: "",
|
|
18
|
+
label: "All statuses"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
value: "ISSUED",
|
|
22
|
+
label: "Issued"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
value: "OVERDUE",
|
|
26
|
+
label: "Overdue"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
value: "PAID",
|
|
30
|
+
label: "Paid"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
value: "FAILED",
|
|
34
|
+
label: "Failed"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
value: "DRAFT",
|
|
38
|
+
label: "Draft"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
value: "CANCELLED",
|
|
42
|
+
label: "Cancelled"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
value: "REFUNDED",
|
|
46
|
+
label: "Refunded"
|
|
47
|
+
}
|
|
48
|
+
], S = new Set([
|
|
49
|
+
"ISSUED",
|
|
50
|
+
"OVERDUE",
|
|
51
|
+
"FAILED"
|
|
52
|
+
]);
|
|
53
|
+
function C() {
|
|
54
|
+
let { navigateTo: C } = e(), [w, T] = p(0), [E, D] = p(""), { data: O, isLoading: k, isError: A, refetch: j, isFetching: M } = u({
|
|
55
|
+
status: E || void 0,
|
|
56
|
+
pagination: {
|
|
57
|
+
limit: b,
|
|
58
|
+
offset: w * b
|
|
59
|
+
}
|
|
60
|
+
}), N = d(), P = f(() => O?.items ?? [], [O]), F = O?.pageInfo.hasMore ?? !1, I = f(() => {
|
|
61
|
+
let e = P.filter((e) => S.has(e.status)), t = P[0]?.currency ?? "USD";
|
|
62
|
+
return {
|
|
63
|
+
outstandingCount: e.length,
|
|
64
|
+
outstandingTotal: e.reduce((e, t) => e + t.total, 0),
|
|
65
|
+
currency: t
|
|
66
|
+
};
|
|
67
|
+
}, [P]);
|
|
68
|
+
return /* @__PURE__ */ v(n, {
|
|
69
|
+
title: "Invoices",
|
|
70
|
+
description: "Your billing ledger across every relationship",
|
|
71
|
+
children: [
|
|
72
|
+
/* @__PURE__ */ _(y, {
|
|
73
|
+
className: "mb-4",
|
|
74
|
+
children: "Every invoice from your recurring relationships in one ledger. Filter by status and settle issued or overdue invoices directly."
|
|
75
|
+
}),
|
|
76
|
+
!k && !A && P.length > 0 && /* @__PURE__ */ v("div", {
|
|
77
|
+
className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3",
|
|
78
|
+
children: [/* @__PURE__ */ _(r, {
|
|
79
|
+
featured: !0,
|
|
80
|
+
icon: /* @__PURE__ */ _(h, {}),
|
|
81
|
+
value: i(I.outstandingTotal, I.currency, { maximumFractionDigits: 0 }),
|
|
82
|
+
label: "Outstanding (this page)"
|
|
83
|
+
}), /* @__PURE__ */ _(r, {
|
|
84
|
+
icon: /* @__PURE__ */ _(m, {}),
|
|
85
|
+
value: I.outstandingCount,
|
|
86
|
+
label: "Open invoices (this page)"
|
|
87
|
+
})]
|
|
88
|
+
}),
|
|
89
|
+
/* @__PURE__ */ _("div", {
|
|
90
|
+
className: "mt-6 flex flex-wrap items-center gap-3",
|
|
91
|
+
children: /* @__PURE__ */ _("select", {
|
|
92
|
+
value: E,
|
|
93
|
+
onChange: (e) => {
|
|
94
|
+
D(e.target.value), T(0);
|
|
95
|
+
},
|
|
96
|
+
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)]",
|
|
97
|
+
children: x.map((e) => /* @__PURE__ */ _("option", {
|
|
98
|
+
value: e.value,
|
|
99
|
+
children: e.label
|
|
100
|
+
}, e.value))
|
|
101
|
+
})
|
|
102
|
+
}),
|
|
103
|
+
k ? /* @__PURE__ */ _(l, { label: "Loading invoices…" }) : A ? /* @__PURE__ */ _(c, {
|
|
104
|
+
message: "Could not load invoices.",
|
|
105
|
+
onRetry: () => void j()
|
|
106
|
+
}) : P.length === 0 ? /* @__PURE__ */ _("div", {
|
|
107
|
+
className: "mt-6",
|
|
108
|
+
children: /* @__PURE__ */ _(s, {
|
|
109
|
+
illustration: "empty-data",
|
|
110
|
+
icon: /* @__PURE__ */ _(h, { className: "h-12 w-12" }),
|
|
111
|
+
title: "No invoices",
|
|
112
|
+
description: E ? "No invoices match this status filter." : "Invoices from your relationships will appear here."
|
|
113
|
+
})
|
|
114
|
+
}) : /* @__PURE__ */ v(g, { children: [/* @__PURE__ */ v("div", {
|
|
115
|
+
className: "mt-6 overflow-hidden rounded-lg border border-border-subtle bg-bg-surface",
|
|
116
|
+
children: [/* @__PURE__ */ v("div", {
|
|
117
|
+
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",
|
|
118
|
+
children: [
|
|
119
|
+
/* @__PURE__ */ _("span", { children: "Invoice" }),
|
|
120
|
+
/* @__PURE__ */ _("span", { children: "Status" }),
|
|
121
|
+
/* @__PURE__ */ _("span", {
|
|
122
|
+
className: "text-right",
|
|
123
|
+
children: "Total"
|
|
124
|
+
}),
|
|
125
|
+
/* @__PURE__ */ _("span", {
|
|
126
|
+
className: "text-right",
|
|
127
|
+
children: "Due"
|
|
128
|
+
}),
|
|
129
|
+
/* @__PURE__ */ _("span", {
|
|
130
|
+
className: "text-right",
|
|
131
|
+
children: "Action"
|
|
132
|
+
})
|
|
133
|
+
]
|
|
134
|
+
}), /* @__PURE__ */ _("div", {
|
|
135
|
+
className: "divide-y divide-border-subtle",
|
|
136
|
+
children: P.map((e) => /* @__PURE__ */ v("div", {
|
|
137
|
+
className: "grid grid-cols-1 gap-2 px-4 py-3 sm:grid-cols-[1.5fr_1fr_1fr_1fr_auto] sm:items-center sm:gap-4",
|
|
138
|
+
children: [
|
|
139
|
+
/* @__PURE__ */ v("button", {
|
|
140
|
+
type: "button",
|
|
141
|
+
onClick: () => C(`/relationships/${e.relationshipId}`),
|
|
142
|
+
className: "min-w-0 text-left",
|
|
143
|
+
children: [/* @__PURE__ */ _("p", {
|
|
144
|
+
className: "truncate text-sm font-medium text-text-primary",
|
|
145
|
+
children: e.number || `Invoice ${e.id.slice(0, 8)}`
|
|
146
|
+
}), /* @__PURE__ */ _("p", {
|
|
147
|
+
className: "text-xs text-text-secondary",
|
|
148
|
+
children: e.issueDate ? `Issued ${a(e.issueDate)}` : "—"
|
|
149
|
+
})]
|
|
150
|
+
}),
|
|
151
|
+
/* @__PURE__ */ _("span", { children: /* @__PURE__ */ _(o, { status: e.status }) }),
|
|
152
|
+
/* @__PURE__ */ _("span", {
|
|
153
|
+
className: "text-sm font-semibold tabular-nums text-text-primary sm:text-right",
|
|
154
|
+
children: i(e.total, e.currency)
|
|
155
|
+
}),
|
|
156
|
+
/* @__PURE__ */ _("span", {
|
|
157
|
+
className: "text-xs text-text-secondary sm:text-right",
|
|
158
|
+
children: e.dueDate ? a(e.dueDate) : "—"
|
|
159
|
+
}),
|
|
160
|
+
/* @__PURE__ */ _("span", {
|
|
161
|
+
className: "sm:text-right",
|
|
162
|
+
children: S.has(e.status) ? /* @__PURE__ */ _("button", {
|
|
163
|
+
type: "button",
|
|
164
|
+
disabled: N.isPending,
|
|
165
|
+
onClick: () => N.mutate(e.id),
|
|
166
|
+
className: "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",
|
|
167
|
+
children: "Mark paid"
|
|
168
|
+
}) : e.paidAt ? /* @__PURE__ */ v("span", {
|
|
169
|
+
className: "text-xs text-text-secondary",
|
|
170
|
+
children: ["Paid ", a(e.paidAt)]
|
|
171
|
+
}) : /* @__PURE__ */ _("span", {
|
|
172
|
+
className: "text-xs text-text-secondary",
|
|
173
|
+
children: "—"
|
|
174
|
+
})
|
|
175
|
+
})
|
|
176
|
+
]
|
|
177
|
+
}, e.id))
|
|
178
|
+
})]
|
|
179
|
+
}), /* @__PURE__ */ v("div", {
|
|
180
|
+
className: "mt-4 flex items-center justify-between",
|
|
181
|
+
children: [/* @__PURE__ */ v("p", {
|
|
182
|
+
className: "text-xs text-text-secondary",
|
|
183
|
+
children: [
|
|
184
|
+
"Page ",
|
|
185
|
+
w + 1,
|
|
186
|
+
M ? " · refreshing…" : ""
|
|
187
|
+
]
|
|
188
|
+
}), /* @__PURE__ */ v("div", {
|
|
189
|
+
className: "flex gap-2",
|
|
190
|
+
children: [/* @__PURE__ */ _("button", {
|
|
191
|
+
type: "button",
|
|
192
|
+
disabled: w === 0,
|
|
193
|
+
onClick: () => T((e) => Math.max(0, e - 1)),
|
|
194
|
+
className: t("rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium", w === 0 ? "cursor-not-allowed text-text-secondary opacity-50" : "text-text-primary hover:bg-bg-sunken"),
|
|
195
|
+
children: "Previous"
|
|
196
|
+
}), /* @__PURE__ */ _("button", {
|
|
197
|
+
type: "button",
|
|
198
|
+
disabled: !F,
|
|
199
|
+
onClick: () => T((e) => e + 1),
|
|
200
|
+
className: t("rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium", F ? "text-text-primary hover:bg-bg-sunken" : "cursor-not-allowed text-text-secondary opacity-50"),
|
|
201
|
+
children: "Next"
|
|
202
|
+
})]
|
|
203
|
+
})]
|
|
204
|
+
})] })
|
|
205
|
+
]
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
//#endregion
|
|
209
|
+
export { C as InvoicesPage };
|
|
210
|
+
|
|
211
|
+
//# sourceMappingURL=InvoicesPage.js.map
|
|
@@ -0,0 +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.\n * Filter by status, mark issued/overdue invoices paid. Reads\n * `subListInvoices` (a SubInvoiceConnection); writes `subMarkInvoicePaid`.\n */\n\nimport { useMemo, useState } from 'react';\nimport { Receipt, FileText } from 'lucide-react';\nimport { PagePurpose } 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 { LoadingView, ErrorView } from '../components/StateViews';\nimport { useSubscriberBot } from '../context/SubscriberBotContext';\nimport { useInvoices, useMarkInvoicePaid } from '../hooks/useSubscriberBotApi';\nimport { cn } from '../utils/cn';\nimport { formatCurrency, formatDate } from '../utils/formatters';\nimport type { SubInvoiceStatus } from '../types';\n\nconst PAGE_SIZE = 25;\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 PAYABLE = new Set<SubInvoiceStatus>(['ISSUED', 'OVERDUE', 'FAILED']);\n\nexport function InvoicesPage() {\n const { navigateTo } = useSubscriberBot();\n const [page, setPage] = useState(0);\n const [status, setStatus] = useState<'' | SubInvoiceStatus>('');\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 return {\n outstandingCount: outstanding.length,\n outstandingTotal: outstanding.reduce((sum, i) => sum + i.total, 0),\n currency,\n };\n }, [items]);\n\n return (\n <PageLayout title=\"Invoices\" description=\"Your billing ledger across every relationship\">\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 sm:grid-cols-2 lg:grid-cols-3\">\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 (this page)\"\n />\n </div>\n )}\n\n <div className=\"mt-6 flex flex-wrap items-center gap-3\">\n <select\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.'\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 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=\"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 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 </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { useSubscriberBot as e } from "../context/SubscriberBotContext.js";
|
|
2
|
+
import { PageLayout as t } from "../components/PageLayout.js";
|
|
3
|
+
import { formatRelativeTime as n } from "../utils/formatters.js";
|
|
4
|
+
import { StatusBadge as r } from "../components/StatusBadge.js";
|
|
5
|
+
import { EmptyState as i } from "../components/EmptyState.js";
|
|
6
|
+
import { ErrorView as a, LoadingView as o } from "../components/StateViews.js";
|
|
7
|
+
import { useActivateWorkflow as s, useCreateWorkflow as c, useWorkflows as l } from "../hooks/useSubscriberBotApi.js";
|
|
8
|
+
import { useState as u } from "react";
|
|
9
|
+
import { Play as d, Plus as f, Workflow as p, X as m } from "lucide-react";
|
|
10
|
+
import { jsx as h, jsxs as g } from "react/jsx-runtime";
|
|
11
|
+
import { PagePurpose as _ } from "@burdenoff/fe-libs/ui";
|
|
12
|
+
//#region src/subscriberbot/pages/WorkflowsPage.tsx
|
|
13
|
+
var v = [
|
|
14
|
+
{
|
|
15
|
+
value: "",
|
|
16
|
+
label: "All statuses"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
value: "ACTIVE",
|
|
20
|
+
label: "Active"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
value: "DRAFT",
|
|
24
|
+
label: "Draft"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
value: "PAUSED",
|
|
28
|
+
label: "Paused"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
value: "ARCHIVED",
|
|
32
|
+
label: "Archived"
|
|
33
|
+
}
|
|
34
|
+
], y = "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)]", b = "mb-1 block text-xs font-medium text-text-secondary";
|
|
35
|
+
function x() {
|
|
36
|
+
let { navigate: x } = e(), [S, C] = u(""), [w, T] = u(!1), { data: E, isLoading: D, isError: O, refetch: k } = l(S || void 0), A = c(), j = s(), [M, N] = u(""), [P, F] = u(""), [I, L] = u(""), [R, z] = u(null), B = E ?? [], V = () => {
|
|
37
|
+
N(""), F(""), L(""), z(null);
|
|
38
|
+
};
|
|
39
|
+
return /* @__PURE__ */ g(t, {
|
|
40
|
+
title: "Workflows",
|
|
41
|
+
description: "Event-driven automation across your portfolio",
|
|
42
|
+
actions: /* @__PURE__ */ g("button", {
|
|
43
|
+
type: "button",
|
|
44
|
+
onClick: () => T(!0),
|
|
45
|
+
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",
|
|
46
|
+
children: [/* @__PURE__ */ h(f, { className: "h-4 w-4" }), " New workflow"]
|
|
47
|
+
}),
|
|
48
|
+
children: [
|
|
49
|
+
/* @__PURE__ */ h(_, {
|
|
50
|
+
className: "mb-4",
|
|
51
|
+
children: "Workflows react to domain events — a new invoice, a detected renewal, a cancelled relationship — and run multi-step automations. Draft a workflow, then activate it when you're ready."
|
|
52
|
+
}),
|
|
53
|
+
/* @__PURE__ */ h("div", {
|
|
54
|
+
className: "flex flex-wrap items-center gap-3",
|
|
55
|
+
children: /* @__PURE__ */ h("select", {
|
|
56
|
+
value: S,
|
|
57
|
+
onChange: (e) => C(e.target.value),
|
|
58
|
+
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)]",
|
|
59
|
+
children: v.map((e) => /* @__PURE__ */ h("option", {
|
|
60
|
+
value: e.value,
|
|
61
|
+
children: e.label
|
|
62
|
+
}, e.value))
|
|
63
|
+
})
|
|
64
|
+
}),
|
|
65
|
+
D ? /* @__PURE__ */ h(o, { label: "Loading workflows…" }) : O ? /* @__PURE__ */ h(a, {
|
|
66
|
+
message: "Could not load workflows.",
|
|
67
|
+
onRetry: () => void k()
|
|
68
|
+
}) : B.length === 0 ? /* @__PURE__ */ h("div", {
|
|
69
|
+
className: "mt-6",
|
|
70
|
+
children: /* @__PURE__ */ h(i, {
|
|
71
|
+
illustration: "empty-data",
|
|
72
|
+
icon: /* @__PURE__ */ h(p, { className: "h-12 w-12" }),
|
|
73
|
+
title: "No workflows yet",
|
|
74
|
+
description: "Create your first event-driven workflow to automate your recurring portfolio.",
|
|
75
|
+
action: /* @__PURE__ */ h("button", {
|
|
76
|
+
type: "button",
|
|
77
|
+
onClick: () => T(!0),
|
|
78
|
+
className: "rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90",
|
|
79
|
+
children: "New workflow"
|
|
80
|
+
})
|
|
81
|
+
})
|
|
82
|
+
}) : /* @__PURE__ */ h("div", {
|
|
83
|
+
className: "mt-6 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3",
|
|
84
|
+
children: B.map((e) => /* @__PURE__ */ g("div", {
|
|
85
|
+
className: "flex flex-col rounded-lg border border-border-subtle bg-bg-surface p-4",
|
|
86
|
+
children: [
|
|
87
|
+
/* @__PURE__ */ g("div", {
|
|
88
|
+
className: "flex items-start justify-between gap-2",
|
|
89
|
+
children: [/* @__PURE__ */ h("span", {
|
|
90
|
+
className: "flex size-9 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary",
|
|
91
|
+
children: /* @__PURE__ */ h(p, { className: "h-4 w-4" })
|
|
92
|
+
}), /* @__PURE__ */ h(r, { status: e.status })]
|
|
93
|
+
}),
|
|
94
|
+
/* @__PURE__ */ h("p", {
|
|
95
|
+
className: "mt-3 truncate text-sm font-semibold text-text-primary",
|
|
96
|
+
children: e.name
|
|
97
|
+
}),
|
|
98
|
+
/* @__PURE__ */ g("p", {
|
|
99
|
+
className: "truncate text-xs text-text-secondary",
|
|
100
|
+
children: ["on ", e.triggerEvent]
|
|
101
|
+
}),
|
|
102
|
+
e.description && /* @__PURE__ */ h("p", {
|
|
103
|
+
className: "mt-2 line-clamp-2 text-xs text-text-secondary",
|
|
104
|
+
children: e.description
|
|
105
|
+
}),
|
|
106
|
+
/* @__PURE__ */ g("p", {
|
|
107
|
+
className: "mt-2 text-xs text-text-secondary",
|
|
108
|
+
children: [
|
|
109
|
+
"Ran ",
|
|
110
|
+
e.runCount,
|
|
111
|
+
"×",
|
|
112
|
+
e.lastRunAt ? ` · last ${n(e.lastRunAt)}` : ""
|
|
113
|
+
]
|
|
114
|
+
}),
|
|
115
|
+
e.status !== "ACTIVE" && /* @__PURE__ */ g("button", {
|
|
116
|
+
type: "button",
|
|
117
|
+
disabled: j.isPending,
|
|
118
|
+
onClick: () => j.mutate(e.id),
|
|
119
|
+
className: "mt-3 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",
|
|
120
|
+
children: [/* @__PURE__ */ h(d, { className: "h-3.5 w-3.5" }), " Activate"]
|
|
121
|
+
})
|
|
122
|
+
]
|
|
123
|
+
}, e.id))
|
|
124
|
+
}),
|
|
125
|
+
w && /* @__PURE__ */ h("div", {
|
|
126
|
+
className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4",
|
|
127
|
+
children: /* @__PURE__ */ g("div", {
|
|
128
|
+
className: "w-full max-w-lg rounded-lg border border-border-subtle bg-bg-surface p-5",
|
|
129
|
+
children: [/* @__PURE__ */ g("div", {
|
|
130
|
+
className: "mb-4 flex items-center justify-between",
|
|
131
|
+
children: [/* @__PURE__ */ h("h3", {
|
|
132
|
+
className: "text-lg font-semibold text-text-primary",
|
|
133
|
+
children: "New workflow"
|
|
134
|
+
}), /* @__PURE__ */ h("button", {
|
|
135
|
+
type: "button",
|
|
136
|
+
onClick: () => {
|
|
137
|
+
T(!1), V();
|
|
138
|
+
},
|
|
139
|
+
className: "rounded-md p-1 text-text-secondary hover:text-text-primary",
|
|
140
|
+
children: /* @__PURE__ */ h(m, { className: "h-5 w-5" })
|
|
141
|
+
})]
|
|
142
|
+
}), /* @__PURE__ */ g("form", {
|
|
143
|
+
onSubmit: (e) => {
|
|
144
|
+
if (e.preventDefault(), z(null), !M.trim() || !P.trim()) {
|
|
145
|
+
z("Name and trigger event are required.");
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
let t = {
|
|
149
|
+
name: M.trim(),
|
|
150
|
+
triggerEvent: P.trim(),
|
|
151
|
+
...I.trim() ? { description: I.trim() } : {}
|
|
152
|
+
};
|
|
153
|
+
A.mutate(t, {
|
|
154
|
+
onSuccess: () => {
|
|
155
|
+
V(), T(!1);
|
|
156
|
+
},
|
|
157
|
+
onError: (e) => z(e instanceof Error ? e.message : "Could not create workflow.")
|
|
158
|
+
});
|
|
159
|
+
},
|
|
160
|
+
className: "space-y-4",
|
|
161
|
+
children: [
|
|
162
|
+
R && /* @__PURE__ */ h("div", {
|
|
163
|
+
className: "rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text",
|
|
164
|
+
children: R
|
|
165
|
+
}),
|
|
166
|
+
/* @__PURE__ */ g("div", { children: [/* @__PURE__ */ g("label", {
|
|
167
|
+
htmlFor: "wf-name",
|
|
168
|
+
className: b,
|
|
169
|
+
children: ["Name ", /* @__PURE__ */ h("span", {
|
|
170
|
+
className: "text-status-error-text",
|
|
171
|
+
children: "*"
|
|
172
|
+
})]
|
|
173
|
+
}), /* @__PURE__ */ h("input", {
|
|
174
|
+
id: "wf-name",
|
|
175
|
+
type: "text",
|
|
176
|
+
value: M,
|
|
177
|
+
onChange: (e) => N(e.target.value),
|
|
178
|
+
placeholder: "e.g. Invoice → notify finance",
|
|
179
|
+
className: y,
|
|
180
|
+
required: !0
|
|
181
|
+
})] }),
|
|
182
|
+
/* @__PURE__ */ g("div", { children: [/* @__PURE__ */ g("label", {
|
|
183
|
+
htmlFor: "wf-trigger",
|
|
184
|
+
className: b,
|
|
185
|
+
children: ["Trigger event ", /* @__PURE__ */ h("span", {
|
|
186
|
+
className: "text-status-error-text",
|
|
187
|
+
children: "*"
|
|
188
|
+
})]
|
|
189
|
+
}), /* @__PURE__ */ h("input", {
|
|
190
|
+
id: "wf-trigger",
|
|
191
|
+
type: "text",
|
|
192
|
+
value: P,
|
|
193
|
+
onChange: (e) => F(e.target.value),
|
|
194
|
+
placeholder: "e.g. InvoiceReceived, RenewalDetected",
|
|
195
|
+
className: y,
|
|
196
|
+
required: !0
|
|
197
|
+
})] }),
|
|
198
|
+
/* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
|
|
199
|
+
htmlFor: "wf-desc",
|
|
200
|
+
className: b,
|
|
201
|
+
children: "Description"
|
|
202
|
+
}), /* @__PURE__ */ h("textarea", {
|
|
203
|
+
id: "wf-desc",
|
|
204
|
+
value: I,
|
|
205
|
+
onChange: (e) => L(e.target.value),
|
|
206
|
+
rows: 3,
|
|
207
|
+
placeholder: "What should this workflow do when the event fires?",
|
|
208
|
+
className: y
|
|
209
|
+
})] }),
|
|
210
|
+
/* @__PURE__ */ g("div", {
|
|
211
|
+
className: "flex justify-end gap-3",
|
|
212
|
+
children: [/* @__PURE__ */ h("button", {
|
|
213
|
+
type: "button",
|
|
214
|
+
onClick: () => {
|
|
215
|
+
T(!1), V();
|
|
216
|
+
},
|
|
217
|
+
className: "rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken",
|
|
218
|
+
children: "Cancel"
|
|
219
|
+
}), /* @__PURE__ */ h("button", {
|
|
220
|
+
type: "submit",
|
|
221
|
+
disabled: A.isPending,
|
|
222
|
+
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",
|
|
223
|
+
children: A.isPending ? "Creating…" : "Create workflow"
|
|
224
|
+
})]
|
|
225
|
+
})
|
|
226
|
+
]
|
|
227
|
+
})]
|
|
228
|
+
})
|
|
229
|
+
})
|
|
230
|
+
]
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
//#endregion
|
|
234
|
+
export { x as WorkflowsPage };
|
|
235
|
+
|
|
236
|
+
//# sourceMappingURL=WorkflowsPage.js.map
|
|
@@ -0,0 +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\nimport { useState } from 'react';\nimport { Plus, Workflow as WorkflowIcon, Play, X } from 'lucide-react';\nimport { PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../components/PageLayout';\nimport { StatusBadge } from '../components/StatusBadge';\nimport { EmptyState } from '../components/EmptyState';\nimport { LoadingView, ErrorView } from '../components/StateViews';\nimport { useSubscriberBot } from '../context/SubscriberBotContext';\nimport {\n useWorkflows,\n useCreateWorkflow,\n useActivateWorkflow,\n} from '../hooks/useSubscriberBotApi';\nimport { formatRelativeTime } from '../utils/formatters';\nimport type { SubCreateWorkflowInput, 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\nexport function WorkflowsPage() {\n const { navigate } = useSubscriberBot();\n const [statusFilter, setStatusFilter] = useState<'' | SubWorkflowStatus>('');\n const [showCreate, setShowCreate] = useState(false);\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 // `navigate` is only consumed via the back-compat context; reference it so\n // its presence keeps integrated routing wired (no-op otherwise).\n void navigate;\n\n const resetForm = () => {\n setName('');\n setTriggerEvent('');\n setDescription('');\n setFormError(null);\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 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 <div className=\"mt-6 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {workflows.map((wf) => (\n <div key={wf.id} className=\"flex flex-col rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"flex size-9 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary\">\n <WorkflowIcon className=\"h-4 w-4\" />\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=\"truncate text-xs text-text-secondary\">on {wf.triggerEvent}</p>\n {wf.description && (\n <p className=\"mt-2 line-clamp-2 text-xs text-text-secondary\">{wf.description}</p>\n )}\n <p className=\"mt-2 text-xs text-text-secondary\">\n 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={() => activate.mutate(wf.id)}\n className=\"mt-3 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 </div>\n ))}\n </div>\n )}\n\n {/* Create modal */}\n {showCreate && (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4\">\n <div className=\"w-full max-w-lg rounded-lg border border-border-subtle bg-bg-surface p-5\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h3 className=\"text-lg font-semibold text-text-primary\">New workflow</h3>\n <button\n type=\"button\"\n onClick={() => {\n setShowCreate(false);\n resetForm();\n }}\n className=\"rounded-md p-1 text-text-secondary hover:text-text-primary\"\n >\n <X className=\"h-5 w-5\" />\n </button>\n </div>\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={() => {\n setShowCreate(false);\n resetForm();\n }}\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 </div>\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|