@aglyn/plugins-commerce 1.0.0-beta.218 → 1.0.0-beta.220
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/README.md +6 -0
- package/package.json +14 -14
- package/src/index.d.ts +1 -0
- package/src/index.js +1 -19
- package/src/index.js.map +1 -1
- package/src/lib/components/cart.js +2 -1
- package/src/lib/components/cart.js.map +1 -1
- package/src/lib/components/console/catalog-organization-card.component.js +665 -459
- package/src/lib/components/console/catalog-organization-card.component.js.map +1 -1
- package/src/lib/components/console/host-orders-card.component.js +2 -1
- package/src/lib/components/console/host-orders-card.component.js.map +1 -1
- package/src/lib/components/console/member-posts-card.component.js +1 -1
- package/src/lib/components/console/member-posts-card.component.js.map +1 -1
- package/src/lib/components/console/order-detail-dialog.component.js +8 -7
- package/src/lib/components/console/order-detail-dialog.component.js.map +1 -1
- package/src/lib/components/console/payments-settings-card.component.js +2 -2
- package/src/lib/components/console/payments-settings-card.component.js.map +1 -1
- package/src/lib/components/console/product-editor-dialog.component.js +1221 -889
- package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
- package/src/lib/components/console/product-zones.d.ts +184 -0
- package/src/lib/components/console/product-zones.js +54 -0
- package/src/lib/components/console/product-zones.js.map +1 -0
- package/src/lib/components/console/products-hub-card.component.js +3 -2
- package/src/lib/components/console/products-hub-card.component.js.map +1 -1
- package/src/lib/components/console/products-hub-zone.component.d.ts +1 -1
- package/src/lib/components/console/products-hub-zone.component.js +2 -1
- package/src/lib/components/console/products-hub-zone.component.js.map +1 -1
- package/src/lib/components/console/site-member-purchases.component.d.ts +24 -0
- package/src/lib/components/console/site-member-purchases.component.js +339 -0
- package/src/lib/components/console/site-member-purchases.component.js.map +1 -0
- package/src/lib/components/console/store-settings-card.component.js +2 -2
- package/src/lib/components/console/store-settings-card.component.js.map +1 -1
- package/src/lib/components/console/storefront-tax-summary-card.component.js +1 -1
- package/src/lib/components/console/storefront-tax-summary-card.component.js.map +1 -1
- package/src/lib/components/member-signup.js +2 -2
- package/src/lib/components/member-signup.js.map +1 -1
- package/src/lib/components/newsletter-signup.js +2 -2
- package/src/lib/components/newsletter-signup.js.map +1 -1
- package/src/lib/components/product-detail.js +2 -1
- package/src/lib/components/product-detail.js.map +1 -1
- package/src/lib/components/product-grid.js +1 -1
- package/src/lib/components/product-grid.js.map +1 -1
- package/src/lib/constants/operator-alerts.js +5 -3
- package/src/lib/constants/operator-alerts.js.map +1 -1
- package/src/lib/constants/storefront-payments.d.ts +33 -0
- package/src/lib/constants/storefront-payments.js +35 -0
- package/src/lib/constants/storefront-payments.js.map +1 -0
- package/src/lib/declarations.server.d.ts +3 -1
- package/src/lib/declarations.server.js +19 -1
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/model/commerce-io.js +1 -1
- package/src/lib/model/commerce-io.js.map +1 -1
- package/src/lib/model/commerce-record-lists.d.ts +31 -0
- package/src/lib/model/commerce-record-lists.js +45 -0
- package/src/lib/model/commerce-record-lists.js.map +1 -0
- package/src/lib/model/commerce-record-routes.d.ts +45 -0
- package/src/lib/model/commerce-record-routes.js +72 -0
- package/src/lib/model/commerce-record-routes.js.map +1 -0
- package/src/lib/model/commerce-search-sources.d.ts +21 -0
- package/src/lib/model/commerce-search-sources.js +42 -0
- package/src/lib/model/commerce-search-sources.js.map +1 -0
- package/src/lib/model/commerce-topics.d.ts +20 -0
- package/src/lib/model/commerce-topics.js +33 -0
- package/src/lib/model/commerce-topics.js.map +1 -0
- package/src/lib/model/index.d.ts +1 -0
- package/src/lib/model/index.js +1 -0
- package/src/lib/model/index.js.map +1 -1
- package/src/lib/model/product-record.d.ts +32 -0
- package/src/lib/model/product-record.js +82 -0
- package/src/lib/model/product-record.js.map +1 -0
- package/src/lib/model/site-member-purchases.d.ts +48 -0
- package/src/lib/model/site-member-purchases.js +50 -0
- package/src/lib/model/site-member-purchases.js.map +1 -0
- package/src/lib/model/storefront-tax-summary.d.ts +121 -0
- package/src/lib/model/storefront-tax-summary.js +148 -0
- package/src/lib/model/storefront-tax-summary.js.map +1 -0
- package/src/lib/plan-entitlements.d.ts +35 -0
- package/src/lib/plan-entitlements.js +54 -0
- package/src/lib/plan-entitlements.js.map +1 -0
- package/src/lib/plugin.js +21 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/billing-webhook.js +387 -248
- package/src/lib/server/billing-webhook.js.map +1 -1
- package/src/lib/server/cart-checkout.js +3 -3
- package/src/lib/server/cart-checkout.js.map +1 -1
- package/src/lib/server/checkout.js +2 -2
- package/src/lib/server/checkout.js.map +1 -1
- package/src/lib/server/connect.js +5 -5
- package/src/lib/server/connect.js.map +1 -1
- package/src/lib/server/draft-order.js +2 -2
- package/src/lib/server/draft-order.js.map +1 -1
- package/src/lib/server/gift-cards.js +7 -3
- package/src/lib/server/gift-cards.js.map +1 -1
- package/src/lib/server/low-stock.js +3 -1
- package/src/lib/server/low-stock.js.map +1 -1
- package/src/lib/server/member-post.js +2 -2
- package/src/lib/server/member-post.js.map +1 -1
- package/src/lib/server/membership-admin-password.js +4 -1
- package/src/lib/server/membership-admin-password.js.map +1 -1
- package/src/lib/server/membership-content.js +1 -1
- package/src/lib/server/membership-content.js.map +1 -1
- package/src/lib/server/membership-register.js +13 -2
- package/src/lib/server/membership-register.js.map +1 -1
- package/src/lib/server/newsletter.js +32 -19
- package/src/lib/server/newsletter.js.map +1 -1
- package/src/lib/server/pos-order.js +3 -3
- package/src/lib/server/pos-order.js.map +1 -1
- package/src/lib/server/process-abandoned.js +4 -2
- package/src/lib/server/process-abandoned.js.map +1 -1
- package/src/lib/server/process-restock.js +7 -5
- package/src/lib/server/process-restock.js.map +1 -1
- package/src/lib/server/product-record-index.d.ts +19 -0
- package/src/lib/server/product-record-index.js +83 -0
- package/src/lib/server/product-record-index.js.map +1 -0
- package/src/lib/server/reconcile-stock.js +1 -1
- package/src/lib/server/reconcile-stock.js.map +1 -1
- package/src/lib/server/reserve-stock.js +6 -1
- package/src/lib/server/reserve-stock.js.map +1 -1
- package/src/lib/server/reserve.js +2 -2
- package/src/lib/server/reserve.js.map +1 -1
- package/src/lib/server/revenue-source.d.ts +130 -0
- package/src/lib/server/revenue-source.js +286 -0
- package/src/lib/server/revenue-source.js.map +1 -0
- package/src/lib/server/supplier-outbox.js +16 -1
- package/src/lib/server/supplier-outbox.js.map +1 -1
- package/src/lib/server/supplier-update.js +19 -9
- package/src/lib/server/supplier-update.js.map +1 -1
- package/src/lib/server/tax-profile.d.ts +8 -5
- package/src/lib/server/tax-profile.js +22 -5
- package/src/lib/server/tax-profile.js.map +1 -1
- package/src/lib/server/tax-return-source.d.ts +125 -0
- package/src/lib/server/tax-return-source.js +524 -0
- package/src/lib/server/tax-return-source.js.map +1 -0
- package/src/lib/server/tax-summary.d.ts +92 -0
- package/src/lib/server/tax-summary.js +258 -0
- package/src/lib/server/tax-summary.js.map +1 -0
- package/src/lib/server.d.ts +1 -0
- package/src/lib/server.js +23 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/starter-templates.d.ts +19 -0
- package/src/lib/starter-templates.js +157 -0
- package/src/lib/starter-templates.js.map +1 -0
- package/src/lib/tenant-emails.d.ts +33 -0
- package/src/lib/tenant-emails.js +551 -0
- package/src/lib/tenant-emails.js.map +1 -0
- package/src/lib/testing/stand-in-crm-record-routes.d.ts +25 -0
- package/src/lib/testing/stand-in-crm-record-routes.js +46 -0
- package/src/lib/testing/stand-in-crm-record-routes.js.map +1 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ 'use client';
|
|
17
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
18
|
+
import { ListPagination } from "@aglyn/shared-ui-jsx/components/list-pagination.component";
|
|
19
|
+
import { TABLE_PAGE_SIZE_DEFAULT } from "@aglyn/shared-ui-jsx/const/table-pagination";
|
|
20
|
+
import { ceilingedWindow, useFirestore, useFirestoreCollection } from "@aglyn/tenant-feature-instance";
|
|
21
|
+
import { Chip, Divider, Stack, Typography } from "@mui/material";
|
|
22
|
+
import { collection, documentId, limit, orderBy, query, where } from "firebase/firestore";
|
|
23
|
+
import { useEffect, useMemo, useState } from "react";
|
|
24
|
+
import { splitOrderReversal } from "../../model/commerce-dispute.js";
|
|
25
|
+
import { computeLifetimePurchaseCents } from "../../model/site-member-purchases.js";
|
|
26
|
+
const usd = (cents)=>`$${(cents / 100).toFixed(2)}`;
|
|
27
|
+
/**
|
|
28
|
+
* How many order documents this reads for one member.
|
|
29
|
+
*
|
|
30
|
+
* A CEILING, not a page size: the lifetime-purchase figure above the list is
|
|
31
|
+
* summed from these rows, so every one it intends to count has to be held. A
|
|
32
|
+
* server page would make that headline number the total of ten orders and
|
|
33
|
+
* print it as a lifetime.
|
|
34
|
+
*/ const ORDER_CEILING = 100;
|
|
35
|
+
/** The same, for the subscriptions this member holds. */ const SUBSCRIPTION_CEILING = 25;
|
|
36
|
+
/**
|
|
37
|
+
* The reversal suffix on one order row, split by the door the money left
|
|
38
|
+
* through (AGL-1810). `refundedCents` carries a lost chargeback as well as a
|
|
39
|
+
* refund (AGL-1787 puts both there deliberately), so rendering the whole
|
|
40
|
+
* figure as "refunded" told the merchant they chose a reversal a bank took.
|
|
41
|
+
* `computeLifetimePurchaseCents` keeps netting the total; only the label
|
|
42
|
+
* splits.
|
|
43
|
+
*/ const reversalSuffix = (order)=>{
|
|
44
|
+
const { refundedCents, chargedBackCents } = splitOrderReversal(order);
|
|
45
|
+
return (refundedCents ? ` · refunded ${usd(refundedCents)}` : '') + (chargedBackCents ? ` · charged back ${usd(chargedBackCents)}` : '');
|
|
46
|
+
};
|
|
47
|
+
/** Display order number: v1 sequential `#1042`, else a doc-id stub. */ const orderNumber = (order)=>{
|
|
48
|
+
var _order_$id;
|
|
49
|
+
return order.number != null ? `#${order.number}` : `#${String((_order_$id = order.$id) != null ? _order_$id : '').slice(-6).toUpperCase()}`;
|
|
50
|
+
};
|
|
51
|
+
const orderCreatedMs = (order)=>{
|
|
52
|
+
var _order_createdAtMs, _ref;
|
|
53
|
+
var _order_createdAt;
|
|
54
|
+
return Number((_order_createdAtMs = order.createdAtMs) != null ? _order_createdAtMs : ((_ref = (_order_createdAt = order.createdAt) == null ? void 0 : _order_createdAt.seconds) != null ? _ref : 0) * 1000) || 0;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* What a site user bought (AGL-546): the lifetime purchase total, the order
|
|
58
|
+
* history (the payment records — Stripe intent id and refunds included) and
|
|
59
|
+
* storefront subscriptions — the `siteMember` zone of the account's drawer.
|
|
60
|
+
*
|
|
61
|
+
* Orders match by email (mirrors membership-account, AGL-294) and sort
|
|
62
|
+
* client-side. The QUERY orders on the document name: `orderBy('createdAt')`
|
|
63
|
+
* would want a composite index and, worse, would drop every order missing the
|
|
64
|
+
* field, while an equality filter plus `orderBy(documentId())` rides the
|
|
65
|
+
* automatic single-field index and can drop nothing — a document's name
|
|
66
|
+
* cannot be absent. That makes the window total rather than a pseudo-random
|
|
67
|
+
* hundred, which is what the newest-first sort below was quietly disguising.
|
|
68
|
+
* `orders` reads are admin/editor-only in rules (AGL-502), so viewers get a
|
|
69
|
+
* note instead of history.
|
|
70
|
+
*
|
|
71
|
+
* One section per element, as a fragment: the zone is `bare`, and the drawer's
|
|
72
|
+
* own column spaces what it holds.
|
|
73
|
+
*/ export function SiteMemberPurchases(props) {
|
|
74
|
+
var _member_$id, _member_email, _ref;
|
|
75
|
+
const { hostId, member } = props;
|
|
76
|
+
const firestore = useFirestore();
|
|
77
|
+
const memberId = String((_member_$id = member.$id) != null ? _member_$id : '');
|
|
78
|
+
const email = String((_member_email = member['email']) != null ? _member_email : '');
|
|
79
|
+
const { data: orderDocs, status: ordersStatus } = useFirestoreCollection(()=>email ? query(collection(firestore, 'hosts', hostId, 'orders'), where('customerEmail', '==', email), orderBy(documentId()), // One document past the ceiling, so "this member has more" is a
|
|
80
|
+
// fact. `length === 100` cannot tell a member with exactly a
|
|
81
|
+
// hundred orders from one with a thousand, and the difference is
|
|
82
|
+
// whether the lifetime figure above is a total or a floor.
|
|
83
|
+
limit(ORDER_CEILING + 1)) : null, [
|
|
84
|
+
firestore,
|
|
85
|
+
hostId,
|
|
86
|
+
email
|
|
87
|
+
], {
|
|
88
|
+
idField: '$id'
|
|
89
|
+
});
|
|
90
|
+
const { rows: readOrders, truncated: ordersTruncated } = ceilingedWindow(orderDocs, ORDER_CEILING);
|
|
91
|
+
const orders = useMemo(()=>[
|
|
92
|
+
...readOrders
|
|
93
|
+
].sort((a, b)=>orderCreatedMs(b) - orderCreatedMs(a)), // eslint-disable-next-line react-hooks/exhaustive-deps
|
|
94
|
+
[
|
|
95
|
+
orderDocs
|
|
96
|
+
]);
|
|
97
|
+
const lifetimeCents = useMemo(()=>computeLifetimePurchaseCents(orders), [
|
|
98
|
+
orders
|
|
99
|
+
]);
|
|
100
|
+
/**
|
|
101
|
+
* The order query failed AND nothing came back with it (AGL-1066).
|
|
102
|
+
*
|
|
103
|
+
* The copy this gates blames the reader's ROLE, which is the right
|
|
104
|
+
* explanation for the denial this was written for — orders are
|
|
105
|
+
* admin/editor-only in rules. It is the wrong explanation once a stale
|
|
106
|
+
* SESSION can push a listen to `'error'` too, and flatly wrong when
|
|
107
|
+
* `persistentLocalCache` is still serving the rows: telling someone their
|
|
108
|
+
* role is insufficient while their orders sit right there is a support
|
|
109
|
+
* ticket about permissions that was never about permissions.
|
|
110
|
+
*/ const ordersUnreadable = ordersStatus === 'error' && orders.length === 0;
|
|
111
|
+
const { data: subscriptionDocs } = useFirestoreCollection(()=>email ? query(collection(firestore, 'hosts', hostId, 'subscriptions'), where('customerEmail', '==', email), // The same decision as the orders above, for the same reason: a
|
|
112
|
+
// subscription written without `createdAt` would be hidden by a
|
|
113
|
+
// field ordering rather than mis-sorted by it.
|
|
114
|
+
orderBy(documentId()), limit(SUBSCRIPTION_CEILING + 1)) : null, [
|
|
115
|
+
firestore,
|
|
116
|
+
hostId,
|
|
117
|
+
email
|
|
118
|
+
], {
|
|
119
|
+
idField: '$id'
|
|
120
|
+
});
|
|
121
|
+
const { rows: subscriptions, truncated: subscriptionsTruncated } = ceilingedWindow(subscriptionDocs, SUBSCRIPTION_CEILING);
|
|
122
|
+
// Product names for subscriptions, loaded only when any exist.
|
|
123
|
+
const { data: productDocs } = useFirestoreCollection(()=>{
|
|
124
|
+
var _ref;
|
|
125
|
+
return email && ((_ref = subscriptionDocs == null ? void 0 : subscriptionDocs.length) != null ? _ref : 0) > 0 ? query(collection(firestore, 'hosts', hostId, 'products'), // A LOOKUP, not a list — but the same ordering decision, so the
|
|
126
|
+
// hundred it reads is a reachable hundred rather than a sample,
|
|
127
|
+
// and a product past it degrades a subscription's label to its
|
|
128
|
+
// id rather than hiding the subscription.
|
|
129
|
+
orderBy(documentId()), limit(100)) : null;
|
|
130
|
+
}, [
|
|
131
|
+
firestore,
|
|
132
|
+
hostId,
|
|
133
|
+
email,
|
|
134
|
+
((_ref = subscriptionDocs == null ? void 0 : subscriptionDocs.length) != null ? _ref : 0) > 0
|
|
135
|
+
], {
|
|
136
|
+
idField: '$id'
|
|
137
|
+
});
|
|
138
|
+
/*==========================================
|
|
139
|
+
* BOTH LISTS PAGE IN MEMORY.
|
|
140
|
+
*
|
|
141
|
+
* Each ceiling is held whole, which is what lets the orders sort
|
|
142
|
+
* newest-first and the lifetime figure sum over all of them. A server page
|
|
143
|
+
* would have made both about ten rows instead of about this member.
|
|
144
|
+
*=========================================*/ const [orderPage, setOrderPage] = useState(0);
|
|
145
|
+
const [orderPageSize, setOrderPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT);
|
|
146
|
+
const visibleOrders = useMemo(()=>orders.slice(orderPage * orderPageSize, orderPage * orderPageSize + orderPageSize), [
|
|
147
|
+
orders,
|
|
148
|
+
orderPage,
|
|
149
|
+
orderPageSize
|
|
150
|
+
]);
|
|
151
|
+
const [subscriptionPage, setSubscriptionPage] = useState(0);
|
|
152
|
+
const [subscriptionPageSize, setSubscriptionPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT);
|
|
153
|
+
const visibleSubscriptions = useMemo(()=>subscriptions.slice(subscriptionPage * subscriptionPageSize, subscriptionPage * subscriptionPageSize + subscriptionPageSize), [
|
|
154
|
+
subscriptions,
|
|
155
|
+
subscriptionPage,
|
|
156
|
+
subscriptionPageSize
|
|
157
|
+
]);
|
|
158
|
+
/*
|
|
159
|
+
* A different member starts at page one. The drawer is reused for whoever
|
|
160
|
+
* is selected, so without this the next member opens three pages into a
|
|
161
|
+
* history they may not have — which renders as an empty list and reads as
|
|
162
|
+
* "this person never bought anything".
|
|
163
|
+
*/ useEffect(()=>{
|
|
164
|
+
setOrderPage(0);
|
|
165
|
+
setSubscriptionPage(0);
|
|
166
|
+
}, [
|
|
167
|
+
memberId
|
|
168
|
+
]);
|
|
169
|
+
const productNames = useMemo(()=>{
|
|
170
|
+
const map = {};
|
|
171
|
+
for (const product of productDocs != null ? productDocs : []){
|
|
172
|
+
var _product_name;
|
|
173
|
+
map[product.$id] = (_product_name = product.name) != null ? _product_name : product.$id;
|
|
174
|
+
}
|
|
175
|
+
return map;
|
|
176
|
+
}, [
|
|
177
|
+
productDocs
|
|
178
|
+
]);
|
|
179
|
+
return /*#__PURE__*/ _jsxs(_Fragment, {
|
|
180
|
+
children: [
|
|
181
|
+
/*#__PURE__*/ _jsx(Divider, {
|
|
182
|
+
textAlign: "left",
|
|
183
|
+
children: 'Lifetime purchases'
|
|
184
|
+
}),
|
|
185
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
186
|
+
variant: "h6",
|
|
187
|
+
children: ordersUnreadable ? '—' : `${ordersTruncated ? 'at least ' : ''}${usd(lifetimeCents)}`
|
|
188
|
+
}),
|
|
189
|
+
/*#__PURE__*/ _jsxs(Typography, {
|
|
190
|
+
variant: "caption",
|
|
191
|
+
color: "text.secondary",
|
|
192
|
+
children: [
|
|
193
|
+
'Charged order totals minus refunds; pending and canceled ' + 'orders excluded.',
|
|
194
|
+
ordersTruncated ? ` Summed over the ${ORDER_CEILING} orders read here; this ` + 'member has more.' : ''
|
|
195
|
+
]
|
|
196
|
+
}),
|
|
197
|
+
/*#__PURE__*/ _jsx(Divider, {
|
|
198
|
+
textAlign: "left",
|
|
199
|
+
children: 'Orders'
|
|
200
|
+
}),
|
|
201
|
+
ordersUnreadable ? /*#__PURE__*/ _jsx(Typography, {
|
|
202
|
+
variant: "body2",
|
|
203
|
+
color: "text.secondary",
|
|
204
|
+
children: 'Order history needs the editor or admin role on this site.'
|
|
205
|
+
}) : orders.length === 0 ? /*#__PURE__*/ _jsx(Typography, {
|
|
206
|
+
variant: "body2",
|
|
207
|
+
color: "text.secondary",
|
|
208
|
+
children: 'No orders yet.'
|
|
209
|
+
}) : visibleOrders.map((order)=>{
|
|
210
|
+
var _ref, _ref1, _order_status;
|
|
211
|
+
var _order_totals;
|
|
212
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
213
|
+
spacing: 0,
|
|
214
|
+
children: [
|
|
215
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
216
|
+
direction: "row",
|
|
217
|
+
spacing: 1,
|
|
218
|
+
sx: {
|
|
219
|
+
alignItems: 'center'
|
|
220
|
+
},
|
|
221
|
+
children: [
|
|
222
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
223
|
+
variant: "body2",
|
|
224
|
+
sx: {
|
|
225
|
+
flex: 1
|
|
226
|
+
},
|
|
227
|
+
noWrap: true,
|
|
228
|
+
children: `${orderNumber(order)} · ${usd(Number((_ref = (_ref1 = (_order_totals = order.totals) == null ? void 0 : _order_totals.totalCents) != null ? _ref1 : order.amountCents) != null ? _ref : 0) || 0)}`
|
|
229
|
+
}),
|
|
230
|
+
/*#__PURE__*/ _jsx(Chip, {
|
|
231
|
+
label: String((_order_status = order.status) != null ? _order_status : 'paid').replace('_', ' '),
|
|
232
|
+
size: "small",
|
|
233
|
+
variant: "outlined"
|
|
234
|
+
})
|
|
235
|
+
]
|
|
236
|
+
}),
|
|
237
|
+
/*#__PURE__*/ _jsxs(Typography, {
|
|
238
|
+
variant: "caption",
|
|
239
|
+
color: "text.secondary",
|
|
240
|
+
children: [
|
|
241
|
+
orderCreatedMs(order) ? new Date(orderCreatedMs(order)).toLocaleDateString() : '—',
|
|
242
|
+
reversalSuffix(order)
|
|
243
|
+
]
|
|
244
|
+
}),
|
|
245
|
+
order.paymentIntentId ? /*#__PURE__*/ _jsx(Typography, {
|
|
246
|
+
variant: "caption",
|
|
247
|
+
color: "text.secondary",
|
|
248
|
+
sx: {
|
|
249
|
+
fontFamily: 'monospace'
|
|
250
|
+
},
|
|
251
|
+
noWrap: true,
|
|
252
|
+
children: order.paymentIntentId
|
|
253
|
+
}) : null
|
|
254
|
+
]
|
|
255
|
+
}, order.$id);
|
|
256
|
+
}),
|
|
257
|
+
ordersUnreadable || orders.length === 0 ? null : /*#__PURE__*/ _jsx(ListPagination, {
|
|
258
|
+
page: orderPage,
|
|
259
|
+
pageSize: orderPageSize,
|
|
260
|
+
rowCount: visibleOrders.length,
|
|
261
|
+
// The orders held here — a slice of rows already read, so the
|
|
262
|
+
// total is exact for the window. The lifetime caption above is
|
|
263
|
+
// where the window's own shortfall is stated.
|
|
264
|
+
count: orders.length,
|
|
265
|
+
onPageChange: setOrderPage,
|
|
266
|
+
onPageSizeChange: setOrderPageSize
|
|
267
|
+
}),
|
|
268
|
+
subscriptions.length > 0 ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
269
|
+
children: [
|
|
270
|
+
/*#__PURE__*/ _jsx(Divider, {
|
|
271
|
+
textAlign: "left",
|
|
272
|
+
children: 'Subscriptions'
|
|
273
|
+
}),
|
|
274
|
+
visibleSubscriptions.map((subscription)=>{
|
|
275
|
+
var _ref, _productNames_subscription_productId, _subscription_invoicesCount, _subscription_status;
|
|
276
|
+
var _subscription_totals;
|
|
277
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
278
|
+
direction: "row",
|
|
279
|
+
spacing: 1,
|
|
280
|
+
sx: {
|
|
281
|
+
alignItems: 'center'
|
|
282
|
+
},
|
|
283
|
+
children: [
|
|
284
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
285
|
+
sx: {
|
|
286
|
+
flex: 1,
|
|
287
|
+
minWidth: 0
|
|
288
|
+
},
|
|
289
|
+
children: [
|
|
290
|
+
/*#__PURE__*/ _jsxs(Typography, {
|
|
291
|
+
variant: "body2",
|
|
292
|
+
noWrap: true,
|
|
293
|
+
children: [
|
|
294
|
+
(_ref = (_productNames_subscription_productId = productNames[subscription.productId]) != null ? _productNames_subscription_productId : subscription.productId) != null ? _ref : 'Subscription',
|
|
295
|
+
((_subscription_totals = subscription.totals) == null ? void 0 : _subscription_totals.totalCents) != null ? ` · ${usd(Number(subscription.totals.totalCents) || 0)}${subscription.interval ? `/${subscription.interval}` : ''}` : ''
|
|
296
|
+
]
|
|
297
|
+
}),
|
|
298
|
+
/*#__PURE__*/ _jsxs(Typography, {
|
|
299
|
+
variant: "caption",
|
|
300
|
+
color: "text.secondary",
|
|
301
|
+
children: [
|
|
302
|
+
subscription.currentPeriodEndMs ? `Renews ${new Date(Number(subscription.currentPeriodEndMs)).toLocaleDateString()}` : '—',
|
|
303
|
+
Number((_subscription_invoicesCount = subscription.invoicesCount) != null ? _subscription_invoicesCount : 0) > 0 ? ` · ${usd(Number(subscription.paidCents) || 0)} paid over ${Number(subscription.invoicesCount) === 1 ? '1 invoice' : `${Number(subscription.invoicesCount)} invoices`}` : ''
|
|
304
|
+
]
|
|
305
|
+
})
|
|
306
|
+
]
|
|
307
|
+
}),
|
|
308
|
+
/*#__PURE__*/ _jsx(Chip, {
|
|
309
|
+
label: String((_subscription_status = subscription.status) != null ? _subscription_status : 'active'),
|
|
310
|
+
size: "small",
|
|
311
|
+
variant: "outlined",
|
|
312
|
+
color: subscription.status === 'active' ? 'success' : 'default'
|
|
313
|
+
})
|
|
314
|
+
]
|
|
315
|
+
}, subscription.$id);
|
|
316
|
+
}),
|
|
317
|
+
/*#__PURE__*/ _jsx(ListPagination, {
|
|
318
|
+
page: subscriptionPage,
|
|
319
|
+
pageSize: subscriptionPageSize,
|
|
320
|
+
rowCount: visibleSubscriptions.length,
|
|
321
|
+
// This member's subscriptions, in full for the window.
|
|
322
|
+
count: subscriptions.length,
|
|
323
|
+
onPageChange: setSubscriptionPage,
|
|
324
|
+
onPageSizeChange: setSubscriptionPageSize
|
|
325
|
+
}),
|
|
326
|
+
subscriptionsTruncated ? /*#__PURE__*/ _jsx(Typography, {
|
|
327
|
+
variant: "caption",
|
|
328
|
+
color: "text.secondary",
|
|
329
|
+
children: `Showing ${SUBSCRIPTION_CEILING} subscriptions; this ` + 'member has more.'
|
|
330
|
+
}) : null
|
|
331
|
+
]
|
|
332
|
+
}) : null
|
|
333
|
+
]
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
SiteMemberPurchases.displayName = 'SiteMemberPurchases';
|
|
337
|
+
export default SiteMemberPurchases;
|
|
338
|
+
|
|
339
|
+
//# sourceMappingURL=site-member-purchases.component.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/site-member-purchases.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport type { ConsoleSiteMemberZoneProps } from '@aglyn/aglyn'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { TABLE_PAGE_SIZE_DEFAULT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport {\n ceilingedWindow,\n useFirestore,\n useFirestoreCollection,\n} from '@aglyn/tenant-feature-instance'\nimport { Chip, Divider, Stack, Typography } from '@mui/material'\nimport {\n collection,\n documentId,\n limit,\n orderBy,\n query,\n where,\n} from 'firebase/firestore'\nimport { useEffect, useMemo, useState } from 'react'\nimport { splitOrderReversal } from '../../model/commerce-dispute'\nimport { computeLifetimePurchaseCents } from '../../model/site-member-purchases'\n\nconst usd = (cents: number) => `$${(cents / 100).toFixed(2)}`\n\n/**\n * How many order documents this reads for one member.\n *\n * A CEILING, not a page size: the lifetime-purchase figure above the list is\n * summed from these rows, so every one it intends to count has to be held. A\n * server page would make that headline number the total of ten orders and\n * print it as a lifetime.\n */\nconst ORDER_CEILING = 100\n/** The same, for the subscriptions this member holds. */\nconst SUBSCRIPTION_CEILING = 25\n\n/**\n * The reversal suffix on one order row, split by the door the money left\n * through (AGL-1810). `refundedCents` carries a lost chargeback as well as a\n * refund (AGL-1787 puts both there deliberately), so rendering the whole\n * figure as \"refunded\" told the merchant they chose a reversal a bank took.\n * `computeLifetimePurchaseCents` keeps netting the total; only the label\n * splits.\n */\nconst reversalSuffix = (order: any) => {\n const { refundedCents, chargedBackCents } = splitOrderReversal(order)\n return (\n (refundedCents ? ` · refunded ${usd(refundedCents)}` : '') +\n (chargedBackCents ? ` · charged back ${usd(chargedBackCents)}` : '')\n )\n}\n\n/** Display order number: v1 sequential `#1042`, else a doc-id stub. */\nconst orderNumber = (order: any) =>\n order.number != null\n ? `#${order.number}`\n : `#${String(order.$id ?? '').slice(-6).toUpperCase()}`\n\nconst orderCreatedMs = (order: any) =>\n Number(order.createdAtMs ?? (order.createdAt?.seconds ?? 0) * 1000) || 0\n\n/**\n * What a site user bought (AGL-546): the lifetime purchase total, the order\n * history (the payment records — Stripe intent id and refunds included) and\n * storefront subscriptions — the `siteMember` zone of the account's drawer.\n *\n * Orders match by email (mirrors membership-account, AGL-294) and sort\n * client-side. The QUERY orders on the document name: `orderBy('createdAt')`\n * would want a composite index and, worse, would drop every order missing the\n * field, while an equality filter plus `orderBy(documentId())` rides the\n * automatic single-field index and can drop nothing — a document's name\n * cannot be absent. That makes the window total rather than a pseudo-random\n * hundred, which is what the newest-first sort below was quietly disguising.\n * `orders` reads are admin/editor-only in rules (AGL-502), so viewers get a\n * note instead of history.\n *\n * One section per element, as a fragment: the zone is `bare`, and the drawer's\n * own column spaces what it holds.\n */\nexport function SiteMemberPurchases(props: ConsoleSiteMemberZoneProps) {\n const { hostId, member } = props\n const firestore = useFirestore()\n const memberId = String(member.$id ?? '')\n const email = String(member['email'] ?? '')\n\n const { data: orderDocs, status: ordersStatus } = useFirestoreCollection<any>(\n () =>\n email\n ? query(\n collection(firestore, 'hosts', hostId, 'orders'),\n where('customerEmail', '==', email),\n orderBy(documentId()),\n // One document past the ceiling, so \"this member has more\" is a\n // fact. `length === 100` cannot tell a member with exactly a\n // hundred orders from one with a thousand, and the difference is\n // whether the lifetime figure above is a total or a floor.\n limit(ORDER_CEILING + 1),\n )\n : null,\n [firestore, hostId, email],\n { idField: '$id' },\n )\n const { rows: readOrders, truncated: ordersTruncated } = ceilingedWindow<any>(\n orderDocs,\n ORDER_CEILING,\n )\n const orders = useMemo(\n () =>\n [...readOrders].sort((a, b) => orderCreatedMs(b) - orderCreatedMs(a)),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [orderDocs],\n )\n const lifetimeCents = useMemo(\n () => computeLifetimePurchaseCents(orders),\n [orders],\n )\n /**\n * The order query failed AND nothing came back with it (AGL-1066).\n *\n * The copy this gates blames the reader's ROLE, which is the right\n * explanation for the denial this was written for — orders are\n * admin/editor-only in rules. It is the wrong explanation once a stale\n * SESSION can push a listen to `'error'` too, and flatly wrong when\n * `persistentLocalCache` is still serving the rows: telling someone their\n * role is insufficient while their orders sit right there is a support\n * ticket about permissions that was never about permissions.\n */\n const ordersUnreadable = ordersStatus === 'error' && orders.length === 0\n\n const { data: subscriptionDocs } = useFirestoreCollection<any>(\n () =>\n email\n ? query(\n collection(firestore, 'hosts', hostId, 'subscriptions'),\n where('customerEmail', '==', email),\n // The same decision as the orders above, for the same reason: a\n // subscription written without `createdAt` would be hidden by a\n // field ordering rather than mis-sorted by it.\n orderBy(documentId()),\n limit(SUBSCRIPTION_CEILING + 1),\n )\n : null,\n [firestore, hostId, email],\n { idField: '$id' },\n )\n const { rows: subscriptions, truncated: subscriptionsTruncated } =\n ceilingedWindow<any>(subscriptionDocs, SUBSCRIPTION_CEILING)\n // Product names for subscriptions, loaded only when any exist.\n const { data: productDocs } = useFirestoreCollection<any>(\n () =>\n email && (subscriptionDocs?.length ?? 0) > 0\n ? query(\n collection(firestore, 'hosts', hostId, 'products'),\n // A LOOKUP, not a list — but the same ordering decision, so the\n // hundred it reads is a reachable hundred rather than a sample,\n // and a product past it degrades a subscription's label to its\n // id rather than hiding the subscription.\n orderBy(documentId()),\n limit(100),\n )\n : null,\n [firestore, hostId, email, (subscriptionDocs?.length ?? 0) > 0],\n { idField: '$id' },\n )\n /*==========================================\n * BOTH LISTS PAGE IN MEMORY.\n *\n * Each ceiling is held whole, which is what lets the orders sort\n * newest-first and the lifetime figure sum over all of them. A server page\n * would have made both about ten rows instead of about this member.\n *=========================================*/\n const [orderPage, setOrderPage] = useState(0)\n const [orderPageSize, setOrderPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT)\n const visibleOrders = useMemo(\n () =>\n orders.slice(\n orderPage * orderPageSize,\n orderPage * orderPageSize + orderPageSize,\n ),\n [orders, orderPage, orderPageSize],\n )\n const [subscriptionPage, setSubscriptionPage] = useState(0)\n const [subscriptionPageSize, setSubscriptionPageSize] = useState(\n TABLE_PAGE_SIZE_DEFAULT,\n )\n const visibleSubscriptions = useMemo(\n () =>\n subscriptions.slice(\n subscriptionPage * subscriptionPageSize,\n subscriptionPage * subscriptionPageSize + subscriptionPageSize,\n ),\n [subscriptions, subscriptionPage, subscriptionPageSize],\n )\n /*\n * A different member starts at page one. The drawer is reused for whoever\n * is selected, so without this the next member opens three pages into a\n * history they may not have — which renders as an empty list and reads as\n * \"this person never bought anything\".\n */\n useEffect(() => {\n setOrderPage(0)\n setSubscriptionPage(0)\n }, [memberId])\n\n const productNames = useMemo(() => {\n const map: Record<string, string> = {}\n for (const product of productDocs ?? []) {\n map[product.$id] = product.name ?? product.$id\n }\n return map\n }, [productDocs])\n\n return (\n <>\n <Divider textAlign=\"left\">{'Lifetime purchases'}</Divider>\n <Typography variant=\"h6\">\n {/*\n \"at least\", or nothing at all. This figure is summed over the order\n WINDOW, so once the probe finds an order past the ceiling it is a\n lower bound — and a lower bound printed as a lifetime is the number\n a support agent quotes back to the customer.\n */}\n {ordersUnreadable\n ? '—'\n : `${ordersTruncated ? 'at least ' : ''}${usd(lifetimeCents)}`}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Charged order totals minus refunds; pending and canceled ' +\n 'orders excluded.'}\n {ordersTruncated\n ? ` Summed over the ${ORDER_CEILING} orders read here; this ` +\n 'member has more.'\n : ''}\n </Typography>\n\n <Divider textAlign=\"left\">{'Orders'}</Divider>\n {ordersUnreadable ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Order history needs the editor or admin role on this site.'}\n </Typography>\n ) : orders.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No orders yet.'}\n </Typography>\n ) : (\n visibleOrders.map((order: any) => (\n <Stack key={order.$id} spacing={0}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\" sx={{ flex: 1 }} noWrap>\n {`${orderNumber(order)} · ${usd(\n Number(order.totals?.totalCents ?? order.amountCents ?? 0) ||\n 0,\n )}`}\n </Typography>\n <Chip\n label={String(order.status ?? 'paid').replace('_', ' ')}\n size=\"small\"\n variant=\"outlined\"\n />\n </Stack>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {orderCreatedMs(order)\n ? new Date(orderCreatedMs(order)).toLocaleDateString()\n : '—'}\n {reversalSuffix(order)}\n </Typography>\n {order.paymentIntentId ? (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ fontFamily: 'monospace' }}\n noWrap\n >\n {order.paymentIntentId}\n </Typography>\n ) : null}\n </Stack>\n ))\n )}\n {ordersUnreadable || orders.length === 0 ? null : (\n <ListPagination\n page={orderPage}\n pageSize={orderPageSize}\n rowCount={visibleOrders.length}\n // The orders held here — a slice of rows already read, so the\n // total is exact for the window. The lifetime caption above is\n // where the window's own shortfall is stated.\n count={orders.length}\n onPageChange={setOrderPage}\n onPageSizeChange={setOrderPageSize}\n />\n )}\n\n {subscriptions.length > 0 ? (\n <>\n <Divider textAlign=\"left\">{'Subscriptions'}</Divider>\n {visibleSubscriptions.map((subscription: any) => (\n <Stack\n key={subscription.$id}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Stack sx={{ flex: 1, minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap>\n {/*\n * The amount (AGL-1732). This row named the product and the\n * renewal date and stopped, and no other console surface\n * carried the figure either — orders, analytics and the CSV\n * all read `orders`, which a subscription sale does not\n * create. Subscriptions written before that fix have no\n * `totals`, so the amount is omitted rather than shown as\n * $0.00.\n */}\n {productNames[subscription.productId] ??\n subscription.productId ??\n 'Subscription'}\n {subscription.totals?.totalCents != null\n ? ` · ${usd(Number(subscription.totals.totalCents) || 0)}${\n subscription.interval ? `/${subscription.interval}` : ''\n }`\n : ''}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {subscription.currentPeriodEndMs\n ? `Renews ${new Date(\n Number(subscription.currentPeriodEndMs),\n ).toLocaleDateString()}`\n : '—'}\n {/*\n * What this subscriber has actually paid, across every cycle\n * (AGL-1743). Omitted, not shown as 0, for subscriptions\n * whose cycles all predate `invoice.payment_succeeded`\n * being handled: those invoices are recoverable only from\n * Stripe.\n */}\n {Number(subscription.invoicesCount ?? 0) > 0\n ? ` · ${usd(Number(subscription.paidCents) || 0)} paid over ${\n Number(subscription.invoicesCount) === 1\n ? '1 invoice'\n : `${Number(subscription.invoicesCount)} invoices`\n }`\n : ''}\n </Typography>\n </Stack>\n <Chip\n label={String(subscription.status ?? 'active')}\n size=\"small\"\n variant=\"outlined\"\n color={subscription.status === 'active' ? 'success' : 'default'}\n />\n </Stack>\n ))}\n <ListPagination\n page={subscriptionPage}\n pageSize={subscriptionPageSize}\n rowCount={visibleSubscriptions.length}\n // This member's subscriptions, in full for the window.\n count={subscriptions.length}\n onPageChange={setSubscriptionPage}\n onPageSizeChange={setSubscriptionPageSize}\n />\n {subscriptionsTruncated ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Showing ${SUBSCRIPTION_CEILING} subscriptions; this ` +\n 'member has more.'}\n </Typography>\n ) : null}\n </>\n ) : null}\n </>\n )\n}\nSiteMemberPurchases.displayName = 'SiteMemberPurchases'\n\nexport default SiteMemberPurchases\n"],"names":["ListPagination","TABLE_PAGE_SIZE_DEFAULT","ceilingedWindow","useFirestore","useFirestoreCollection","Chip","Divider","Stack","Typography","collection","documentId","limit","orderBy","query","where","useEffect","useMemo","useState","splitOrderReversal","computeLifetimePurchaseCents","usd","cents","toFixed","ORDER_CEILING","SUBSCRIPTION_CEILING","reversalSuffix","order","refundedCents","chargedBackCents","orderNumber","number","String","$id","slice","toUpperCase","orderCreatedMs","Number","createdAtMs","createdAt","seconds","SiteMemberPurchases","props","member","hostId","firestore","memberId","email","data","orderDocs","status","ordersStatus","idField","rows","readOrders","truncated","ordersTruncated","orders","sort","a","b","lifetimeCents","ordersUnreadable","length","subscriptionDocs","subscriptions","subscriptionsTruncated","productDocs","orderPage","setOrderPage","orderPageSize","setOrderPageSize","visibleOrders","subscriptionPage","setSubscriptionPage","subscriptionPageSize","setSubscriptionPageSize","visibleSubscriptions","productNames","map","product","name","textAlign","variant","color","spacing","direction","sx","alignItems","flex","noWrap","totals","totalCents","amountCents","label","replace","size","Date","toLocaleDateString","paymentIntentId","fontFamily","page","pageSize","rowCount","count","onPageChange","onPageSizeChange","subscription","minWidth","productId","interval","currentPeriodEndMs","invoicesCount","paidCents","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;AAGA,SAASA,cAAc,QAAQ,4DAA2D;AAC1F,SAASC,uBAAuB,QAAQ,8CAA6C;AACrF,SACEC,eAAe,EACfC,YAAY,EACZC,sBAAsB,QACjB,iCAAgC;AACvC,SAASC,IAAI,EAAEC,OAAO,EAAEC,KAAK,EAAEC,UAAU,QAAQ,gBAAe;AAChE,SACEC,UAAU,EACVC,UAAU,EACVC,KAAK,EACLC,OAAO,EACPC,KAAK,EACLC,KAAK,QACA,qBAAoB;AAC3B,SAASC,SAAS,EAAEC,OAAO,EAAEC,QAAQ,QAAQ,QAAO;AACpD,SAASC,kBAAkB,QAAQ,kCAA8B;AACjE,SAASC,4BAA4B,QAAQ,uCAAmC;AAEhF,MAAMC,MAAM,CAACC,QAAkB,CAAC,CAAC,EAAE,AAACA,CAAAA,QAAQ,GAAE,EAAGC,OAAO,CAAC,IAAI;AAE7D;;;;;;;CAOC,GACD,MAAMC,gBAAgB;AACtB,uDAAuD,GACvD,MAAMC,uBAAuB;AAE7B;;;;;;;CAOC,GACD,MAAMC,iBAAiB,CAACC;IACtB,MAAM,EAAEC,aAAa,EAAEC,gBAAgB,EAAE,GAAGV,mBAAmBQ;IAC/D,OACE,AAACC,CAAAA,gBAAgB,CAAC,YAAY,EAAEP,IAAIO,gBAAgB,GAAG,EAAC,IACvDC,CAAAA,mBAAmB,CAAC,gBAAgB,EAAER,IAAIQ,mBAAmB,GAAG,EAAC;AAEtE;AAEA,qEAAqE,GACrE,MAAMC,cAAc,CAACH;QAGJA;WAFfA,MAAMI,MAAM,IAAI,OACZ,CAAC,CAAC,EAAEJ,MAAMI,MAAM,EAAE,GAClB,CAAC,CAAC,EAAEC,QAAOL,aAAAA,MAAMM,GAAG,YAATN,aAAa,IAAIO,KAAK,CAAC,CAAC,GAAGC,WAAW,IAAI;;AAE3D,MAAMC,iBAAiB,CAACT;QACfA;QAAsBA;WAA7BU,QAAOV,qBAAAA,MAAMW,WAAW,YAAjBX,qBAAqB,UAACA,mBAAAA,MAAMY,SAAS,qBAAfZ,iBAAiBa,OAAO,mBAAI,KAAK,SAAS;;AAEzE;;;;;;;;;;;;;;;;;CAiBC,GACD,OAAO,SAASC,oBAAoBC,KAAiC;QAG3CC,aACHA;IAHrB,MAAM,EAAEC,MAAM,EAAED,MAAM,EAAE,GAAGD;IAC3B,MAAMG,YAAYzC;IAClB,MAAM0C,WAAWd,QAAOW,cAAAA,OAAOV,GAAG,YAAVU,cAAc;IACtC,MAAMI,QAAQf,QAAOW,gBAAAA,MAAM,CAAC,QAAQ,YAAfA,gBAAmB;IAExC,MAAM,EAAEK,MAAMC,SAAS,EAAEC,QAAQC,YAAY,EAAE,GAAG9C,uBAChD,IACE0C,QACIjC,MACEJ,WAAWmC,WAAW,SAASD,QAAQ,WACvC7B,MAAM,iBAAiB,MAAMgC,QAC7BlC,QAAQF,eACR,gEAAgE;QAChE,6DAA6D;QAC7D,iEAAiE;QACjE,2DAA2D;QAC3DC,MAAMY,gBAAgB,MAExB,MACN;QAACqB;QAAWD;QAAQG;KAAM,EAC1B;QAAEK,SAAS;IAAM;IAEnB,MAAM,EAAEC,MAAMC,UAAU,EAAEC,WAAWC,eAAe,EAAE,GAAGrD,gBACvD8C,WACAzB;IAEF,MAAMiC,SAASxC,QACb,IACE;eAAIqC;SAAW,CAACI,IAAI,CAAC,CAACC,GAAGC,IAAMxB,eAAewB,KAAKxB,eAAeuB,KACpE,uDAAuD;IACvD;QAACV;KAAU;IAEb,MAAMY,gBAAgB5C,QACpB,IAAMG,6BAA6BqC,SACnC;QAACA;KAAO;IAEV;;;;;;;;;;GAUC,GACD,MAAMK,mBAAmBX,iBAAiB,WAAWM,OAAOM,MAAM,KAAK;IAEvE,MAAM,EAAEf,MAAMgB,gBAAgB,EAAE,GAAG3D,uBACjC,IACE0C,QACIjC,MACEJ,WAAWmC,WAAW,SAASD,QAAQ,kBACvC7B,MAAM,iBAAiB,MAAMgC,QAC7B,gEAAgE;QAChE,gEAAgE;QAChE,+CAA+C;QAC/ClC,QAAQF,eACRC,MAAMa,uBAAuB,MAE/B,MACN;QAACoB;QAAWD;QAAQG;KAAM,EAC1B;QAAEK,SAAS;IAAM;IAEnB,MAAM,EAAEC,MAAMY,aAAa,EAAEV,WAAWW,sBAAsB,EAAE,GAC9D/D,gBAAqB6D,kBAAkBvC;IACzC,+DAA+D;IAC/D,MAAM,EAAEuB,MAAMmB,WAAW,EAAE,GAAG9D,uBAC5B;;eACE0C,SAAS,SAACiB,oCAAAA,iBAAkBD,MAAM,mBAAI,KAAK,IACvCjD,MACEJ,WAAWmC,WAAW,SAASD,QAAQ,aACvC,gEAAgE;QAChE,gEAAgE;QAChE,+DAA+D;QAC/D,0CAA0C;QAC1C/B,QAAQF,eACRC,MAAM,QAER;OACN;QAACiC;QAAWD;QAAQG;QAAO,SAACiB,oCAAAA,iBAAkBD,MAAM,mBAAI,KAAK;KAAE,EAC/D;QAAEX,SAAS;IAAM;IAEnB;;;;;;6CAM2C,GAC3C,MAAM,CAACgB,WAAWC,aAAa,GAAGnD,SAAS;IAC3C,MAAM,CAACoD,eAAeC,iBAAiB,GAAGrD,SAAShB;IACnD,MAAMsE,gBAAgBvD,QACpB,IACEwC,OAAOvB,KAAK,CACVkC,YAAYE,eACZF,YAAYE,gBAAgBA,gBAEhC;QAACb;QAAQW;QAAWE;KAAc;IAEpC,MAAM,CAACG,kBAAkBC,oBAAoB,GAAGxD,SAAS;IACzD,MAAM,CAACyD,sBAAsBC,wBAAwB,GAAG1D,SACtDhB;IAEF,MAAM2E,uBAAuB5D,QAC3B,IACEgD,cAAc/B,KAAK,CACjBuC,mBAAmBE,sBACnBF,mBAAmBE,uBAAuBA,uBAE9C;QAACV;QAAeQ;QAAkBE;KAAqB;IAEzD;;;;;GAKC,GACD3D,UAAU;QACRqD,aAAa;QACbK,oBAAoB;IACtB,GAAG;QAAC5B;KAAS;IAEb,MAAMgC,eAAe7D,QAAQ;QAC3B,MAAM8D,MAA8B,CAAC;QACrC,KAAK,MAAMC,WAAWb,sBAAAA,cAAe,EAAE,CAAE;gBACpBa;YAAnBD,GAAG,CAACC,QAAQ/C,GAAG,CAAC,IAAG+C,gBAAAA,QAAQC,IAAI,YAAZD,gBAAgBA,QAAQ/C,GAAG;QAChD;QACA,OAAO8C;IACT,GAAG;QAACZ;KAAY;IAEhB,qBACE;;0BACE,KAAC5D;gBAAQ2E,WAAU;0BAAQ;;0BAC3B,KAACzE;gBAAW0E,SAAQ;0BAOjBrB,mBACG,MACA,GAAGN,kBAAkB,cAAc,KAAKnC,IAAIwC,gBAAgB;;0BAElE,MAACpD;gBAAW0E,SAAQ;gBAAUC,OAAM;;oBACjC,8DACC;oBACD5B,kBACG,CAAC,iBAAiB,EAAEhC,cAAc,wBAAwB,CAAC,GAC3D,qBACA;;;0BAGN,KAACjB;gBAAQ2E,WAAU;0BAAQ;;YAC1BpB,iCACC,KAACrD;gBAAW0E,SAAQ;gBAAQC,OAAM;0BAC/B;iBAED3B,OAAOM,MAAM,KAAK,kBACpB,KAACtD;gBAAW0E,SAAQ;gBAAQC,OAAM;0BAC/B;iBAGHZ,cAAcO,GAAG,CAAC,CAACpD;oBAKFA,aAKKA;oBALLA;qCAJf,MAACnB;oBAAsB6E,SAAS;;sCAC9B,MAAC7E;4BAAM8E,WAAU;4BAAMD,SAAS;4BAAGE,IAAI;gCAAEC,YAAY;4BAAS;;8CAC5D,KAAC/E;oCAAW0E,SAAQ;oCAAQI,IAAI;wCAAEE,MAAM;oCAAE;oCAAGC,MAAM;8CAChD,GAAG5D,YAAYH,OAAO,GAAG,EAAEN,IAC1BgB,QAAOV,iBAAAA,gBAAAA,MAAMgE,MAAM,qBAAZhE,cAAciE,UAAU,oBAAIjE,MAAMkE,WAAW,YAA7ClE,OAAiD,MACtD,IACD;;8CAEL,KAACrB;oCACCwF,OAAO9D,QAAOL,gBAAAA,MAAMuB,MAAM,YAAZvB,gBAAgB,QAAQoE,OAAO,CAAC,KAAK;oCACnDC,MAAK;oCACLb,SAAQ;;;;sCAGZ,MAAC1E;4BAAW0E,SAAQ;4BAAUC,OAAM;;gCACjChD,eAAeT,SACZ,IAAIsE,KAAK7D,eAAeT,QAAQuE,kBAAkB,KAClD;gCACHxE,eAAeC;;;wBAEjBA,MAAMwE,eAAe,iBACpB,KAAC1F;4BACC0E,SAAQ;4BACRC,OAAM;4BACNG,IAAI;gCAAEa,YAAY;4BAAY;4BAC9BV,MAAM;sCAEL/D,MAAMwE,eAAe;6BAEtB;;mBA7BMxE,MAAMM,GAAG;;YAiCxB6B,oBAAoBL,OAAOM,MAAM,KAAK,IAAI,qBACzC,KAAC9D;gBACCoG,MAAMjC;gBACNkC,UAAUhC;gBACViC,UAAU/B,cAAcT,MAAM;gBAC9B,8DAA8D;gBAC9D,+DAA+D;gBAC/D,8CAA8C;gBAC9CyC,OAAO/C,OAAOM,MAAM;gBACpB0C,cAAcpC;gBACdqC,kBAAkBnC;;YAIrBN,cAAcF,MAAM,GAAG,kBACtB;;kCACE,KAACxD;wBAAQ2E,WAAU;kCAAQ;;oBAC1BL,qBAAqBE,GAAG,CAAC,CAAC4B;4BAkBlB7B,MAAAA,sCAsBO6B,6BAUIA;4BA7BXA;6CApBP,MAACnG;4BAEC8E,WAAU;4BACVD,SAAS;4BACTE,IAAI;gCAAEC,YAAY;4BAAS;;8CAE3B,MAAChF;oCAAM+E,IAAI;wCAAEE,MAAM;wCAAGmB,UAAU;oCAAE;;sDAChC,MAACnG;4CAAW0E,SAAQ;4CAAQO,MAAM;;iDAU/BZ,QAAAA,uCAAAA,YAAY,CAAC6B,aAAaE,SAAS,CAAC,YAApC/B,uCACC6B,aAAaE,SAAS,YADvB/B,OAEC;gDACD6B,EAAAA,uBAAAA,aAAahB,MAAM,qBAAnBgB,qBAAqBf,UAAU,KAAI,OAChC,CAAC,GAAG,EAAEvE,IAAIgB,OAAOsE,aAAahB,MAAM,CAACC,UAAU,KAAK,KAClDe,aAAaG,QAAQ,GAAG,CAAC,CAAC,EAAEH,aAAaG,QAAQ,EAAE,GAAG,IACtD,GACF;;;sDAEN,MAACrG;4CAAW0E,SAAQ;4CAAUC,OAAM;;gDACjCuB,aAAaI,kBAAkB,GAC5B,CAAC,OAAO,EAAE,IAAId,KACZ5D,OAAOsE,aAAaI,kBAAkB,GACtCb,kBAAkB,IAAI,GACxB;gDAQH7D,QAAOsE,8BAAAA,aAAaK,aAAa,YAA1BL,8BAA8B,KAAK,IACvC,CAAC,GAAG,EAAEtF,IAAIgB,OAAOsE,aAAaM,SAAS,KAAK,GAAG,WAAW,EACxD5E,OAAOsE,aAAaK,aAAa,MAAM,IACnC,cACA,GAAG3E,OAAOsE,aAAaK,aAAa,EAAE,SAAS,CAAC,EACpD,GACF;;;;;8CAGR,KAAC1G;oCACCwF,OAAO9D,QAAO2E,uBAAAA,aAAazD,MAAM,YAAnByD,uBAAuB;oCACrCX,MAAK;oCACLb,SAAQ;oCACRC,OAAOuB,aAAazD,MAAM,KAAK,WAAW,YAAY;;;2BAnDnDyD,aAAa1E,GAAG;;kCAuDzB,KAAChC;wBACCoG,MAAM5B;wBACN6B,UAAU3B;wBACV4B,UAAU1B,qBAAqBd,MAAM;wBACrC,uDAAuD;wBACvDyC,OAAOvC,cAAcF,MAAM;wBAC3B0C,cAAc/B;wBACdgC,kBAAkB9B;;oBAEnBV,uCACC,KAACzD;wBAAW0E,SAAQ;wBAAUC,OAAM;kCACjC,CAAC,QAAQ,EAAE3D,qBAAqB,qBAAqB,CAAC,GACrD;yBAEF;;iBAEJ;;;AAGV;AACAgB,oBAAoByE,WAAW,GAAG;AAElC,eAAezE,oBAAmB"}
|
|
@@ -164,7 +164,7 @@ import { writeSiteWideChange } from "@aglyn/tenant-feature-instance/hooks/helper
|
|
|
164
164
|
}),
|
|
165
165
|
size: "small",
|
|
166
166
|
select: true,
|
|
167
|
-
helperText: '
|
|
167
|
+
helperText: 'Page rendered at /products/{slug} — design it in the ' + 'Besigner with the Product detail block and {{product.*}} tokens.',
|
|
168
168
|
children: [
|
|
169
169
|
/*#__PURE__*/ _jsx(MenuItem, {
|
|
170
170
|
value: "",
|
|
@@ -184,7 +184,7 @@ import { writeSiteWideChange } from "@aglyn/tenant-feature-instance/hooks/helper
|
|
|
184
184
|
}),
|
|
185
185
|
size: "small",
|
|
186
186
|
select: true,
|
|
187
|
-
helperText: '
|
|
187
|
+
helperText: 'Page rendered at /collections/{slug} — drop a Product grid ' + 'with source "A collection" and {{collection.*}} tokens.',
|
|
188
188
|
children: [
|
|
189
189
|
/*#__PURE__*/ _jsx(MenuItem, {
|
|
190
190
|
value: "",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/store-settings-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Button,\n FormControlLabel,\n MenuItem,\n Stack,\n Switch,\n TextField,\n Typography,\n} from '@mui/material'\nimport { doc } from 'firebase/firestore'\nimport { useCallback, useState } from 'react'\nimport { useFirestore, useUser } from '@aglyn/tenant-feature-instance'\nimport {\n useFirestoreDoc,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport { hostPublicOrigin, pluginDocsHelp } from '@aglyn/aglyn'\nimport { writeSiteWideChange } from '@aglyn/tenant-feature-instance/hooks/helpers/site-wide-change'\n\nexport interface StoreSettingsCardProps {\n hostId: string\n}\n\ninterface StoreSettings {\n /** Screen rendered for /products/{slug} with product tokens (AGL-292). */\n pdpScreenId?: string\n /** Screen rendered for /collections/{slug} (AGL-298). */\n collectionScreenId?: string\n currency?: string\n guestCheckout?: boolean\n termsUrl?: string\n receiptFooter?: string\n}\n\n/**\n * Store settings (AGL-295) on `hosts/{hostId}/settings/store`: the PDP\n * template screen (drives /products/{slug}), currency display, checkout\n * preferences, and receipt branding. Tax and shipping have their own\n * cards; store password/maintenance live in Site Protection.\n */\nexport function StoreSettingsCard(props: StoreSettingsCardProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const {\n data: store,\n status: storeStatus,\n /**\n * The settings doc this form is seeded from is unconfirmed by the server\n * (AGL-1358). `current` is `draft ?? {…store}` and the save writes all\n * six keys, so `merge: true` protects only the tax and shipping maps that\n * belong to other cards — never this card's own fields. Against a cached\n * read, changing the currency rewrites `guestCheckout`, and the two\n * template ids fall back to `|| null`, which is how a storefront's\n * product and collection URLs start returning 404.\n */\n fromCache: storeFromCache,\n } = useFirestoreDoc<any>(\n () => doc(firestore, 'hosts', hostId, 'settings', 'store'),\n [firestore, hostId],\n )\n const { data: host } = useFirestoreDoc<any>(\n () => doc(firestore, 'hosts', hostId),\n [firestore, hostId],\n )\n const [draft, setDraft] = useState<StoreSettings | null>(null)\n const current: StoreSettings = draft ?? {\n pdpScreenId: store?.pdpScreenId ?? '',\n collectionScreenId: store?.collectionScreenId ?? '',\n currency: store?.currency ?? 'USD',\n guestCheckout: store?.guestCheckout !== false,\n termsUrl: store?.termsUrl ?? '',\n receiptFooter: store?.receiptFooter ?? '',\n }\n const update = (patch: Partial<StoreSettings>) =>\n setDraft({ ...current, ...patch })\n\n /**\n * Google Merchant Center feed address (AGL-2249).\n *\n * `server/feed.ts` has served this since AGL-299, and the only place the\n * URL was ever written down was that file's own docblock — so the feature\n * was built, deployed, and undiscoverable. A feed nobody can find is a\n * shopping channel nobody switches on.\n *\n * Through `hostPublicOrigin` rather than a hand-built string, because a\n * store on a custom domain must be told ITS name: the feed's `<link>`\n * elements are built from the request host, so submitting the `.aglyn.app`\n * address for a site that serves on `shop.example.com` would put the wrong\n * domain on every item in Merchant Center.\n *\n * Undefined until the host doc lands, and undefined forever for a host with\n * neither a cname nor a subdomain — `hostPublicOrigin` returns undefined\n * rather than a half-built URL, and a copyable half-URL is worse than none.\n */\n const feedUrl = host\n ? (() => {\n const origin = hostPublicOrigin(host)\n return origin\n ? `${origin}/api/commerce/feed?hostId=${hostId}`\n : undefined\n })()\n : undefined\n\n // Host screen directory: {screenId: path} (AGL-292 template pick).\n const screenEntries = Object.entries(\n (host?.screens ?? {}) as Record<string, string>,\n ).sort(([, a], [, b]) => String(a).localeCompare(String(b)))\n\n const handleSave = useCallback(async () => {\n /**\n * Refuse a save whose seed the server never confirmed (AGL-1358).\n *\n * There is no create path to exempt, and that is deliberate rather than\n * an oversight: unlike a new row at a fresh uid, this writes a FIXED\n * document path, so the all-defaults `current` produced when the cache\n * has never seen this doc is not the harmless blank a fresh uid would\n * be. It would put `USD`, guest checkout on and two null template ids\n * over whatever the server really holds.\n *\n * The guard WRAPS the write — an early return is a shape you can keep\n * while losing the protection.\n */\n const verdict = await writeGuardedBySeed(\n {\n subject: 'store settings',\n unreadable: storeStatus === 'error',\n fromCache: storeFromCache,\n },\n async () => {\n // The product and collection templates, the currency and the\n // checkout rules render on the live store with no publish step, so\n // the save carries the site's cache drop with it.\n await writeSiteWideChange({\n firestore,\n user,\n hostId,\n write: (batch) =>\n batch.set(\n doc(firestore, 'hosts', hostId, 'settings', 'store'),\n {\n pdpScreenId: current.pdpScreenId || null,\n collectionScreenId: current.collectionScreenId || null,\n currency: current.currency ?? 'USD',\n guestCheckout: current.guestCheckout !== false,\n termsUrl: current.termsUrl || null,\n receiptFooter: current.receiptFooter || null,\n },\n { merge: true },\n ),\n })\n },\n )\n // Before `setDraft(null)`, so a refusal keeps every typed value on\n // screen. A save that silently does nothing sends the user back to\n // retype a form that will be refused again just as quietly.\n if (!verdict.ok) {\n return void enqueueSnackbar(verdict.message, {\n variant: 'warning',\n persist: false,\n })\n }\n setDraft(null)\n enqueueSnackbar('Store settings saved', {\n variant: 'success',\n persist: false,\n })\n }, [\n current,\n firestore,\n hostId,\n user,\n enqueueSnackbar,\n storeStatus,\n storeFromCache,\n ])\n\n return (\n <CardDisplay\n header={'Store settings'}\n help={pluginDocsHelp('commerce')}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1.5}>\n <TextField\n label=\"Product page template\"\n value={current.pdpScreenId ?? ''}\n onChange={(event) => update({ pdpScreenId: event.target.value })}\n size=\"small\"\n select\n helperText={\n 'Screen rendered at /products/{slug} — design it in the ' +\n 'besigner with the Product detail block and {{product.*}} tokens.'\n }\n >\n <MenuItem value=\"\">{'None (product URLs 404)'}</MenuItem>\n {screenEntries.map(([screenId, path]) => (\n <MenuItem key={screenId} value={screenId}>\n {`/${path === '/' ? '' : path}`}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n label=\"Collection page template\"\n value={current.collectionScreenId ?? ''}\n onChange={(event) =>\n update({ collectionScreenId: event.target.value })\n }\n size=\"small\"\n select\n helperText={\n 'Screen rendered at /collections/{slug} — drop a Product grid ' +\n 'with source \"A collection\" and {{collection.*}} tokens.'\n }\n >\n <MenuItem value=\"\">{'None (collection URLs 404)'}</MenuItem>\n {screenEntries.map(([screenId, path]) => (\n <MenuItem key={screenId} value={screenId}>\n {`/${path === '/' ? '' : path}`}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n label=\"Currency\"\n value={current.currency ?? 'USD'}\n onChange={(event) => update({ currency: event.target.value })}\n size=\"small\"\n select\n sx={{ maxWidth: 200 }}\n helperText=\"Charges settle in USD; more currencies are coming\"\n >\n <MenuItem value=\"USD\">{'USD ($)'}</MenuItem>\n </TextField>\n <FormControlLabel\n control={\n <Switch\n size=\"small\"\n checked={current.guestCheckout !== false}\n onChange={(event) =>\n update({ guestCheckout: event.target.checked })\n }\n />\n }\n label=\"Allow guest checkout (no account required)\"\n />\n <TextField\n label=\"Terms URL\"\n value={current.termsUrl ?? ''}\n onChange={(event) => update({ termsUrl: event.target.value })}\n size=\"small\"\n placeholder=\"/terms\"\n helperText=\"Linked from checkout when set\"\n />\n <TextField\n label=\"Receipt footer\"\n value={current.receiptFooter ?? ''}\n onChange={(event) => update({ receiptFooter: event.target.value })}\n size=\"small\"\n multiline\n minRows={2}\n placeholder=\"Thanks for supporting our shop!\"\n />\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Password protection and maintenance mode live under Site ' +\n 'Protection.'}\n </Typography>\n <Button\n variant=\"contained\"\n color=\"primary\"\n size=\"small\"\n disabled={!draft}\n onClick={handleSave}\n sx={{ alignSelf: 'flex-start' }}\n >\n {'Save store settings'}\n </Button>\n <Stack spacing={0.5}>\n <Typography variant=\"subtitle2\">{'Product feed'}</Typography>\n {feedUrl ? (\n <>\n <TextField\n label=\"Google Merchant Center feed URL\"\n value={feedUrl}\n size=\"small\"\n slotProps={{ input: { readOnly: true } }}\n onFocus={(event) => event.target.select()}\n helperText={\n 'Paste this into Merchant Center → Products → Feeds as a ' +\n 'scheduled fetch. It lists every active product with price ' +\n 'and availability, and refreshes itself.'\n }\n />\n <Button\n size=\"small\"\n sx={{ alignSelf: 'flex-start' }}\n onClick={() => {\n void navigator.clipboard?.writeText(feedUrl).then(() =>\n enqueueSnackbar('Feed URL copied', {\n variant: 'success',\n persist: false,\n }),\n )\n }}\n >\n {'Copy feed URL'}\n </Button>\n </>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Publish this site to a subdomain or custom domain to get its ' +\n 'Google Merchant Center feed URL.'}\n </Typography>\n )}\n </Stack>\n </Stack>\n </CardDisplay>\n )\n}\nStoreSettingsCard.displayName = 'StoreSettingsCard'\n\nexport default StoreSettingsCard\n"],"names":["CardDisplay","useSnackbar","Button","FormControlLabel","MenuItem","Stack","Switch","TextField","Typography","doc","useCallback","useState","useFirestore","useUser","useFirestoreDoc","writeGuardedBySeed","hostPublicOrigin","pluginDocsHelp","writeSiteWideChange","StoreSettingsCard","props","current","hostId","firestore","data","user","enqueueSnackbar","store","status","storeStatus","fromCache","storeFromCache","host","draft","setDraft","pdpScreenId","collectionScreenId","currency","guestCheckout","termsUrl","receiptFooter","update","patch","feedUrl","origin","undefined","screenEntries","Object","entries","screens","sort","a","b","String","localeCompare","handleSave","verdict","subject","unreadable","write","batch","set","merge","ok","message","variant","persist","header","help","contentGutterX","contentGutterY","spacing","label","value","onChange","event","target","size","select","helperText","map","screenId","path","sx","maxWidth","control","checked","placeholder","multiline","minRows","color","disabled","onClick","alignSelf","slotProps","input","readOnly","onFocus","navigator","clipboard","writeText","then","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/store-settings-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Button,\n FormControlLabel,\n MenuItem,\n Stack,\n Switch,\n TextField,\n Typography,\n} from '@mui/material'\nimport { doc } from 'firebase/firestore'\nimport { useCallback, useState } from 'react'\nimport { useFirestore, useUser } from '@aglyn/tenant-feature-instance'\nimport {\n useFirestoreDoc,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport { hostPublicOrigin, pluginDocsHelp } from '@aglyn/aglyn'\nimport { writeSiteWideChange } from '@aglyn/tenant-feature-instance/hooks/helpers/site-wide-change'\n\nexport interface StoreSettingsCardProps {\n hostId: string\n}\n\ninterface StoreSettings {\n /** Screen rendered for /products/{slug} with product tokens (AGL-292). */\n pdpScreenId?: string\n /** Screen rendered for /collections/{slug} (AGL-298). */\n collectionScreenId?: string\n currency?: string\n guestCheckout?: boolean\n termsUrl?: string\n receiptFooter?: string\n}\n\n/**\n * Store settings (AGL-295) on `hosts/{hostId}/settings/store`: the PDP\n * template screen (drives /products/{slug}), currency display, checkout\n * preferences, and receipt branding. Tax and shipping have their own\n * cards; store password/maintenance live in Site Protection.\n */\nexport function StoreSettingsCard(props: StoreSettingsCardProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const {\n data: store,\n status: storeStatus,\n /**\n * The settings doc this form is seeded from is unconfirmed by the server\n * (AGL-1358). `current` is `draft ?? {…store}` and the save writes all\n * six keys, so `merge: true` protects only the tax and shipping maps that\n * belong to other cards — never this card's own fields. Against a cached\n * read, changing the currency rewrites `guestCheckout`, and the two\n * template ids fall back to `|| null`, which is how a storefront's\n * product and collection URLs start returning 404.\n */\n fromCache: storeFromCache,\n } = useFirestoreDoc<any>(\n () => doc(firestore, 'hosts', hostId, 'settings', 'store'),\n [firestore, hostId],\n )\n const { data: host } = useFirestoreDoc<any>(\n () => doc(firestore, 'hosts', hostId),\n [firestore, hostId],\n )\n const [draft, setDraft] = useState<StoreSettings | null>(null)\n const current: StoreSettings = draft ?? {\n pdpScreenId: store?.pdpScreenId ?? '',\n collectionScreenId: store?.collectionScreenId ?? '',\n currency: store?.currency ?? 'USD',\n guestCheckout: store?.guestCheckout !== false,\n termsUrl: store?.termsUrl ?? '',\n receiptFooter: store?.receiptFooter ?? '',\n }\n const update = (patch: Partial<StoreSettings>) =>\n setDraft({ ...current, ...patch })\n\n /**\n * Google Merchant Center feed address (AGL-2249).\n *\n * `server/feed.ts` has served this since AGL-299, and the only place the\n * URL was ever written down was that file's own docblock — so the feature\n * was built, deployed, and undiscoverable. A feed nobody can find is a\n * shopping channel nobody switches on.\n *\n * Through `hostPublicOrigin` rather than a hand-built string, because a\n * store on a custom domain must be told ITS name: the feed's `<link>`\n * elements are built from the request host, so submitting the `.aglyn.app`\n * address for a site that serves on `shop.example.com` would put the wrong\n * domain on every item in Merchant Center.\n *\n * Undefined until the host doc lands, and undefined forever for a host with\n * neither a cname nor a subdomain — `hostPublicOrigin` returns undefined\n * rather than a half-built URL, and a copyable half-URL is worse than none.\n */\n const feedUrl = host\n ? (() => {\n const origin = hostPublicOrigin(host)\n return origin\n ? `${origin}/api/commerce/feed?hostId=${hostId}`\n : undefined\n })()\n : undefined\n\n // Host screen directory: {screenId: path} (AGL-292 template pick).\n const screenEntries = Object.entries(\n (host?.screens ?? {}) as Record<string, string>,\n ).sort(([, a], [, b]) => String(a).localeCompare(String(b)))\n\n const handleSave = useCallback(async () => {\n /**\n * Refuse a save whose seed the server never confirmed (AGL-1358).\n *\n * There is no create path to exempt, and that is deliberate rather than\n * an oversight: unlike a new row at a fresh uid, this writes a FIXED\n * document path, so the all-defaults `current` produced when the cache\n * has never seen this doc is not the harmless blank a fresh uid would\n * be. It would put `USD`, guest checkout on and two null template ids\n * over whatever the server really holds.\n *\n * The guard WRAPS the write — an early return is a shape you can keep\n * while losing the protection.\n */\n const verdict = await writeGuardedBySeed(\n {\n subject: 'store settings',\n unreadable: storeStatus === 'error',\n fromCache: storeFromCache,\n },\n async () => {\n // The product and collection templates, the currency and the\n // checkout rules render on the live store with no publish step, so\n // the save carries the site's cache drop with it.\n await writeSiteWideChange({\n firestore,\n user,\n hostId,\n write: (batch) =>\n batch.set(\n doc(firestore, 'hosts', hostId, 'settings', 'store'),\n {\n pdpScreenId: current.pdpScreenId || null,\n collectionScreenId: current.collectionScreenId || null,\n currency: current.currency ?? 'USD',\n guestCheckout: current.guestCheckout !== false,\n termsUrl: current.termsUrl || null,\n receiptFooter: current.receiptFooter || null,\n },\n { merge: true },\n ),\n })\n },\n )\n // Before `setDraft(null)`, so a refusal keeps every typed value on\n // screen. A save that silently does nothing sends the user back to\n // retype a form that will be refused again just as quietly.\n if (!verdict.ok) {\n return void enqueueSnackbar(verdict.message, {\n variant: 'warning',\n persist: false,\n })\n }\n setDraft(null)\n enqueueSnackbar('Store settings saved', {\n variant: 'success',\n persist: false,\n })\n }, [\n current,\n firestore,\n hostId,\n user,\n enqueueSnackbar,\n storeStatus,\n storeFromCache,\n ])\n\n return (\n <CardDisplay\n header={'Store settings'}\n help={pluginDocsHelp('commerce')}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1.5}>\n <TextField\n label=\"Product page template\"\n value={current.pdpScreenId ?? ''}\n onChange={(event) => update({ pdpScreenId: event.target.value })}\n size=\"small\"\n select\n helperText={\n 'Page rendered at /products/{slug} — design it in the ' +\n 'Besigner with the Product detail block and {{product.*}} tokens.'\n }\n >\n <MenuItem value=\"\">{'None (product URLs 404)'}</MenuItem>\n {screenEntries.map(([screenId, path]) => (\n <MenuItem key={screenId} value={screenId}>\n {`/${path === '/' ? '' : path}`}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n label=\"Collection page template\"\n value={current.collectionScreenId ?? ''}\n onChange={(event) =>\n update({ collectionScreenId: event.target.value })\n }\n size=\"small\"\n select\n helperText={\n 'Page rendered at /collections/{slug} — drop a Product grid ' +\n 'with source \"A collection\" and {{collection.*}} tokens.'\n }\n >\n <MenuItem value=\"\">{'None (collection URLs 404)'}</MenuItem>\n {screenEntries.map(([screenId, path]) => (\n <MenuItem key={screenId} value={screenId}>\n {`/${path === '/' ? '' : path}`}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n label=\"Currency\"\n value={current.currency ?? 'USD'}\n onChange={(event) => update({ currency: event.target.value })}\n size=\"small\"\n select\n sx={{ maxWidth: 200 }}\n helperText=\"Charges settle in USD; more currencies are coming\"\n >\n <MenuItem value=\"USD\">{'USD ($)'}</MenuItem>\n </TextField>\n <FormControlLabel\n control={\n <Switch\n size=\"small\"\n checked={current.guestCheckout !== false}\n onChange={(event) =>\n update({ guestCheckout: event.target.checked })\n }\n />\n }\n label=\"Allow guest checkout (no account required)\"\n />\n <TextField\n label=\"Terms URL\"\n value={current.termsUrl ?? ''}\n onChange={(event) => update({ termsUrl: event.target.value })}\n size=\"small\"\n placeholder=\"/terms\"\n helperText=\"Linked from checkout when set\"\n />\n <TextField\n label=\"Receipt footer\"\n value={current.receiptFooter ?? ''}\n onChange={(event) => update({ receiptFooter: event.target.value })}\n size=\"small\"\n multiline\n minRows={2}\n placeholder=\"Thanks for supporting our shop!\"\n />\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Password protection and maintenance mode live under Site ' +\n 'Protection.'}\n </Typography>\n <Button\n variant=\"contained\"\n color=\"primary\"\n size=\"small\"\n disabled={!draft}\n onClick={handleSave}\n sx={{ alignSelf: 'flex-start' }}\n >\n {'Save store settings'}\n </Button>\n <Stack spacing={0.5}>\n <Typography variant=\"subtitle2\">{'Product feed'}</Typography>\n {feedUrl ? (\n <>\n <TextField\n label=\"Google Merchant Center feed URL\"\n value={feedUrl}\n size=\"small\"\n slotProps={{ input: { readOnly: true } }}\n onFocus={(event) => event.target.select()}\n helperText={\n 'Paste this into Merchant Center → Products → Feeds as a ' +\n 'scheduled fetch. It lists every active product with price ' +\n 'and availability, and refreshes itself.'\n }\n />\n <Button\n size=\"small\"\n sx={{ alignSelf: 'flex-start' }}\n onClick={() => {\n void navigator.clipboard?.writeText(feedUrl).then(() =>\n enqueueSnackbar('Feed URL copied', {\n variant: 'success',\n persist: false,\n }),\n )\n }}\n >\n {'Copy feed URL'}\n </Button>\n </>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Publish this site to a subdomain or custom domain to get its ' +\n 'Google Merchant Center feed URL.'}\n </Typography>\n )}\n </Stack>\n </Stack>\n </CardDisplay>\n )\n}\nStoreSettingsCard.displayName = 'StoreSettingsCard'\n\nexport default StoreSettingsCard\n"],"names":["CardDisplay","useSnackbar","Button","FormControlLabel","MenuItem","Stack","Switch","TextField","Typography","doc","useCallback","useState","useFirestore","useUser","useFirestoreDoc","writeGuardedBySeed","hostPublicOrigin","pluginDocsHelp","writeSiteWideChange","StoreSettingsCard","props","current","hostId","firestore","data","user","enqueueSnackbar","store","status","storeStatus","fromCache","storeFromCache","host","draft","setDraft","pdpScreenId","collectionScreenId","currency","guestCheckout","termsUrl","receiptFooter","update","patch","feedUrl","origin","undefined","screenEntries","Object","entries","screens","sort","a","b","String","localeCompare","handleSave","verdict","subject","unreadable","write","batch","set","merge","ok","message","variant","persist","header","help","contentGutterX","contentGutterY","spacing","label","value","onChange","event","target","size","select","helperText","map","screenId","path","sx","maxWidth","control","checked","placeholder","multiline","minRows","color","disabled","onClick","alignSelf","slotProps","input","readOnly","onFocus","navigator","clipboard","writeText","then","displayName"],"mappings":"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"}
|
|
@@ -112,7 +112,7 @@ const usd = (cents)=>`$${(cents / 100).toFixed(2)}`;
|
|
|
112
112
|
from: selected.from,
|
|
113
113
|
to: selected.to
|
|
114
114
|
});
|
|
115
|
-
const response = await authorizedFetch(user, `/api/
|
|
115
|
+
const response = await authorizedFetch(user, `/api/commerce/tax-summary?${search.toString()}`);
|
|
116
116
|
if (!response.ok) throw new Error('tax summary failed');
|
|
117
117
|
return await response.json();
|
|
118
118
|
}, [
|