@burdenoff/microfe-admin 2026.624.1 → 2026.626.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/admin/components/AccessDenied.js +2 -2
- package/dist/admin/components/AccessDenied.js.map +1 -1
- package/dist/admin/components/ActivityFeed.js +4 -4
- package/dist/admin/components/ActivityFeed.js.map +1 -1
- package/dist/admin/components/Charts.js.map +1 -1
- package/dist/admin/components/ConfirmDialog.js +1 -1
- package/dist/admin/components/ConfirmDialog.js.map +1 -1
- package/dist/admin/components/DataTable.js +5 -5
- package/dist/admin/components/DataTable.js.map +1 -1
- package/dist/admin/components/FormSheet.js +1 -1
- package/dist/admin/components/FormSheet.js.map +1 -1
- package/dist/admin/components/MetricCard.js +40 -37
- package/dist/admin/components/MetricCard.js.map +1 -1
- package/dist/admin/components/PageLayout.js +28 -22
- package/dist/admin/components/PageLayout.js.map +1 -1
- package/dist/admin/components/TenantSsoPanel.js +1 -1
- package/dist/admin/components/TenantSsoPanel.js.map +1 -1
- package/dist/admin/components/Toast.js +11 -11
- package/dist/admin/components/Toast.js.map +1 -1
- package/dist/admin/components/forms/SubscriptionForm.js +2 -2
- package/dist/admin/components/forms/SubscriptionForm.js.map +1 -1
- package/dist/admin/components/forms/TenantForm.js +1 -1
- package/dist/admin/components/forms/UserForm.js +1 -1
- package/dist/admin/components/ui.js +48 -41
- package/dist/admin/components/ui.js.map +1 -1
- package/dist/admin/constants/index.js +33 -24
- package/dist/admin/constants/index.js.map +1 -1
- package/dist/admin/pages/AuditLogsPage.js +94 -77
- package/dist/admin/pages/AuditLogsPage.js.map +1 -1
- package/dist/admin/pages/BillingPage.js +70 -18
- package/dist/admin/pages/BillingPage.js.map +1 -1
- package/dist/admin/pages/CommunicationsPage.js +98 -90
- package/dist/admin/pages/CommunicationsPage.js.map +1 -1
- package/dist/admin/pages/ContentPagesPage.js +276 -259
- package/dist/admin/pages/ContentPagesPage.js.map +1 -1
- package/dist/admin/pages/DashboardPage.js +114 -82
- package/dist/admin/pages/DashboardPage.js.map +1 -1
- package/dist/admin/pages/FeatureFlagsPage.js +94 -74
- package/dist/admin/pages/FeatureFlagsPage.js.map +1 -1
- package/dist/admin/pages/OrganizationsListPage.js +106 -86
- package/dist/admin/pages/OrganizationsListPage.js.map +1 -1
- package/dist/admin/pages/ProductsPage.js +139 -128
- package/dist/admin/pages/ProductsPage.js.map +1 -1
- package/dist/admin/pages/ReferralsPage.js +74 -52
- package/dist/admin/pages/ReferralsPage.js.map +1 -1
- package/dist/admin/pages/RolesPermissionsPage.js +137 -116
- package/dist/admin/pages/RolesPermissionsPage.js.map +1 -1
- package/dist/admin/pages/SREPage.js +38 -32
- package/dist/admin/pages/SREPage.js.map +1 -1
- package/dist/admin/pages/SandboxSettingsPage.js +361 -322
- package/dist/admin/pages/SandboxSettingsPage.js.map +1 -1
- package/dist/admin/pages/SettingsPage.js +145 -130
- package/dist/admin/pages/SettingsPage.js.map +1 -1
- package/dist/admin/pages/SyncFailuresPage.js +123 -118
- package/dist/admin/pages/SyncFailuresPage.js.map +1 -1
- package/dist/admin/pages/TenantDetailPage.js +261 -218
- package/dist/admin/pages/TenantDetailPage.js.map +1 -1
- package/dist/admin/pages/TenantFormRoutePage.js +65 -61
- package/dist/admin/pages/TenantFormRoutePage.js.map +1 -1
- package/dist/admin/pages/TenantsListPage.js +88 -71
- package/dist/admin/pages/TenantsListPage.js.map +1 -1
- package/dist/admin/pages/UsersListPage.js +129 -112
- package/dist/admin/pages/UsersListPage.js.map +1 -1
- package/dist/admin/pages/WaitlistPage.js +80 -63
- package/dist/admin/pages/WaitlistPage.js.map +1 -1
- package/dist/admin/pages/WorkflowsPage.js +108 -100
- package/dist/admin/pages/WorkflowsPage.js.map +1 -1
- package/dist/admin/pages/WorkspacesListPage.js +62 -49
- package/dist/admin/pages/WorkspacesListPage.js.map +1 -1
- package/dist/node_modules/@hookform/resolvers/dist/resolvers.js +2 -2
- package/dist/node_modules/@hookform/resolvers/dist/resolvers.js.map +1 -1
- package/dist/node_modules/@hookform/resolvers/zod/dist/zod.js +35 -26
- package/dist/node_modules/@hookform/resolvers/zod/dist/zod.js.map +1 -1
- package/dist/node_modules/react-hook-form/dist/index.esm.js +609 -516
- package/dist/node_modules/react-hook-form/dist/index.esm.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Badge as e,
|
|
1
|
+
import { Badge as e, EmptyState as t } from "../components/ui.js";
|
|
2
2
|
import { PageLayout as n } from "../components/PageLayout.js";
|
|
3
3
|
import { formatCurrency as r, formatDateTime as i } from "../utils/formatters.js";
|
|
4
4
|
import { MetricCard as a, MetricsGrid as o } from "../components/MetricCard.js";
|
|
@@ -7,28 +7,60 @@ import "../components/index.js";
|
|
|
7
7
|
import { useListAllPlansQuery as c, useListAllQuotasQuery as l, useListRefundRecordsQuery as u } from "../../generated/global-operations.js";
|
|
8
8
|
import { ArrowUpRight as d, CreditCard as f, Download as p, Plus as m, Receipt as h, Shield as g } from "lucide-react";
|
|
9
9
|
import { jsx as _, jsxs as v } from "react/jsx-runtime";
|
|
10
|
+
import { CTAOverflowMenu as y, NextSteps as b, PagePurpose as x } from "@burdenoff/fe-libs/ui";
|
|
10
11
|
//#region src/admin/pages/BillingPage.tsx
|
|
11
|
-
function
|
|
12
|
-
let { data:
|
|
12
|
+
function S() {
|
|
13
|
+
let { data: S, loading: C } = c(), { data: w, loading: T } = l(), { data: E, loading: D } = u({ variables: {
|
|
13
14
|
page: 1,
|
|
14
15
|
pageSize: 10
|
|
15
|
-
} }),
|
|
16
|
+
} }), O = S?.getAllPlans || [], k = w?.getAllQuotas || [], A = E?.getRefundRecords?.records || [];
|
|
16
17
|
return /* @__PURE__ */ _(n, {
|
|
17
18
|
title: "Billing & Subscriptions",
|
|
19
|
+
gradientWord: "Subscriptions",
|
|
18
20
|
description: "Manage platform-wide plans, quotas, and refunds",
|
|
19
|
-
actions: /* @__PURE__ */
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
21
|
+
actions: /* @__PURE__ */ _(y, {
|
|
22
|
+
primary: {
|
|
23
|
+
label: "Create Plan",
|
|
24
|
+
icon: /* @__PURE__ */ _(m, { className: "mr-2 h-4 w-4" }),
|
|
25
|
+
onSelect: () => {}
|
|
26
|
+
},
|
|
27
|
+
actions: [{
|
|
28
|
+
label: "Export Audit",
|
|
29
|
+
icon: /* @__PURE__ */ _(p, { className: "h-4 w-4" }),
|
|
30
|
+
onSelect: () => {}
|
|
31
|
+
}]
|
|
25
32
|
}),
|
|
26
33
|
children: /* @__PURE__ */ v("div", {
|
|
27
34
|
className: "space-y-8",
|
|
28
35
|
children: [
|
|
36
|
+
/* @__PURE__ */ _(x, { children: "The money side of the platform in one place. Define the plans customers can buy, set the resource quotas each plan grants, and review refunds — so you can launch a new pricing tier or resolve a billing dispute without leaving the console." }),
|
|
37
|
+
/* @__PURE__ */ _(b, {
|
|
38
|
+
storageKey: "admin-billing",
|
|
39
|
+
steps: [
|
|
40
|
+
{
|
|
41
|
+
id: "create-plan",
|
|
42
|
+
label: "Create a plan",
|
|
43
|
+
description: "Define a new pricing tier customers can subscribe to.",
|
|
44
|
+
onClick: () => {}
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: "review-refunds",
|
|
48
|
+
label: "Review pending refunds",
|
|
49
|
+
description: "Approve or investigate refunds awaiting a decision.",
|
|
50
|
+
href: "#refunds"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: "check-quotas",
|
|
54
|
+
label: "Check resource quotas",
|
|
55
|
+
description: "Make sure each plan grants the limits you intend.",
|
|
56
|
+
href: "#quotas"
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}),
|
|
29
60
|
/* @__PURE__ */ v(o, { children: [
|
|
30
61
|
/* @__PURE__ */ _(a, {
|
|
31
62
|
title: "Total Revenue (30d)",
|
|
63
|
+
hero: !0,
|
|
32
64
|
value: "$124,500",
|
|
33
65
|
icon: /* @__PURE__ */ _(f, { className: "h-4 w-4" }),
|
|
34
66
|
trend: "up"
|
|
@@ -40,7 +72,7 @@ function y() {
|
|
|
40
72
|
}),
|
|
41
73
|
/* @__PURE__ */ _(a, {
|
|
42
74
|
title: "Pending Refunds",
|
|
43
|
-
value:
|
|
75
|
+
value: E?.getRefundRecords?.totalCount ?? 0,
|
|
44
76
|
icon: /* @__PURE__ */ _(d, { className: "h-4 w-4" }),
|
|
45
77
|
priority: "high"
|
|
46
78
|
}),
|
|
@@ -85,11 +117,18 @@ function y() {
|
|
|
85
117
|
})
|
|
86
118
|
}
|
|
87
119
|
],
|
|
88
|
-
data:
|
|
120
|
+
data: O,
|
|
89
121
|
keyExtractor: (e) => e.id,
|
|
90
|
-
loading:
|
|
122
|
+
loading: C,
|
|
123
|
+
emptyState: /* @__PURE__ */ _(t, {
|
|
124
|
+
illustration: "empty-data",
|
|
125
|
+
icon: /* @__PURE__ */ _(f, { className: "h-8 w-8" }),
|
|
126
|
+
title: "No plans yet",
|
|
127
|
+
description: "Create your first pricing plan to let customers subscribe."
|
|
128
|
+
})
|
|
91
129
|
})]
|
|
92
130
|
}), /* @__PURE__ */ v("div", {
|
|
131
|
+
id: "quotas",
|
|
93
132
|
className: "space-y-4",
|
|
94
133
|
children: [/* @__PURE__ */ _("h2", {
|
|
95
134
|
className: "text-lg font-semibold",
|
|
@@ -113,13 +152,20 @@ function y() {
|
|
|
113
152
|
})
|
|
114
153
|
}
|
|
115
154
|
],
|
|
116
|
-
data:
|
|
155
|
+
data: k,
|
|
117
156
|
keyExtractor: (e) => e.id,
|
|
118
|
-
loading:
|
|
157
|
+
loading: T,
|
|
158
|
+
emptyState: /* @__PURE__ */ _(t, {
|
|
159
|
+
illustration: "empty-data",
|
|
160
|
+
icon: /* @__PURE__ */ _(g, { className: "h-8 w-8" }),
|
|
161
|
+
title: "No quotas defined",
|
|
162
|
+
description: "Resource quotas you create will appear here."
|
|
163
|
+
})
|
|
119
164
|
})]
|
|
120
165
|
})]
|
|
121
166
|
}),
|
|
122
167
|
/* @__PURE__ */ v("div", {
|
|
168
|
+
id: "refunds",
|
|
123
169
|
className: "space-y-4",
|
|
124
170
|
children: [/* @__PURE__ */ _("h2", {
|
|
125
171
|
className: "text-lg font-semibold",
|
|
@@ -149,9 +195,15 @@ function y() {
|
|
|
149
195
|
render: (e) => i(e.createdAt)
|
|
150
196
|
}
|
|
151
197
|
],
|
|
152
|
-
data:
|
|
198
|
+
data: A,
|
|
153
199
|
keyExtractor: (e) => e.id,
|
|
154
|
-
loading:
|
|
200
|
+
loading: D,
|
|
201
|
+
emptyState: /* @__PURE__ */ _(t, {
|
|
202
|
+
illustration: "empty-generic",
|
|
203
|
+
icon: /* @__PURE__ */ _(h, { className: "h-8 w-8" }),
|
|
204
|
+
title: "No refunds recorded",
|
|
205
|
+
description: "Refund activity will show up here as it happens."
|
|
206
|
+
})
|
|
155
207
|
})]
|
|
156
208
|
})
|
|
157
209
|
]
|
|
@@ -159,6 +211,6 @@ function y() {
|
|
|
159
211
|
});
|
|
160
212
|
}
|
|
161
213
|
//#endregion
|
|
162
|
-
export {
|
|
214
|
+
export { S as BillingPage };
|
|
163
215
|
|
|
164
216
|
//# sourceMappingURL=BillingPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BillingPage.js","names":[],"sources":["../../../src/admin/pages/BillingPage.tsx"],"sourcesContent":["import { CreditCard, Plus, ArrowUpRight, Shield, Receipt, Download } from 'lucide-react';\nimport { PageLayout, Button, DataTable, Badge, MetricCard, MetricsGrid } from '../components';\nimport {\n useListAllPlansQuery,\n useListAllQuotasQuery,\n useListRefundRecordsQuery,\n} from '../../generated/global-operations';\nimport { formatCurrency, formatDateTime } from '../utils/formatters';\n\nexport function BillingPage() {\n const { data: plansData, loading: plansLoading } = useListAllPlansQuery();\n const { data: quotasData, loading: quotasLoading } = useListAllQuotasQuery();\n const { data: refundsData, loading: refundsLoading } = useListRefundRecordsQuery({\n variables: { page: 1, pageSize: 10 },\n });\n\n const plans = plansData?.getAllPlans || [];\n const quotas = quotasData?.getAllQuotas || [];\n const refunds = refundsData?.getRefundRecords?.records || [];\n\n const planColumns = [\n {\n key: 'name',\n header: 'Plan Name',\n render: (plan: any) => <span className=\"font-medium\">{plan.name}</span>,\n },\n {\n key: 'price',\n header: 'Price',\n render: (plan: any) => formatCurrency(plan.price, plan.currency),\n },\n {\n key: 'duration',\n header: 'Interval',\n },\n {\n key: 'isActive',\n header: 'Status',\n render: (plan: any) => (\n <Badge variant={plan.isActive ? 'success' : 'secondary'}>\n {plan.isActive ? 'Active' : 'Inactive'}\n </Badge>\n ),\n },\n ];\n\n const quotaColumns = [\n {\n key: 'name',\n header: 'Quota Name',\n },\n {\n key: 'quotaType',\n header: 'Metric Type',\n },\n {\n key: 'isActive',\n header: 'Status',\n render: (quota: any) => (\n <Badge variant={quota.isActive ? 'success' : 'secondary'}>\n {quota.isActive ? 'Active' : 'Inactive'}\n </Badge>\n ),\n },\n ];\n\n const refundColumns = [\n {\n key: 'amount',\n header: 'Amount',\n render: (refund: any) => formatCurrency(refund.amount, refund.currency),\n },\n {\n key: 'status',\n header: 'Status',\n render: (refund: any) => (\n <Badge variant={refund.status === 'COMPLETED' ? 'success' : 'warning'}>\n {refund.status}\n </Badge>\n ),\n },\n {\n key: 'reason',\n header: 'Reason',\n },\n {\n key: 'createdAt',\n header: 'Date',\n render: (refund: any) => formatDateTime(refund.createdAt),\n },\n ];\n\n return (\n <PageLayout\n title=\"Billing & Subscriptions\"\n description=\"Manage platform-wide plans, quotas, and refunds\"\n actions={\n <div className=\"flex gap-2\">\n <Button variant=\"outline\">\n <Download className=\"mr-2 h-4 w-4\" />\n Export Audit\n </Button>\n <Button>\n <Plus className=\"mr-2 h-4 w-4\" />\n Create Plan\n </Button>\n </div>\n }\n >\n <div className=\"space-y-8\">\n <MetricsGrid>\n <MetricCard\n title=\"Total Revenue (30d)\"\n value=\"$124,500\"\n icon={<CreditCard className=\"h-4 w-4\" />}\n trend=\"up\"\n />\n <MetricCard\n title=\"Active Subscriptions\"\n value=\"1,248\"\n icon={<Shield className=\"h-4 w-4\" />}\n />\n <MetricCard\n title=\"Pending Refunds\"\n value={refundsData?.getRefundRecords?.totalCount ?? 0}\n icon={<ArrowUpRight className=\"h-4 w-4\" />}\n priority=\"high\"\n />\n <MetricCard title=\"Upcoming Invoices\" value=\"42\" icon={<Receipt className=\"h-4 w-4\" />} />\n </MetricsGrid>\n\n <div className=\"grid gap-8 lg:grid-cols-2\">\n <div className=\"space-y-4\">\n <h2 className=\"text-lg font-semibold\">Platform Plans</h2>\n <DataTable\n columns={planColumns}\n data={plans}\n keyExtractor={(p) => p.id}\n loading={plansLoading}\n />\n </div>\n <div className=\"space-y-4\">\n <h2 className=\"text-lg font-semibold\">Resource Quotas</h2>\n <DataTable\n columns={quotaColumns}\n data={quotas}\n keyExtractor={(q) => q.id}\n loading={quotasLoading}\n />\n </div>\n </div>\n\n <div className=\"space-y-4\">\n <h2 className=\"text-lg font-semibold\">Recent Refund Records</h2>\n <DataTable\n columns={refundColumns}\n data={refunds}\n keyExtractor={(r) => r.id}\n loading={refundsLoading}\n />\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BillingPage.js","names":[],"sources":["../../../src/admin/pages/BillingPage.tsx"],"sourcesContent":["import { CreditCard, Plus, ArrowUpRight, Shield, Receipt, Download } from 'lucide-react';\nimport { CTAOverflowMenu, PagePurpose, NextSteps } from '@burdenoff/fe-libs/ui';\nimport { PageLayout, DataTable, Badge, MetricCard, MetricsGrid, EmptyState } from '../components';\nimport {\n useListAllPlansQuery,\n useListAllQuotasQuery,\n useListRefundRecordsQuery,\n} from '../../generated/global-operations';\nimport { formatCurrency, formatDateTime } from '../utils/formatters';\n\nexport function BillingPage() {\n const { data: plansData, loading: plansLoading } = useListAllPlansQuery();\n const { data: quotasData, loading: quotasLoading } = useListAllQuotasQuery();\n const { data: refundsData, loading: refundsLoading } = useListRefundRecordsQuery({\n variables: { page: 1, pageSize: 10 },\n });\n\n const plans = plansData?.getAllPlans || [];\n const quotas = quotasData?.getAllQuotas || [];\n const refunds = refundsData?.getRefundRecords?.records || [];\n\n const planColumns = [\n {\n key: 'name',\n header: 'Plan Name',\n render: (plan: any) => <span className=\"font-medium\">{plan.name}</span>,\n },\n {\n key: 'price',\n header: 'Price',\n render: (plan: any) => formatCurrency(plan.price, plan.currency),\n },\n {\n key: 'duration',\n header: 'Interval',\n },\n {\n key: 'isActive',\n header: 'Status',\n render: (plan: any) => (\n <Badge variant={plan.isActive ? 'success' : 'secondary'}>\n {plan.isActive ? 'Active' : 'Inactive'}\n </Badge>\n ),\n },\n ];\n\n const quotaColumns = [\n {\n key: 'name',\n header: 'Quota Name',\n },\n {\n key: 'quotaType',\n header: 'Metric Type',\n },\n {\n key: 'isActive',\n header: 'Status',\n render: (quota: any) => (\n <Badge variant={quota.isActive ? 'success' : 'secondary'}>\n {quota.isActive ? 'Active' : 'Inactive'}\n </Badge>\n ),\n },\n ];\n\n const refundColumns = [\n {\n key: 'amount',\n header: 'Amount',\n render: (refund: any) => formatCurrency(refund.amount, refund.currency),\n },\n {\n key: 'status',\n header: 'Status',\n render: (refund: any) => (\n <Badge variant={refund.status === 'COMPLETED' ? 'success' : 'warning'}>\n {refund.status}\n </Badge>\n ),\n },\n {\n key: 'reason',\n header: 'Reason',\n },\n {\n key: 'createdAt',\n header: 'Date',\n render: (refund: any) => formatDateTime(refund.createdAt),\n },\n ];\n\n return (\n <PageLayout\n title=\"Billing & Subscriptions\"\n gradientWord=\"Subscriptions\"\n description=\"Manage platform-wide plans, quotas, and refunds\"\n actions={\n <CTAOverflowMenu\n primary={{\n label: 'Create Plan',\n icon: <Plus className=\"mr-2 h-4 w-4\" />,\n onSelect: () => {},\n }}\n actions={[\n {\n label: 'Export Audit',\n icon: <Download className=\"h-4 w-4\" />,\n onSelect: () => {},\n },\n ]}\n />\n }\n >\n <div className=\"space-y-8\">\n <PagePurpose>\n The money side of the platform in one place. Define the plans customers can buy, set the\n resource quotas each plan grants, and review refunds — so you can launch a new pricing\n tier or resolve a billing dispute without leaving the console.\n </PagePurpose>\n\n <NextSteps\n storageKey=\"admin-billing\"\n steps={[\n {\n id: 'create-plan',\n label: 'Create a plan',\n description: 'Define a new pricing tier customers can subscribe to.',\n onClick: () => {},\n },\n {\n id: 'review-refunds',\n label: 'Review pending refunds',\n description: 'Approve or investigate refunds awaiting a decision.',\n href: '#refunds',\n },\n {\n id: 'check-quotas',\n label: 'Check resource quotas',\n description: 'Make sure each plan grants the limits you intend.',\n href: '#quotas',\n },\n ]}\n />\n\n <MetricsGrid>\n <MetricCard\n title=\"Total Revenue (30d)\"\n hero\n value=\"$124,500\"\n icon={<CreditCard className=\"h-4 w-4\" />}\n trend=\"up\"\n />\n <MetricCard\n title=\"Active Subscriptions\"\n value=\"1,248\"\n icon={<Shield className=\"h-4 w-4\" />}\n />\n <MetricCard\n title=\"Pending Refunds\"\n value={refundsData?.getRefundRecords?.totalCount ?? 0}\n icon={<ArrowUpRight className=\"h-4 w-4\" />}\n priority=\"high\"\n />\n <MetricCard title=\"Upcoming Invoices\" value=\"42\" icon={<Receipt className=\"h-4 w-4\" />} />\n </MetricsGrid>\n\n <div className=\"grid gap-8 lg:grid-cols-2\">\n <div className=\"space-y-4\">\n <h2 className=\"text-lg font-semibold\">Platform Plans</h2>\n <DataTable\n columns={planColumns}\n data={plans}\n keyExtractor={(p) => p.id}\n loading={plansLoading}\n emptyState={\n <EmptyState\n illustration=\"empty-data\"\n icon={<CreditCard className=\"h-8 w-8\" />}\n title=\"No plans yet\"\n description=\"Create your first pricing plan to let customers subscribe.\"\n />\n }\n />\n </div>\n <div id=\"quotas\" className=\"space-y-4\">\n <h2 className=\"text-lg font-semibold\">Resource Quotas</h2>\n <DataTable\n columns={quotaColumns}\n data={quotas}\n keyExtractor={(q) => q.id}\n loading={quotasLoading}\n emptyState={\n <EmptyState\n illustration=\"empty-data\"\n icon={<Shield className=\"h-8 w-8\" />}\n title=\"No quotas defined\"\n description=\"Resource quotas you create will appear here.\"\n />\n }\n />\n </div>\n </div>\n\n <div id=\"refunds\" className=\"space-y-4\">\n <h2 className=\"text-lg font-semibold\">Recent Refund Records</h2>\n <DataTable\n columns={refundColumns}\n data={refunds}\n keyExtractor={(r) => r.id}\n loading={refundsLoading}\n emptyState={\n <EmptyState\n illustration=\"empty-generic\"\n icon={<Receipt className=\"h-8 w-8\" />}\n title=\"No refunds recorded\"\n description=\"Refund activity will show up here as it happens.\"\n />\n }\n />\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -7,112 +7,120 @@ import "../components/index.js";
|
|
|
7
7
|
import { useGetNewsletterStatsQuery as l, useListNewslettersQuery as u, useSendNewsletterMutation as d } from "../../generated/global-operations.js";
|
|
8
8
|
import { Eye as f, Mail as p, MousePointer2 as m, Plus as h, Send as g, Users as _ } from "lucide-react";
|
|
9
9
|
import { jsx as v, jsxs as y } from "react/jsx-runtime";
|
|
10
|
+
import { PagePurpose as b } from "@burdenoff/fe-libs/ui";
|
|
10
11
|
//#region src/admin/pages/CommunicationsPage.tsx
|
|
11
|
-
function
|
|
12
|
-
let { data:
|
|
12
|
+
function x() {
|
|
13
|
+
let { data: x, loading: S } = l(), { data: C, loading: w, refetch: T } = u({ variables: { limit: 50 } }), [E] = d(), D = x?.newsletterStats, O = C?.newsletters || [], k = async (e) => {
|
|
13
14
|
if (confirm("Are you sure you want to send this newsletter to all subscribers?")) try {
|
|
14
|
-
await
|
|
15
|
+
await E({ variables: { id: e } }), T();
|
|
15
16
|
} catch (e) {
|
|
16
17
|
console.error("Failed to send newsletter:", e);
|
|
17
18
|
}
|
|
18
19
|
};
|
|
19
20
|
return /* @__PURE__ */ v(r, {
|
|
20
21
|
title: "Communications",
|
|
22
|
+
gradientWord: "Communications",
|
|
21
23
|
description: "Manage platform newsletters and system notifications",
|
|
22
24
|
actions: /* @__PURE__ */ y(t, { children: [/* @__PURE__ */ v(h, { className: "mr-2 h-4 w-4" }), "Create Newsletter"] }),
|
|
23
25
|
children: /* @__PURE__ */ y("div", {
|
|
24
26
|
className: "space-y-8",
|
|
25
|
-
children: [
|
|
26
|
-
/* @__PURE__ */ v(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
children: [/* @__PURE__ */ v("h2", {
|
|
53
|
-
className: "text-lg font-semibold",
|
|
54
|
-
children: "Newsletters"
|
|
55
|
-
}), /* @__PURE__ */ v(c, {
|
|
56
|
-
columns: [
|
|
57
|
-
{
|
|
58
|
-
key: "title",
|
|
59
|
-
header: "Title",
|
|
60
|
-
render: (e) => /* @__PURE__ */ y("div", {
|
|
61
|
-
className: "flex flex-col",
|
|
62
|
-
children: [/* @__PURE__ */ v("span", {
|
|
63
|
-
className: "font-medium text-text-primary",
|
|
64
|
-
children: e.title
|
|
65
|
-
}), /* @__PURE__ */ v("span", {
|
|
66
|
-
className: "text-xs text-text-secondary",
|
|
67
|
-
children: e.subject
|
|
68
|
-
})]
|
|
69
|
-
})
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
key: "status",
|
|
73
|
-
header: "Status",
|
|
74
|
-
render: (t) => /* @__PURE__ */ v(e, {
|
|
75
|
-
variant: {
|
|
76
|
-
SENT: "success",
|
|
77
|
-
SCHEDULED: "warning",
|
|
78
|
-
DRAFT: "secondary"
|
|
79
|
-
}[t.status] || "outline",
|
|
80
|
-
children: t.status
|
|
81
|
-
})
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
key: "sentAt",
|
|
85
|
-
header: "Sent Date",
|
|
86
|
-
render: (e) => e.sentAt ? i(e.sentAt) : "-"
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
key: "actions",
|
|
90
|
-
header: "",
|
|
91
|
-
align: "right",
|
|
92
|
-
render: (e) => /* @__PURE__ */ v("div", {
|
|
93
|
-
className: "flex justify-end gap-2",
|
|
94
|
-
children: e.status === "DRAFT" && /* @__PURE__ */ y(t, {
|
|
95
|
-
size: "sm",
|
|
96
|
-
onClick: () => O(e.id),
|
|
97
|
-
children: [/* @__PURE__ */ v(g, { className: "mr-2 h-3 w-3" }), "Send Now"]
|
|
98
|
-
})
|
|
99
|
-
})
|
|
100
|
-
}
|
|
101
|
-
],
|
|
102
|
-
data: D,
|
|
103
|
-
keyExtractor: (e) => e.id,
|
|
104
|
-
loading: C,
|
|
105
|
-
emptyState: /* @__PURE__ */ v(n, {
|
|
106
|
-
icon: /* @__PURE__ */ v(p, { className: "h-8 w-8" }),
|
|
107
|
-
title: "No newsletters found",
|
|
108
|
-
description: "Create your first newsletter to communicate with users"
|
|
27
|
+
children: [
|
|
28
|
+
/* @__PURE__ */ v(b, { children: "Reach your users directly. Draft newsletters, send them to your subscriber base, and track how many people opened and clicked — use it to announce launches, share product updates, or run a re-engagement campaign." }),
|
|
29
|
+
/* @__PURE__ */ y(s, { children: [
|
|
30
|
+
/* @__PURE__ */ v(o, {
|
|
31
|
+
title: "Total Subscribers",
|
|
32
|
+
hero: !0,
|
|
33
|
+
value: D?.totalSubscribers ?? 0,
|
|
34
|
+
loading: S,
|
|
35
|
+
icon: /* @__PURE__ */ v(_, { className: "h-4 w-4" })
|
|
36
|
+
}),
|
|
37
|
+
/* @__PURE__ */ v(o, {
|
|
38
|
+
title: "Newsletters Sent",
|
|
39
|
+
value: D?.totalSent ?? 0,
|
|
40
|
+
loading: S,
|
|
41
|
+
icon: /* @__PURE__ */ v(g, { className: "h-4 w-4" })
|
|
42
|
+
}),
|
|
43
|
+
/* @__PURE__ */ v(o, {
|
|
44
|
+
title: "Avg. Open Rate",
|
|
45
|
+
value: a(D?.averageOpenRate ?? 0),
|
|
46
|
+
loading: S,
|
|
47
|
+
icon: /* @__PURE__ */ v(f, { className: "h-4 w-4" })
|
|
48
|
+
}),
|
|
49
|
+
/* @__PURE__ */ v(o, {
|
|
50
|
+
title: "Avg. Click Rate",
|
|
51
|
+
value: a(D?.averageClickRate ?? 0),
|
|
52
|
+
loading: S,
|
|
53
|
+
icon: /* @__PURE__ */ v(m, { className: "h-4 w-4" })
|
|
109
54
|
})
|
|
110
|
-
})
|
|
111
|
-
|
|
55
|
+
] }),
|
|
56
|
+
/* @__PURE__ */ y("div", {
|
|
57
|
+
className: "space-y-4",
|
|
58
|
+
children: [/* @__PURE__ */ v("h2", {
|
|
59
|
+
className: "text-lg font-semibold",
|
|
60
|
+
children: "Newsletters"
|
|
61
|
+
}), /* @__PURE__ */ v(c, {
|
|
62
|
+
columns: [
|
|
63
|
+
{
|
|
64
|
+
key: "title",
|
|
65
|
+
header: "Title",
|
|
66
|
+
render: (e) => /* @__PURE__ */ y("div", {
|
|
67
|
+
className: "flex flex-col",
|
|
68
|
+
children: [/* @__PURE__ */ v("span", {
|
|
69
|
+
className: "font-medium text-text-primary",
|
|
70
|
+
children: e.title
|
|
71
|
+
}), /* @__PURE__ */ v("span", {
|
|
72
|
+
className: "text-xs text-text-secondary",
|
|
73
|
+
children: e.subject
|
|
74
|
+
})]
|
|
75
|
+
})
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
key: "status",
|
|
79
|
+
header: "Status",
|
|
80
|
+
render: (t) => /* @__PURE__ */ v(e, {
|
|
81
|
+
variant: {
|
|
82
|
+
SENT: "success",
|
|
83
|
+
SCHEDULED: "warning",
|
|
84
|
+
DRAFT: "secondary"
|
|
85
|
+
}[t.status] || "outline",
|
|
86
|
+
children: t.status
|
|
87
|
+
})
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
key: "sentAt",
|
|
91
|
+
header: "Sent Date",
|
|
92
|
+
render: (e) => e.sentAt ? i(e.sentAt) : "-"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
key: "actions",
|
|
96
|
+
header: "",
|
|
97
|
+
align: "right",
|
|
98
|
+
render: (e) => /* @__PURE__ */ v("div", {
|
|
99
|
+
className: "flex justify-end gap-2",
|
|
100
|
+
children: e.status === "DRAFT" && /* @__PURE__ */ y(t, {
|
|
101
|
+
size: "sm",
|
|
102
|
+
onClick: () => k(e.id),
|
|
103
|
+
children: [/* @__PURE__ */ v(g, { className: "mr-2 h-3 w-3" }), "Send Now"]
|
|
104
|
+
})
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
],
|
|
108
|
+
data: O,
|
|
109
|
+
keyExtractor: (e) => e.id,
|
|
110
|
+
loading: w,
|
|
111
|
+
emptyState: /* @__PURE__ */ v(n, {
|
|
112
|
+
illustration: "empty-notifications",
|
|
113
|
+
icon: /* @__PURE__ */ v(p, { className: "h-8 w-8" }),
|
|
114
|
+
title: "No newsletters found",
|
|
115
|
+
description: "Create your first newsletter to communicate with users"
|
|
116
|
+
})
|
|
117
|
+
})]
|
|
118
|
+
})
|
|
119
|
+
]
|
|
112
120
|
})
|
|
113
121
|
});
|
|
114
122
|
}
|
|
115
123
|
//#endregion
|
|
116
|
-
export {
|
|
124
|
+
export { x as CommunicationsPage };
|
|
117
125
|
|
|
118
126
|
//# sourceMappingURL=CommunicationsPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommunicationsPage.js","names":[],"sources":["../../../src/admin/pages/CommunicationsPage.tsx"],"sourcesContent":["import { Mail, Plus, Send, Users, Eye, MousePointer2 } from 'lucide-react';\nimport {\n PageLayout,\n Button,\n DataTable,\n Badge,\n MetricCard,\n MetricsGrid,\n EmptyState,\n} from '../components';\nimport {\n useListNewslettersQuery,\n useGetNewsletterStatsQuery,\n useSendNewsletterMutation,\n} from '../../generated/global-operations';\nimport { formatDateTime, formatPercentage } from '../utils/formatters';\n\nexport function CommunicationsPage() {\n const { data: statsData, loading: statsLoading } = useGetNewsletterStatsQuery();\n const {\n data: listData,\n loading: listLoading,\n refetch,\n } = useListNewslettersQuery({\n variables: { limit: 50 },\n });\n\n const [sendNewsletter] = useSendNewsletterMutation();\n\n const stats = statsData?.newsletterStats;\n const newsletters = listData?.newsletters || [];\n\n const handleSend = async (id: string) => {\n if (confirm('Are you sure you want to send this newsletter to all subscribers?')) {\n try {\n await sendNewsletter({ variables: { id } });\n refetch();\n } catch (err) {\n console.error('Failed to send newsletter:', err);\n }\n }\n };\n\n const columns = [\n {\n key: 'title',\n header: 'Title',\n render: (news: any) => (\n <div className=\"flex flex-col\">\n <span className=\"font-medium text-text-primary\">{news.title}</span>\n <span className=\"text-xs text-text-secondary\">{news.subject}</span>\n </div>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n render: (news: any) => {\n const variants: Record<\n string,\n 'default' | 'secondary' | 'success' | 'warning' | 'destructive' | 'outline'\n > = {\n SENT: 'success',\n SCHEDULED: 'warning',\n DRAFT: 'secondary',\n };\n return <Badge variant={variants[news.status] || 'outline'}>{news.status}</Badge>;\n },\n },\n {\n key: 'sentAt',\n header: 'Sent Date',\n render: (news: any) => (news.sentAt ? formatDateTime(news.sentAt) : '-'),\n },\n {\n key: 'actions',\n header: '',\n align: 'right' as const,\n render: (news: any) => (\n <div className=\"flex justify-end gap-2\">\n {news.status === 'DRAFT' && (\n <Button size=\"sm\" onClick={() => handleSend(news.id)}>\n <Send className=\"mr-2 h-3 w-3\" />\n Send Now\n </Button>\n )}\n </div>\n ),\n },\n ];\n\n return (\n <PageLayout\n title=\"Communications\"\n description=\"Manage platform newsletters and system notifications\"\n actions={\n <Button>\n <Plus className=\"mr-2 h-4 w-4\" />\n Create Newsletter\n </Button>\n }\n >\n <div className=\"space-y-8\">\n <MetricsGrid>\n <MetricCard\n title=\"Total Subscribers\"\n value={stats?.totalSubscribers ?? 0}\n loading={statsLoading}\n icon={<Users className=\"h-4 w-4\" />}\n />\n <MetricCard\n title=\"Newsletters Sent\"\n value={stats?.totalSent ?? 0}\n loading={statsLoading}\n icon={<Send className=\"h-4 w-4\" />}\n />\n <MetricCard\n title=\"Avg. Open Rate\"\n value={formatPercentage(stats?.averageOpenRate ?? 0)}\n loading={statsLoading}\n icon={<Eye className=\"h-4 w-4\" />}\n />\n <MetricCard\n title=\"Avg. Click Rate\"\n value={formatPercentage(stats?.averageClickRate ?? 0)}\n loading={statsLoading}\n icon={<MousePointer2 className=\"h-4 w-4\" />}\n />\n </MetricsGrid>\n\n <div className=\"space-y-4\">\n <h2 className=\"text-lg font-semibold\">Newsletters</h2>\n <DataTable\n columns={columns}\n data={newsletters}\n keyExtractor={(n) => n.id}\n loading={listLoading}\n emptyState={\n <EmptyState\n icon={<Mail className=\"h-8 w-8\" />}\n title=\"No newsletters found\"\n description=\"Create your first newsletter to communicate with users\"\n />\n }\n />\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"CommunicationsPage.js","names":[],"sources":["../../../src/admin/pages/CommunicationsPage.tsx"],"sourcesContent":["import { Mail, Plus, Send, Users, Eye, MousePointer2 } from 'lucide-react';\nimport {\n PageLayout,\n Button,\n DataTable,\n Badge,\n MetricCard,\n MetricsGrid,\n EmptyState,\n} from '../components';\nimport {\n useListNewslettersQuery,\n useGetNewsletterStatsQuery,\n useSendNewsletterMutation,\n} from '../../generated/global-operations';\nimport { formatDateTime, formatPercentage } from '../utils/formatters';\nimport { PagePurpose } from '@burdenoff/fe-libs/ui';\n\nexport function CommunicationsPage() {\n const { data: statsData, loading: statsLoading } = useGetNewsletterStatsQuery();\n const {\n data: listData,\n loading: listLoading,\n refetch,\n } = useListNewslettersQuery({\n variables: { limit: 50 },\n });\n\n const [sendNewsletter] = useSendNewsletterMutation();\n\n const stats = statsData?.newsletterStats;\n const newsletters = listData?.newsletters || [];\n\n const handleSend = async (id: string) => {\n if (confirm('Are you sure you want to send this newsletter to all subscribers?')) {\n try {\n await sendNewsletter({ variables: { id } });\n refetch();\n } catch (err) {\n console.error('Failed to send newsletter:', err);\n }\n }\n };\n\n const columns = [\n {\n key: 'title',\n header: 'Title',\n render: (news: any) => (\n <div className=\"flex flex-col\">\n <span className=\"font-medium text-text-primary\">{news.title}</span>\n <span className=\"text-xs text-text-secondary\">{news.subject}</span>\n </div>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n render: (news: any) => {\n const variants: Record<\n string,\n 'default' | 'secondary' | 'success' | 'warning' | 'destructive' | 'outline'\n > = {\n SENT: 'success',\n SCHEDULED: 'warning',\n DRAFT: 'secondary',\n };\n return <Badge variant={variants[news.status] || 'outline'}>{news.status}</Badge>;\n },\n },\n {\n key: 'sentAt',\n header: 'Sent Date',\n render: (news: any) => (news.sentAt ? formatDateTime(news.sentAt) : '-'),\n },\n {\n key: 'actions',\n header: '',\n align: 'right' as const,\n render: (news: any) => (\n <div className=\"flex justify-end gap-2\">\n {news.status === 'DRAFT' && (\n <Button size=\"sm\" onClick={() => handleSend(news.id)}>\n <Send className=\"mr-2 h-3 w-3\" />\n Send Now\n </Button>\n )}\n </div>\n ),\n },\n ];\n\n return (\n <PageLayout\n title=\"Communications\"\n gradientWord=\"Communications\"\n description=\"Manage platform newsletters and system notifications\"\n actions={\n <Button>\n <Plus className=\"mr-2 h-4 w-4\" />\n Create Newsletter\n </Button>\n }\n >\n <div className=\"space-y-8\">\n <PagePurpose>\n Reach your users directly. Draft newsletters, send them to your subscriber base, and\n track how many people opened and clicked — use it to announce launches, share product\n updates, or run a re-engagement campaign.\n </PagePurpose>\n\n <MetricsGrid>\n <MetricCard\n title=\"Total Subscribers\"\n hero\n value={stats?.totalSubscribers ?? 0}\n loading={statsLoading}\n icon={<Users className=\"h-4 w-4\" />}\n />\n <MetricCard\n title=\"Newsletters Sent\"\n value={stats?.totalSent ?? 0}\n loading={statsLoading}\n icon={<Send className=\"h-4 w-4\" />}\n />\n <MetricCard\n title=\"Avg. Open Rate\"\n value={formatPercentage(stats?.averageOpenRate ?? 0)}\n loading={statsLoading}\n icon={<Eye className=\"h-4 w-4\" />}\n />\n <MetricCard\n title=\"Avg. Click Rate\"\n value={formatPercentage(stats?.averageClickRate ?? 0)}\n loading={statsLoading}\n icon={<MousePointer2 className=\"h-4 w-4\" />}\n />\n </MetricsGrid>\n\n <div className=\"space-y-4\">\n <h2 className=\"text-lg font-semibold\">Newsletters</h2>\n <DataTable\n columns={columns}\n data={newsletters}\n keyExtractor={(n) => n.id}\n loading={listLoading}\n emptyState={\n <EmptyState\n illustration=\"empty-notifications\"\n icon={<Mail className=\"h-8 w-8\" />}\n title=\"No newsletters found\"\n description=\"Create your first newsletter to communicate with users\"\n />\n }\n />\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;AAkBA,SAAgB,IAAqB;CACnC,IAAM,EAAE,MAAM,GAAW,SAAS,MAAiB,GAA4B,EACzE,EACJ,MAAM,GACN,SAAS,GACT,eACE,EAAwB,EAC1B,WAAW,EAAE,OAAO,IAAI,EACzB,CAAC,EAEI,CAAC,KAAkB,GAA2B,EAE9C,IAAQ,GAAW,iBACnB,IAAc,GAAU,eAAe,EAAE,EAEzC,IAAa,OAAO,MAAe;AACvC,MAAI,QAAQ,oEAAoE,CAC9E,KAAI;AAEF,GADA,MAAM,EAAe,EAAE,WAAW,EAAE,OAAI,EAAE,CAAC,EAC3C,GAAS;WACF,GAAK;AACZ,WAAQ,MAAM,8BAA8B,EAAI;;;AAqDtD,QACE,kBAAC,GAAD;EACE,OAAM;EACN,cAAa;EACb,aAAY;EACZ,SACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAM,WAAU,gBAAiB,CAAA,EAAA,oBAE1B,EAAA,CAAA;YAGX,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,GAAD,EAAA,UAAa,wNAIC,CAAA;IAEd,kBAAC,GAAD,EAAA,UAAA;KACE,kBAAC,GAAD;MACE,OAAM;MACN,MAAA;MACA,OAAO,GAAO,oBAAoB;MAClC,SAAS;MACT,MAAM,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA;MACnC,CAAA;KACF,kBAAC,GAAD;MACE,OAAM;MACN,OAAO,GAAO,aAAa;MAC3B,SAAS;MACT,MAAM,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;MAClC,CAAA;KACF,kBAAC,GAAD;MACE,OAAM;MACN,OAAO,EAAiB,GAAO,mBAAmB,EAAE;MACpD,SAAS;MACT,MAAM,kBAAC,GAAD,EAAK,WAAU,WAAY,CAAA;MACjC,CAAA;KACF,kBAAC,GAAD;MACE,OAAM;MACN,OAAO,EAAiB,GAAO,oBAAoB,EAAE;MACrD,SAAS;MACT,MAAM,kBAAC,GAAD,EAAe,WAAU,WAAY,CAAA;MAC3C,CAAA;KACU,EAAA,CAAA;IAEd,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,MAAD;MAAI,WAAU;gBAAwB;MAAgB,CAAA,EACtD,kBAAC,GAAD;MACW,SAlGH;OACd;QACE,KAAK;QACL,QAAQ;QACR,SAAS,MACP,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,QAAD;UAAM,WAAU;oBAAiC,EAAK;UAAa,CAAA,EACnE,kBAAC,QAAD;UAAM,WAAU;oBAA+B,EAAK;UAAe,CAAA,CAC/D;;QAET;OACD;QACE,KAAK;QACL,QAAQ;QACR,SAAS,MASA,kBAAC,GAAD;SAAO,SALV;UACF,MAAM;UACN,WAAW;UACX,OAAO;UACR,CAC+B,EAAK,WAAW;mBAAY,EAAK;SAAe,CAAA;QAEnF;OACD;QACE,KAAK;QACL,QAAQ;QACR,SAAS,MAAe,EAAK,SAAS,EAAe,EAAK,OAAO,GAAG;QACrE;OACD;QACE,KAAK;QACL,QAAQ;QACR,OAAO;QACP,SAAS,MACP,kBAAC,OAAD;SAAK,WAAU;mBACZ,EAAK,WAAW,WACf,kBAAC,GAAD;UAAQ,MAAK;UAAK,eAAe,EAAW,EAAK,GAAG;oBAApD,CACE,kBAAC,GAAD,EAAM,WAAU,gBAAiB,CAAA,EAAA,WAE1B;;SAEP,CAAA;QAET;OACF;MAqDS,MAAM;MACN,eAAe,MAAM,EAAE;MACvB,SAAS;MACT,YACE,kBAAC,GAAD;OACE,cAAa;OACb,MAAM,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;OAClC,OAAM;OACN,aAAY;OACZ,CAAA;MAEJ,CAAA,CACE;;IACF;;EACK,CAAA"}
|