@mohasinac/appkit 4.62.0 → 4.62.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/.build-stamp.json +3 -3
- package/dist/_internal/server/features/checkout/actions.d.ts +80 -0
- package/dist/_internal/server/features/checkout/actions.js +542 -26
- package/dist/_internal/server/features/invoicing/allocate.d.ts +63 -0
- package/dist/_internal/server/features/invoicing/allocate.js +162 -0
- package/dist/_internal/server/features/invoicing/credit-note.d.ts +57 -0
- package/dist/_internal/server/features/invoicing/credit-note.js +126 -0
- package/dist/_internal/server/features/invoicing/gstr1.d.ts +74 -0
- package/dist/_internal/server/features/invoicing/gstr1.js +251 -0
- package/dist/_internal/server/features/invoicing/issue.d.ts +96 -0
- package/dist/_internal/server/features/invoicing/issue.js +202 -0
- package/dist/_internal/server/features/orders/actions.d.ts +8 -1
- package/dist/_internal/server/features/orders/actions.js +81 -8
- package/dist/_internal/server/features/orders/adapters.js +33 -0
- package/dist/_internal/server/features/orders/cancellation-refund.d.ts +15 -9
- package/dist/_internal/server/features/orders/cancellation-refund.js +15 -9
- package/dist/_internal/server/features/points/award.js +46 -4
- package/dist/_internal/server/features/refunds/actions.js +26 -0
- package/dist/_internal/server/features/shipping/courier-block.d.ts +69 -0
- package/dist/_internal/server/features/shipping/courier-block.js +239 -0
- package/dist/_internal/server/features/site-settings/adapters.d.ts +1 -1
- package/dist/_internal/server/features/site-settings/adapters.js +15 -0
- package/dist/_internal/server/features/stores/adapters.d.ts +2 -2
- package/dist/_internal/server/features/stores/adapters.js +51 -0
- package/dist/_internal/server/functions/scheduled.js +38 -1
- package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +9 -2
- package/dist/_internal/server/jobs/core/dailyStatusDigest.d.ts +4 -0
- package/dist/_internal/server/jobs/core/dailyStatusDigest.js +19 -2
- package/dist/_internal/server/jobs/core/index.d.ts +2 -1
- package/dist/_internal/server/jobs/core/index.js +2 -1
- package/dist/_internal/server/jobs/core/onJobCreated.d.ts +30 -7
- package/dist/_internal/server/jobs/core/onJobCreated.js +78 -45
- package/dist/_internal/server/jobs/core/onOrderStatusChange.d.ts +7 -0
- package/dist/_internal/server/jobs/core/onOrderStatusChange.js +62 -0
- package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +33 -0
- package/dist/_internal/server/jobs/core/runJobOnce.d.ts +48 -0
- package/dist/_internal/server/jobs/core/runJobOnce.js +195 -0
- package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +10 -2
- package/dist/_internal/server/jobs/handlers/onOrderStatusChange.js +17 -0
- package/dist/_internal/server/notifications/channel-health.d.ts +26 -3
- package/dist/_internal/shared/actions/action-registry.js +28 -0
- package/dist/_internal/shared/checkout/coupon-discount.d.ts +43 -1
- package/dist/_internal/shared/checkout/coupon-discount.js +26 -1
- package/dist/_internal/shared/checkout/order-math.d.ts +145 -0
- package/dist/_internal/shared/checkout/order-math.js +173 -0
- package/dist/_internal/shared/checkout/refundable-base.d.ts +25 -0
- package/dist/_internal/shared/checkout/refundable-base.js +13 -1
- package/dist/_internal/shared/checkout/split.d.ts +87 -0
- package/dist/_internal/shared/checkout/split.js +100 -0
- package/dist/_internal/shared/content/disclaimers.d.ts +88 -0
- package/dist/_internal/shared/content/disclaimers.js +217 -0
- package/dist/_internal/shared/content/published-values.d.ts +88 -0
- package/dist/_internal/shared/content/published-values.js +198 -0
- package/dist/_internal/shared/features/admin/integrations.d.ts +109 -0
- package/dist/_internal/shared/features/admin/integrations.js +253 -0
- package/dist/_internal/shared/features/orders/dispute-reasons.d.ts +113 -0
- package/dist/_internal/shared/features/orders/dispute-reasons.js +181 -0
- package/dist/_internal/shared/features/orders/refund-copy.d.ts +1 -1
- package/dist/_internal/shared/features/orders/refund-copy.js +18 -1
- package/dist/_internal/shared/features/points/accepts.d.ts +36 -0
- package/dist/_internal/shared/features/points/accepts.js +43 -0
- package/dist/_internal/shared/features/shipping/shared-purchase.d.ts +91 -0
- package/dist/_internal/shared/features/shipping/shared-purchase.js +108 -0
- package/dist/_internal/shared/features/site-settings/types.d.ts +11 -0
- package/dist/_internal/shared/fees/calculator.d.ts +33 -1
- package/dist/_internal/shared/fees/calculator.js +32 -2
- package/dist/client.d.ts +11 -0
- package/dist/client.js +30 -0
- package/dist/constants/api-endpoints.d.ts +42 -0
- package/dist/constants/api-endpoints.js +14 -0
- package/dist/constants/field-names.d.ts +1 -0
- package/dist/constants/field-names.js +5 -0
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/shipping.d.ts +45 -0
- package/dist/contracts/shipping.js +15 -0
- package/dist/features/about/components/AboutView.js +1 -1
- package/dist/features/account/components/BuyerAuctionsGuideView.js +4 -1
- package/dist/features/account/components/UserOrdersView.js +19 -33
- package/dist/features/admin/components/AdminCouponEditorView.js +17 -0
- package/dist/features/admin/components/AdminInvoicingView.d.ts +2 -0
- package/dist/features/admin/components/AdminInvoicingView.js +65 -0
- package/dist/features/admin/components/AdminOrderEditorView.d.ts +18 -1
- package/dist/features/admin/components/AdminOrderEditorView.js +11 -2
- package/dist/features/admin/components/AdminOrdersGuideView.js +23 -2
- package/dist/features/admin/components/AdminOrdersView.js +67 -5
- package/dist/features/admin/components/OwnerQuickActions.js +24 -2
- package/dist/features/admin/constants/filter-tabs.d.ts +6 -0
- package/dist/features/admin/constants/filter-tabs.js +6 -0
- package/dist/features/admin/constants/site-settings-tabs.d.ts +1 -1
- package/dist/features/admin/hooks/useAdminListingData.js +16 -0
- package/dist/features/admin/schemas/admin-user-form.d.ts +2 -2
- package/dist/features/admin/schemas/firestore.d.ts +30 -0
- package/dist/features/admin/schemas/firestore.js +5 -0
- package/dist/features/audit-log/schemas/firestore.d.ts +30 -0
- package/dist/features/audit-log/schemas/firestore.js +15 -0
- package/dist/features/auth/permissions/constants.js +13 -0
- package/dist/features/cart/components/CartPriceBreakdown.d.ts +13 -0
- package/dist/features/cart/components/CartPriceBreakdown.js +6 -2
- package/dist/features/cart/components/PointsTenderPicker.d.ts +61 -0
- package/dist/features/cart/components/PointsTenderPicker.js +15 -0
- package/dist/features/cart/components/StoreAddonsPicker.js +2 -1
- package/dist/features/cart/components/index.d.ts +2 -0
- package/dist/features/cart/components/index.js +1 -0
- package/dist/features/cart/repository/cart.repository.d.ts +19 -0
- package/dist/features/cart/repository/cart.repository.js +27 -0
- package/dist/features/cart/schemas/firestore.d.ts +92 -0
- package/dist/features/contact/email.d.ts +27 -10
- package/dist/features/contact/email.js +20 -3
- package/dist/features/dispute-reasons/repository/dispute-reasons.repository.d.ts +38 -0
- package/dist/features/dispute-reasons/repository/dispute-reasons.repository.js +80 -0
- package/dist/features/dispute-reasons/schemas/firestore.d.ts +64 -0
- package/dist/features/dispute-reasons/schemas/firestore.js +40 -0
- package/dist/features/faq/components/FAQAccordion.d.ts +13 -1
- package/dist/features/faq/components/FAQAccordion.js +29 -4
- package/dist/features/invoicing/schemas/firestore.d.ts +112 -0
- package/dist/features/invoicing/schemas/firestore.js +45 -0
- package/dist/features/invoicing/utils/financial-year.d.ts +43 -0
- package/dist/features/invoicing/utils/financial-year.js +53 -0
- package/dist/features/jobs/actions/enqueue-job.js +18 -1
- package/dist/features/jobs/repository/jobs.repository.d.ts +62 -1
- package/dist/features/jobs/repository/jobs.repository.js +84 -1
- package/dist/features/jobs/schemas/firestore.d.ts +80 -0
- package/dist/features/jobs/schemas/firestore.js +37 -0
- package/dist/features/lottery/schemas/zod.d.ts +6 -6
- package/dist/features/media/MediaImage.js +22 -1
- package/dist/features/media/upload/ImageUpload.js +1 -1
- package/dist/features/obligations/constants/policies.js +24 -1
- package/dist/features/orders/components/OrderPaymentSummary.js +10 -1
- package/dist/features/orders/components/OrderProvenanceCard.d.ts +8 -0
- package/dist/features/orders/components/OrderProvenanceCard.js +185 -0
- package/dist/features/orders/components/RaiseDisputeForm.d.ts +14 -0
- package/dist/features/orders/components/RaiseDisputeForm.js +111 -0
- package/dist/features/orders/components/RefundRequestView.js +19 -1
- package/dist/features/orders/components/index.d.ts +4 -0
- package/dist/features/orders/components/index.js +2 -0
- package/dist/features/orders/constants/order-lane-tabs.d.ts +69 -0
- package/dist/features/orders/constants/order-lane-tabs.js +88 -0
- package/dist/features/orders/repository/orders.repository.d.ts +57 -0
- package/dist/features/orders/repository/orders.repository.js +145 -0
- package/dist/features/orders/schemas/firestore.d.ts +196 -1
- package/dist/features/orders/schemas/index.d.ts +54 -6
- package/dist/features/orders/schemas/index.js +12 -0
- package/dist/features/orders/types/index.d.ts +26 -0
- package/dist/features/orders/utils/courier-block.d.ts +25 -0
- package/dist/features/orders/utils/courier-block.js +23 -0
- package/dist/features/payment-cases/schemas/firestore.d.ts +26 -1
- package/dist/features/payment-cases/schemas/firestore.js +1 -0
- package/dist/features/products/components/PrizeDrawDetailPageView.js +2 -2
- package/dist/features/products/components/ProductDetailPageView.d.ts +17 -1
- package/dist/features/products/components/ProductDetailPageView.js +2 -2
- package/dist/features/products/schemas/product-features.validators.d.ts +10 -10
- package/dist/features/promotions/actions/coupon-stacking.d.ts +30 -3
- package/dist/features/promotions/actions/coupon-stacking.js +21 -2
- package/dist/features/promotions/components/CouponCard.js +13 -0
- package/dist/features/promotions/constants/coupon-help.d.ts +2 -2
- package/dist/features/promotions/constants/coupon-help.js +9 -1
- package/dist/features/promotions/schemas/firestore.d.ts +24 -0
- package/dist/features/promotions/schemas/firestore.js +4 -0
- package/dist/features/promotions/types/index.d.ts +19 -1
- package/dist/features/scams/components/ScamProfileView.js +2 -2
- package/dist/features/scams/components/ScamRegistryView.js +2 -2
- package/dist/features/search/action-index/tab-entries.js +1 -1
- package/dist/features/seller/actions/seller-actions.js +33 -3
- package/dist/features/seller/components/SellerOrdersView.js +129 -27
- package/dist/features/seller/hooks/useSellerListingData.js +25 -0
- package/dist/features/shipments/schemas/validation.d.ts +4 -4
- package/dist/features/shipping-boxes/repository/shipping-boxes.repository.d.ts +43 -0
- package/dist/features/shipping-boxes/repository/shipping-boxes.repository.js +90 -0
- package/dist/features/shipping-boxes/schemas/firestore.d.ts +82 -0
- package/dist/features/shipping-boxes/schemas/firestore.js +53 -0
- package/dist/features/store-extensions/schemas/report-create-form.d.ts +2 -2
- package/dist/features/store-extensions/schemas/shipping-config-form.d.ts +2 -2
- package/dist/features/stores/components/StoreOrdersGuideView.js +6 -1
- package/dist/features/stores/schemas/firestore.d.ts +71 -0
- package/dist/features/stores/types/index.d.ts +19 -0
- package/dist/features/support/schemas/index.d.ts +6 -6
- package/dist/features/wishlist/hooks/useWishlistWithGuest.d.ts +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +14 -1
- package/dist/next/routing/route-map.d.ts +20 -0
- package/dist/next/routing/route-map.js +10 -0
- package/dist/providers/shipping-shiprocket/index.d.ts +27 -1
- package/dist/providers/shipping-shiprocket/index.js +84 -1
- package/dist/schemas/registry.d.ts +47 -18
- package/dist/schemas/sieve.d.ts +2 -2
- package/dist/server-entry.d.ts +2 -2
- package/dist/server-entry.js +8 -1
- package/dist/server.d.ts +10 -1
- package/dist/server.js +39 -1
- package/dist/styles.css +64 -80
- package/dist/tailwind-utilities.css +1 -1
- package/dist/tokens/themes/default-dark.js +1 -0
- package/dist/tokens/themes/default-light.js +1 -0
- package/dist/tokens/themes/required.d.ts +1 -1
- package/dist/tokens/themes/required.js +1 -0
- package/dist/tokens/tokens.css +15 -0
- package/dist/ui/components/Disclaimer.d.ts +19 -0
- package/dist/ui/components/Disclaimer.js +74 -0
- package/dist/ui/components/tabs-overflow.js +64 -11
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.js +6 -0
- package/dist/ui/rich-text/RichText.style.css +48 -79
- package/package.json +1 -1
- package/scripts/seed-cli.mjs +1831 -1822
|
@@ -129,6 +129,25 @@ export declare class CartRepository extends BaseRepository<CartDocument> {
|
|
|
129
129
|
* and keeping both shapes around invites readers that handle only one.
|
|
130
130
|
*/
|
|
131
131
|
setStoreAddons(userId: string, storeId: string, addons: CartStoreAddons | null): Promise<void>;
|
|
132
|
+
/**
|
|
133
|
+
* Opt the cart in or out of paying with points (D46/D47, decision 24).
|
|
134
|
+
*
|
|
135
|
+
* 🛑 A BOOLEAN, and the schema deliberately has no numeric sibling. Opt in
|
|
136
|
+
* and the server applies as many points as the 50% cap allows; whatever
|
|
137
|
+
* cannot be applied stays in the balance for a later order. A client-supplied
|
|
138
|
+
* amount is a client-supplied discount, which is the whole reason
|
|
139
|
+
* `storeAddons` is the source of truth for add-on fees rather than a request
|
|
140
|
+
* body.
|
|
141
|
+
*
|
|
142
|
+
* Written with the same `set(..., { merge: true })` as its neighbours so a
|
|
143
|
+
* concurrent add-on or selection write is not clobbered.
|
|
144
|
+
*
|
|
145
|
+
* 🛑 Opting OUT deletes the field rather than writing `false` — absent and
|
|
146
|
+
* `false` mean the same thing to every reader (`cart.useAllPoints === true`),
|
|
147
|
+
* and keeping both shapes invites a reader that handles only one. Same
|
|
148
|
+
* reasoning as `setStoreAddons` directly above.
|
|
149
|
+
*/
|
|
150
|
+
setUseAllPoints(userId: string, useAllPoints: boolean): Promise<void>;
|
|
132
151
|
/**
|
|
133
152
|
* Cloud Functions: return refs of stale carts not updated within TTL.
|
|
134
153
|
*/
|
|
@@ -590,6 +590,33 @@ export class CartRepository extends BaseRepository {
|
|
|
590
590
|
updatedAt: new Date(),
|
|
591
591
|
}, { merge: true });
|
|
592
592
|
}
|
|
593
|
+
/**
|
|
594
|
+
* Opt the cart in or out of paying with points (D46/D47, decision 24).
|
|
595
|
+
*
|
|
596
|
+
* 🛑 A BOOLEAN, and the schema deliberately has no numeric sibling. Opt in
|
|
597
|
+
* and the server applies as many points as the 50% cap allows; whatever
|
|
598
|
+
* cannot be applied stays in the balance for a later order. A client-supplied
|
|
599
|
+
* amount is a client-supplied discount, which is the whole reason
|
|
600
|
+
* `storeAddons` is the source of truth for add-on fees rather than a request
|
|
601
|
+
* body.
|
|
602
|
+
*
|
|
603
|
+
* Written with the same `set(..., { merge: true })` as its neighbours so a
|
|
604
|
+
* concurrent add-on or selection write is not clobbered.
|
|
605
|
+
*
|
|
606
|
+
* 🛑 Opting OUT deletes the field rather than writing `false` — absent and
|
|
607
|
+
* `false` mean the same thing to every reader (`cart.useAllPoints === true`),
|
|
608
|
+
* and keeping both shapes invites a reader that handles only one. Same
|
|
609
|
+
* reasoning as `setStoreAddons` directly above.
|
|
610
|
+
*/
|
|
611
|
+
async setUseAllPoints(userId, useAllPoints) {
|
|
612
|
+
await this.db
|
|
613
|
+
.collection(this.collection)
|
|
614
|
+
.doc(userId)
|
|
615
|
+
.set({
|
|
616
|
+
useAllPoints: useAllPoints ? true : FieldValue.delete(),
|
|
617
|
+
updatedAt: new Date(),
|
|
618
|
+
}, { merge: true });
|
|
619
|
+
}
|
|
593
620
|
/*
|
|
594
621
|
* `pruneStoreAddons(userId, keepStoreIds)` used to live here. It had zero
|
|
595
622
|
* callers from the day it was written, and `pruneForItems` now does the same
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { BaseDocument } from "../../../_internal/shared/types/base-document";
|
|
8
8
|
import type { ListingType } from "../../products/types/index";
|
|
9
|
+
import type { CouponType } from "../../promotions/types/index";
|
|
9
10
|
/**
|
|
10
11
|
* What KIND of thing one cart line represents.
|
|
11
12
|
*
|
|
@@ -57,6 +58,33 @@ export interface CartLineMember {
|
|
|
57
58
|
gstRate?: 0 | 5 | 12 | 18 | 28;
|
|
58
59
|
hsnCode?: string;
|
|
59
60
|
listingType?: ListingType;
|
|
61
|
+
/**
|
|
62
|
+
* How the coordinator expressed this member's share of the line.
|
|
63
|
+
*
|
|
64
|
+
* Absent means `"amount"` — every line written before splits existed, so no
|
|
65
|
+
* migration and no behaviour change until a coordinator picks percentages.
|
|
66
|
+
*
|
|
67
|
+
* 🛑 The PERCENTAGE is the stored fact, not the resolved rupees. Percentages
|
|
68
|
+
* apply to the LANDED cost (goods + shipping + fees), so a member's figure
|
|
69
|
+
* MOVES when shipping is re-quoted — storing the computed amount instead
|
|
70
|
+
* would freeze a number that no longer matches the agreed share. The
|
|
71
|
+
* computed figures are snapshotted at COMMIT, which is where the
|
|
72
|
+
* coordinator's agreement actually becomes binding (D61).
|
|
73
|
+
*
|
|
74
|
+
* `resolveSplit` (`_internal/shared/checkout/split.ts`) is the only thing
|
|
75
|
+
* that turns these into money.
|
|
76
|
+
*/
|
|
77
|
+
splitMode?: "amount" | "percent";
|
|
78
|
+
/** 0–100, read only when `splitMode === "percent"`. */
|
|
79
|
+
percent?: number;
|
|
80
|
+
/**
|
|
81
|
+
* The member's agreed contribution in decimal rupees, when typed directly.
|
|
82
|
+
*
|
|
83
|
+
* 🛑 NOT the same as `unitPrice`. `unitPrice` is what the ITEM costs;
|
|
84
|
+
* this is what this PERSON agreed to put in, and the two differ by design
|
|
85
|
+
* the moment a split is uneven or the total exceeds the landed cost.
|
|
86
|
+
*/
|
|
87
|
+
contribution?: number;
|
|
60
88
|
}
|
|
61
89
|
export interface CartItemDocument {
|
|
62
90
|
itemId: string;
|
|
@@ -183,6 +211,26 @@ export interface CartItemDocument {
|
|
|
183
211
|
chosenShippingProviderId?: string;
|
|
184
212
|
/** Fee in decimal rupees for the chosen provider, snapshotted at cart time. */
|
|
185
213
|
chosenShippingFee?: number;
|
|
214
|
+
/**
|
|
215
|
+
* ── S5a: which address THIS line ships to ────────────────────────────────
|
|
216
|
+
*
|
|
217
|
+
* One payer, N destinations. Absent means "the order's own shipping
|
|
218
|
+
* address", which is every cart that predates this and every ordinary
|
|
219
|
+
* single-destination order — so nothing changes for them.
|
|
220
|
+
*
|
|
221
|
+
* 🛑 An `addresses/{id}` REFERENCE, never a copied address. Copying would
|
|
222
|
+
* put a name, a phone number and a street address onto the cart document,
|
|
223
|
+
* which is a PII store with no encryption, no `*_PII_FIELDS` entry and no
|
|
224
|
+
* retention story — and carts are swept by a TTL job that is not written to
|
|
225
|
+
* handle personal data. The id resolves server-side at checkout, where the
|
|
226
|
+
* address is already being read and decrypted.
|
|
227
|
+
*
|
|
228
|
+
* 🛑 It must belong to the BUYER. An arbitrary `addresses` id would let a
|
|
229
|
+
* caller address a parcel to any stored address on the platform, which is
|
|
230
|
+
* both a delivery hijack and a way to read whether an id exists. Checked
|
|
231
|
+
* server-side at the write, not trusted from the client.
|
|
232
|
+
*/
|
|
233
|
+
destinationAddressId?: string;
|
|
186
234
|
addedAt: Date;
|
|
187
235
|
updatedAt: Date;
|
|
188
236
|
}
|
|
@@ -196,6 +244,28 @@ export interface CartAppliedCoupon {
|
|
|
196
244
|
storeId?: string;
|
|
197
245
|
/** Item IDs (CartItemDocument.itemId) this coupon was calculated against */
|
|
198
246
|
applicableItemIds?: string[];
|
|
247
|
+
/**
|
|
248
|
+
* The coupon's type, snapshotted at apply time (D51).
|
|
249
|
+
*
|
|
250
|
+
* 🛑 Needed because `detectCouponConflict` buckets on TYPE, not scope: a
|
|
251
|
+
* `points_earn` coupon is admin-scoped like every platform coupon but
|
|
252
|
+
* competes for its own slot, so without this field on the stored entry the
|
|
253
|
+
* detector could not tell a points coupon already in the cart from an
|
|
254
|
+
* ordinary platform one, and `RIPLI05` would block every other admin coupon
|
|
255
|
+
* — the exact defect the third bucket exists to prevent.
|
|
256
|
+
*
|
|
257
|
+
* Absent means "a discount coupon", which is what every cart written before
|
|
258
|
+
* this field holds, and is the reading that preserves the previous
|
|
259
|
+
* behaviour exactly.
|
|
260
|
+
*
|
|
261
|
+
* 🛑 Snapshotted deliberately, unlike the restrictions: a coupon's TYPE
|
|
262
|
+
* decides which slot it occupies, so re-reading it live would mean an admin
|
|
263
|
+
* editing a coupon's type could retroactively make a live cart's
|
|
264
|
+
* combination illegal. The money is still re-validated against the live
|
|
265
|
+
* coupon at placement (`revalidateAppliedCoupons`), which is where a changed
|
|
266
|
+
* discount has to be caught.
|
|
267
|
+
*/
|
|
268
|
+
type?: CouponType;
|
|
199
269
|
}
|
|
200
270
|
/**
|
|
201
271
|
* Paid add-ons the buyer opted into, for ONE store.
|
|
@@ -239,6 +309,28 @@ export interface CartDocument extends BaseDocument {
|
|
|
239
309
|
* rather than a flag someone has to remember to check.
|
|
240
310
|
*/
|
|
241
311
|
storeAddons?: Record<string, CartStoreAddons>;
|
|
312
|
+
/**
|
|
313
|
+
* The buyer has opted to spend points on this checkout.
|
|
314
|
+
*
|
|
315
|
+
* ## 🛑 A BOOLEAN, and the amount is never stored or sent
|
|
316
|
+
*
|
|
317
|
+
* Owner's rule: *"if use point it is all and then the balance is returned if
|
|
318
|
+
* excess"* — one opt-in, all-or-nothing. Ticked, the checkout applies as many
|
|
319
|
+
* points as the 50% cap allows; whatever cannot be applied stays in the
|
|
320
|
+
* balance. The buyer never types a number.
|
|
321
|
+
*
|
|
322
|
+
* So this is the ONLY thing the client may say about points. The amount is
|
|
323
|
+
* derived server-side from the live balance and
|
|
324
|
+
* `POINTS_MAX_REDEMPTION_PERCENT` at the moment the order is priced — the
|
|
325
|
+
* same rule that makes `storeAddons` above the source of truth rather than a
|
|
326
|
+
* request body, and for the same reason: a client-supplied money figure is a
|
|
327
|
+
* client-supplied discount.
|
|
328
|
+
*
|
|
329
|
+
* 🛑 This replaces a deleted `pointsToRedeem?: number` on the checkout input,
|
|
330
|
+
* whose handler debited the buyer AFTER the gateway had captured full price
|
|
331
|
+
* (D46). A number was the wrong shape as well as the wrong place.
|
|
332
|
+
*/
|
|
333
|
+
useAllPoints?: boolean;
|
|
242
334
|
}
|
|
243
335
|
export declare const CART_COLLECTION: "carts";
|
|
244
336
|
export declare const CART_INDEXED_FIELDS: string[];
|
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
import "server-only";
|
|
2
2
|
import type { SendGuardContext } from "../../_internal/shared/features/messaging/config";
|
|
3
3
|
import type { JsonValue } from "@mohasinac/appkit";
|
|
4
|
-
type
|
|
5
|
-
|
|
6
|
-
subject: string;
|
|
4
|
+
import type { EmailOptions } from "../../contracts/email";
|
|
5
|
+
type SendEmailOptions = Omit<EmailOptions, "html"> & {
|
|
7
6
|
html?: string;
|
|
8
|
-
text?: string;
|
|
9
|
-
from?: string;
|
|
10
|
-
cc?: string | string[];
|
|
11
|
-
replyTo?: string;
|
|
12
7
|
};
|
|
13
8
|
/**
|
|
14
9
|
* Everything `sendEmail` needs beyond the message itself.
|
|
@@ -22,9 +17,9 @@ type SendEmailOptions = Record<string, JsonValue> & {
|
|
|
22
17
|
* (CLAUDE.md Rule #22: make the wrong thing unrepresentable).
|
|
23
18
|
*
|
|
24
19
|
* Kept as a separate second parameter rather than a field on
|
|
25
|
-
* `SendEmailOptions
|
|
26
|
-
*
|
|
27
|
-
*
|
|
20
|
+
* `SendEmailOptions`, which is now the provider's own `EmailOptions`. The
|
|
21
|
+
* guard is about who may send and why; it is not part of the message, and
|
|
22
|
+
* folding it in would put it on the wire to the provider.
|
|
28
23
|
*/
|
|
29
24
|
export type SendEmailGuard = SendGuardContext;
|
|
30
25
|
/**
|
|
@@ -59,6 +54,28 @@ export interface OrderConfirmationEmailParams {
|
|
|
59
54
|
unitPrice: number;
|
|
60
55
|
totalPrice: number;
|
|
61
56
|
}>;
|
|
57
|
+
/**
|
|
58
|
+
* Phase 7 — the invoice PDF to attach, as STORED bytes.
|
|
59
|
+
*
|
|
60
|
+
* 🛑 Bytes, never a path and never a renderer. Two renders can disagree — a
|
|
61
|
+
* changed template, a different library version — and the buyer's copy must
|
|
62
|
+
* be byte-identical to ours. Letting this function render would be a second
|
|
63
|
+
* render by definition.
|
|
64
|
+
*
|
|
65
|
+
* 🛑 Optional, and absence must stay silent. The invoice is numbered on the
|
|
66
|
+
* PAID transition and its bytes are produced on first request, so a COD
|
|
67
|
+
* order confirmed before payment legitimately has none — and this email has
|
|
68
|
+
* always sent without an attachment. Failing or warning here would turn the
|
|
69
|
+
* normal case into a noisy one.
|
|
70
|
+
*
|
|
71
|
+
* Resend supports attachments already (`email-resend/provider.ts`), so this
|
|
72
|
+
* needs no new send, no new credential and no change to the 80/day ceiling.
|
|
73
|
+
* Only the payload is bigger.
|
|
74
|
+
*/
|
|
75
|
+
invoicePdf?: {
|
|
76
|
+
filename: string;
|
|
77
|
+
content: Buffer;
|
|
78
|
+
};
|
|
62
79
|
}
|
|
63
80
|
export declare function sendOrderConfirmationEmail(params: OrderConfirmationEmailParams): Promise<{
|
|
64
81
|
success: boolean;
|
|
@@ -124,9 +124,10 @@ export async function sendEmail(opts, guard) {
|
|
|
124
124
|
? opts.cc
|
|
125
125
|
: undefined,
|
|
126
126
|
replyTo: typeof opts.replyTo === "string" ? opts.replyTo : undefined,
|
|
127
|
-
headers:
|
|
128
|
-
|
|
129
|
-
|
|
127
|
+
headers: opts.headers,
|
|
128
|
+
// Forwarded, not dropped — the provider has mapped these to Resend's
|
|
129
|
+
// `attachments` all along; nothing here ever handed them over.
|
|
130
|
+
attachments: opts.attachments,
|
|
130
131
|
});
|
|
131
132
|
void recordSendAttempt({
|
|
132
133
|
to: opts.to,
|
|
@@ -184,6 +185,22 @@ export async function sendOrderConfirmationEmail(params) {
|
|
|
184
185
|
to,
|
|
185
186
|
subject: `Order Confirmed — ${orderId}`,
|
|
186
187
|
html,
|
|
188
|
+
/*
|
|
189
|
+
* Phase 7 — the invoice, when there is one. The STORED bytes, handed
|
|
190
|
+
* in by the caller: see `invoicePdf` on the params type for why this
|
|
191
|
+
* function must not render its own.
|
|
192
|
+
*/
|
|
193
|
+
...(params.invoicePdf
|
|
194
|
+
? {
|
|
195
|
+
attachments: [
|
|
196
|
+
{
|
|
197
|
+
filename: params.invoicePdf.filename,
|
|
198
|
+
content: params.invoicePdf.content,
|
|
199
|
+
contentType: "application/pdf",
|
|
200
|
+
},
|
|
201
|
+
],
|
|
202
|
+
}
|
|
203
|
+
: {}),
|
|
187
204
|
text: `Hi ${userName},\n\nYour order ${orderId} has been confirmed!\n\n${itemsText}\nTotal: ${formattedTotal}\nPayment: ${paymentLabel}\nShip to: ${shippingAddress ?? "-"}\n\nView your order: ${orderUrl}\n\n© ${currentYear()} ${siteName}`,
|
|
188
205
|
}, {
|
|
189
206
|
channel: "email",
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { FirebaseSieveFields, FirebaseSieveResult, SieveModel } from "../../../providers/db-firebase";
|
|
2
|
+
import { BaseRepository } from "../../../providers/db-firebase";
|
|
3
|
+
import type { DisputeKind } from "../../../_internal/shared/features/orders/dispute-reasons";
|
|
4
|
+
import { type DisputeReasonDocument } from "../schemas/firestore";
|
|
5
|
+
export declare class DisputeReasonsRepository extends BaseRepository<DisputeReasonDocument> {
|
|
6
|
+
static readonly SIEVE_FIELDS: FirebaseSieveFields;
|
|
7
|
+
constructor();
|
|
8
|
+
/**
|
|
9
|
+
* The picker's list, scoped to one kind.
|
|
10
|
+
*
|
|
11
|
+
* 🛑 Scoped by KIND, not merged. A buyer reporting a damaged item must not be
|
|
12
|
+
* offered "it never arrived" — that reason belongs to the other gate, and
|
|
13
|
+
* picking it would produce a dispute the gate then refuses, which reads to
|
|
14
|
+
* the buyer as the form being broken rather than as the wrong choice.
|
|
15
|
+
*/
|
|
16
|
+
listActiveByKind(kind: DisputeKind, model: SieveModel): Promise<FirebaseSieveResult<DisputeReasonDocument>>;
|
|
17
|
+
/**
|
|
18
|
+
* Add a reason. Staff only — enforced at the route, not here.
|
|
19
|
+
*
|
|
20
|
+
* 🛑 Keyed `dreason-<slug>`, so two agents cannot add the same wording twice.
|
|
21
|
+
* A colliding create returns the EXISTING row rather than overwriting it:
|
|
22
|
+
* that row has an author and may already be named on live disputes, and the
|
|
23
|
+
* second agent's intent ("there should be a reason for this") is satisfied by
|
|
24
|
+
* what exists. Same rule as the shipping-box presets.
|
|
25
|
+
*/
|
|
26
|
+
createReason(input: {
|
|
27
|
+
label: string;
|
|
28
|
+
kind: DisputeKind;
|
|
29
|
+
createdByUid: string;
|
|
30
|
+
createdByName: string;
|
|
31
|
+
}): Promise<{
|
|
32
|
+
reason: DisputeReasonDocument;
|
|
33
|
+
created: boolean;
|
|
34
|
+
}>;
|
|
35
|
+
/** Soft-retire. See the schema for why this is never a delete. */
|
|
36
|
+
retire(id: string): Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
export declare const disputeReasonsRepository: DisputeReasonsRepository;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { BaseRepository } from "../../../providers/db-firebase/index.js";
|
|
2
|
+
import { slugify } from "../../../utils/string.formatter.js";
|
|
3
|
+
import { DISPUTE_REASON_COLLECTION, DISPUTE_REASON_FIELDS, DISPUTE_REASON_LABEL_MAX, } from "../schemas/firestore.js";
|
|
4
|
+
export class DisputeReasonsRepository extends BaseRepository {
|
|
5
|
+
constructor() {
|
|
6
|
+
super(DISPUTE_REASON_COLLECTION);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The picker's list, scoped to one kind.
|
|
10
|
+
*
|
|
11
|
+
* 🛑 Scoped by KIND, not merged. A buyer reporting a damaged item must not be
|
|
12
|
+
* offered "it never arrived" — that reason belongs to the other gate, and
|
|
13
|
+
* picking it would produce a dispute the gate then refuses, which reads to
|
|
14
|
+
* the buyer as the form being broken rather than as the wrong choice.
|
|
15
|
+
*/
|
|
16
|
+
async listActiveByKind(kind, model) {
|
|
17
|
+
const baseQuery = this.getCollection()
|
|
18
|
+
.where(DISPUTE_REASON_FIELDS.IS_ACTIVE, "==", true)
|
|
19
|
+
.where(DISPUTE_REASON_FIELDS.KIND, "==", kind);
|
|
20
|
+
return this.sieveQuery(model, DisputeReasonsRepository.SIEVE_FIELDS, { baseQuery, defaultPageSize: 20, maxPageSize: 50 });
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Add a reason. Staff only — enforced at the route, not here.
|
|
24
|
+
*
|
|
25
|
+
* 🛑 Keyed `dreason-<slug>`, so two agents cannot add the same wording twice.
|
|
26
|
+
* A colliding create returns the EXISTING row rather than overwriting it:
|
|
27
|
+
* that row has an author and may already be named on live disputes, and the
|
|
28
|
+
* second agent's intent ("there should be a reason for this") is satisfied by
|
|
29
|
+
* what exists. Same rule as the shipping-box presets.
|
|
30
|
+
*/
|
|
31
|
+
async createReason(input) {
|
|
32
|
+
const label = input.label.trim().slice(0, DISPUTE_REASON_LABEL_MAX);
|
|
33
|
+
const id = `dreason-${slugify(label)}`;
|
|
34
|
+
const existing = await this.findById(id);
|
|
35
|
+
/*
|
|
36
|
+
* A retired row that someone re-adds is REACTIVATED rather than duplicated
|
|
37
|
+
* — otherwise the slug collides forever and the agent can never get their
|
|
38
|
+
* reason back. Reactivating keeps the original author, which is correct:
|
|
39
|
+
* they wrote the wording.
|
|
40
|
+
*/
|
|
41
|
+
if (existing) {
|
|
42
|
+
if (!existing.isActive) {
|
|
43
|
+
await this.update(id, { isActive: true, updatedAt: new Date() });
|
|
44
|
+
return { reason: { ...existing, isActive: true }, created: false };
|
|
45
|
+
}
|
|
46
|
+
return { reason: existing, created: false };
|
|
47
|
+
}
|
|
48
|
+
const now = new Date();
|
|
49
|
+
const reason = {
|
|
50
|
+
id,
|
|
51
|
+
label,
|
|
52
|
+
kind: input.kind,
|
|
53
|
+
isActive: true,
|
|
54
|
+
createdByUid: input.createdByUid,
|
|
55
|
+
createdByName: input.createdByName,
|
|
56
|
+
createdAt: now,
|
|
57
|
+
updatedAt: now,
|
|
58
|
+
};
|
|
59
|
+
await this.createWithId(id, reason);
|
|
60
|
+
return { reason, created: true };
|
|
61
|
+
}
|
|
62
|
+
/** Soft-retire. See the schema for why this is never a delete. */
|
|
63
|
+
async retire(id) {
|
|
64
|
+
await this.update(id, { isActive: false, updatedAt: new Date() });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
DisputeReasonsRepository.SIEVE_FIELDS = {
|
|
68
|
+
/*
|
|
69
|
+
* 🛑 `label` must be here because `PaginatedSelect` SEARCHES it. An
|
|
70
|
+
* unallowlisted searched field does not raise — `sieve.ts` sets
|
|
71
|
+
* `throwExceptions: false`, so `findField` returns undefined, the clause is
|
|
72
|
+
* silently dropped, and the picker returns the WHOLE list while looking
|
|
73
|
+
* exactly like it filtered (Root Cause #62).
|
|
74
|
+
*/
|
|
75
|
+
label: { canFilter: true, canSort: true },
|
|
76
|
+
kind: { canFilter: true, canSort: false },
|
|
77
|
+
isActive: { canFilter: true, canSort: false },
|
|
78
|
+
createdByUid: { canFilter: true, canSort: false },
|
|
79
|
+
};
|
|
80
|
+
export const disputeReasonsRepository = new DisputeReasonsRepository();
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `disputeReasons` — the staff-curated list a buyer picks from when raising a
|
|
3
|
+
* dispute.
|
|
4
|
+
*
|
|
5
|
+
* ## What this is NOT
|
|
6
|
+
*
|
|
7
|
+
* 🛑 It is not policy. Every row carries a `kind`, and the GATE lives on the
|
|
8
|
+
* kind — see `_internal/shared/features/orders/dispute-reasons.ts`. A row can
|
|
9
|
+
* never introduce a new kind of claim, because a gate cannot branch on a
|
|
10
|
+
* user-created string.
|
|
11
|
+
*
|
|
12
|
+
* So this list is for TRIAGE and ANALYTICS: "what are buyers actually
|
|
13
|
+
* reporting", answered without reading free text. Staff (admin + employees)
|
|
14
|
+
* add rows as real gaps appear; buyers select only.
|
|
15
|
+
*
|
|
16
|
+
* ## Why staff-creatable rather than a closed enum
|
|
17
|
+
*
|
|
18
|
+
* Its sibling `RETURN_REASON` *is* a closed enum, and deliberately so: each
|
|
19
|
+
* member there decides whether a final sale can refuse the return, which is a
|
|
20
|
+
* money consequence that must be reviewed in code. Nothing here decides money.
|
|
21
|
+
* A support agent who keeps seeing "the courier left it at the wrong flat"
|
|
22
|
+
* should be able to add it without a release.
|
|
23
|
+
*/
|
|
24
|
+
import type { DisputeKind } from "../../../_internal/shared/features/orders/dispute-reasons";
|
|
25
|
+
export declare const DISPUTE_REASON_COLLECTION = "disputeReasons";
|
|
26
|
+
export interface DisputeReasonDocument {
|
|
27
|
+
/** `dreason-<slug of label>`, so the same reason cannot be added twice. */
|
|
28
|
+
id: string;
|
|
29
|
+
/** Buyer-facing. Shown verbatim in the picker. */
|
|
30
|
+
label: string;
|
|
31
|
+
/** Which gate this reason is subject to. Never free-form. */
|
|
32
|
+
kind: DisputeKind;
|
|
33
|
+
/**
|
|
34
|
+
* 🛑 Soft-retire, never delete.
|
|
35
|
+
*
|
|
36
|
+
* A past dispute names this reason. Deleting the row would orphan that
|
|
37
|
+
* record and take the author with it — and the order snapshots the LABEL
|
|
38
|
+
* precisely so history survives either way, but a deleted row still loses
|
|
39
|
+
* "who added this, and when" for anyone auditing the list itself.
|
|
40
|
+
*/
|
|
41
|
+
isActive: boolean;
|
|
42
|
+
/** Display name, so a seller or agent can see whose wording this is. */
|
|
43
|
+
createdByName: string;
|
|
44
|
+
/** The uid, for moderation. Never rendered. */
|
|
45
|
+
createdByUid: string;
|
|
46
|
+
createdAt: Date;
|
|
47
|
+
updatedAt: Date;
|
|
48
|
+
}
|
|
49
|
+
export declare const DISPUTE_REASON_FIELDS: {
|
|
50
|
+
readonly ID: "id";
|
|
51
|
+
readonly LABEL: "label";
|
|
52
|
+
readonly KIND: "kind";
|
|
53
|
+
readonly IS_ACTIVE: "isActive";
|
|
54
|
+
readonly CREATED_BY_UID: "createdByUid";
|
|
55
|
+
readonly CREATED_AT: "createdAt";
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Bounds on a label.
|
|
59
|
+
*
|
|
60
|
+
* Short on purpose: this is a picker option, not the buyer's explanation. The
|
|
61
|
+
* detail goes in the free-text field on the dispute itself, which is where a
|
|
62
|
+
* buyer should describe what actually happened.
|
|
63
|
+
*/
|
|
64
|
+
export declare const DISPUTE_REASON_LABEL_MAX = 80;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `disputeReasons` — the staff-curated list a buyer picks from when raising a
|
|
3
|
+
* dispute.
|
|
4
|
+
*
|
|
5
|
+
* ## What this is NOT
|
|
6
|
+
*
|
|
7
|
+
* 🛑 It is not policy. Every row carries a `kind`, and the GATE lives on the
|
|
8
|
+
* kind — see `_internal/shared/features/orders/dispute-reasons.ts`. A row can
|
|
9
|
+
* never introduce a new kind of claim, because a gate cannot branch on a
|
|
10
|
+
* user-created string.
|
|
11
|
+
*
|
|
12
|
+
* So this list is for TRIAGE and ANALYTICS: "what are buyers actually
|
|
13
|
+
* reporting", answered without reading free text. Staff (admin + employees)
|
|
14
|
+
* add rows as real gaps appear; buyers select only.
|
|
15
|
+
*
|
|
16
|
+
* ## Why staff-creatable rather than a closed enum
|
|
17
|
+
*
|
|
18
|
+
* Its sibling `RETURN_REASON` *is* a closed enum, and deliberately so: each
|
|
19
|
+
* member there decides whether a final sale can refuse the return, which is a
|
|
20
|
+
* money consequence that must be reviewed in code. Nothing here decides money.
|
|
21
|
+
* A support agent who keeps seeing "the courier left it at the wrong flat"
|
|
22
|
+
* should be able to add it without a release.
|
|
23
|
+
*/
|
|
24
|
+
export const DISPUTE_REASON_COLLECTION = "disputeReasons";
|
|
25
|
+
export const DISPUTE_REASON_FIELDS = {
|
|
26
|
+
ID: "id",
|
|
27
|
+
LABEL: "label",
|
|
28
|
+
KIND: "kind",
|
|
29
|
+
IS_ACTIVE: "isActive",
|
|
30
|
+
CREATED_BY_UID: "createdByUid",
|
|
31
|
+
CREATED_AT: "createdAt",
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Bounds on a label.
|
|
35
|
+
*
|
|
36
|
+
* Short on purpose: this is a picker option, not the buyer's explanation. The
|
|
37
|
+
* detail goes in the free-text field on the dispute itself, which is where a
|
|
38
|
+
* buyer should describe what actually happened.
|
|
39
|
+
*/
|
|
40
|
+
export const DISPUTE_REASON_LABEL_MAX = 80;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import type { FAQ, FAQCategory } from "../types";
|
|
3
|
+
import { type PublishedValues } from "../../../_internal/shared/content/published-values";
|
|
3
4
|
export interface FAQAccordionProps {
|
|
4
5
|
faqs: FAQ[];
|
|
5
6
|
className?: string;
|
|
@@ -8,8 +9,19 @@ export interface FAQAccordionProps {
|
|
|
8
9
|
labels?: {
|
|
9
10
|
noResults?: string;
|
|
10
11
|
};
|
|
12
|
+
/**
|
|
13
|
+
* Phase 9 — resolved published values, for `{{token}}` substitution in
|
|
14
|
+
* answers. Build them with `resolvePublishedValues(settings?.commissions)`
|
|
15
|
+
* at the page level and pass them down.
|
|
16
|
+
*
|
|
17
|
+
* 🛑 Passed in rather than read here. This component renders on the server
|
|
18
|
+
* and in the client, and a settings read inside a presentational component
|
|
19
|
+
* would both make it unusable in half the places it is needed and fire one
|
|
20
|
+
* read per accordion on a page that has several.
|
|
21
|
+
*/
|
|
22
|
+
publishedValues?: PublishedValues;
|
|
11
23
|
}
|
|
12
|
-
export declare function FAQAccordion({ faqs, className, renderExpandedFooter, labels, }: FAQAccordionProps): React.JSX.Element;
|
|
24
|
+
export declare function FAQAccordion({ faqs, className, renderExpandedFooter, labels, publishedValues, }: FAQAccordionProps): React.JSX.Element;
|
|
13
25
|
interface FAQCategoryTabsProps {
|
|
14
26
|
categories: FAQCategory[];
|
|
15
27
|
active?: FAQCategory | null;
|
|
@@ -2,16 +2,41 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from "react";
|
|
4
4
|
import { Button, Div, RichText, Row, Span, Tabs, TabsList, TabsTrigger, Text } from "../../../ui/index.js";
|
|
5
|
+
/* Phase 9 — `publishedCopyKey`'s first reader. See the 🛑 at the render site. */
|
|
6
|
+
import { resolvePublishedCopy, resolvePublishedValues, } from "../../../_internal/shared/content/published-values.js";
|
|
5
7
|
import { RecordOwnerQuickActions } from "../../admin/components/RecordOwnerQuickActions.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Code-derived values, resolved once at module scope.
|
|
10
|
+
*
|
|
11
|
+
* ## 🛑 Why a DEFAULT rather than a required prop
|
|
12
|
+
*
|
|
13
|
+
* `resolvePublishedValues()` with no settings argument is PURE over code
|
|
14
|
+
* constants — the obligation policies, `LANE_CHECKOUT_WINDOW_MS`,
|
|
15
|
+
* `POINTS_EARN_PERCENT`. It reads no Firestore and touches no settings, so
|
|
16
|
+
* calling it here costs one object at import and cannot leak anything.
|
|
17
|
+
*
|
|
18
|
+
* That matters because there are FOUR mount sites (the FAQ page, the
|
|
19
|
+
* searchable list, the category highlights section, and this file's own
|
|
20
|
+
* loop). Requiring the prop would mean threading it through four callers and
|
|
21
|
+
* their callers — and the one that got missed would render `{{…}}` to a
|
|
22
|
+
* visitor. A default that always resolves the code-derived numbers, with an
|
|
23
|
+
* override for the settings-driven money ones, gets every surface right
|
|
24
|
+
* immediately.
|
|
25
|
+
*
|
|
26
|
+
* A caller WITH settings in hand should still pass them: without them the
|
|
27
|
+
* money values fall back to the schema defaults, which is correct but is not
|
|
28
|
+
* the admin's current configuration.
|
|
29
|
+
*/
|
|
30
|
+
const CODE_DERIVED_VALUES = resolvePublishedValues();
|
|
31
|
+
function FAQAccordionItem({ faq, isOpen, onToggle, renderExpandedFooter, publishedValues = CODE_DERIVED_VALUES, }) {
|
|
32
|
+
return (_jsxs(Div, { className: "border-b border-neutral-200 last:border-0", children: [_jsxs(Button, { variant: "ghost", onClick: onToggle, "aria-expanded": isOpen, justify: "between", className: "w-full gap-[1rem] py-[1rem] text-left text-[0.875rem] font-[500] text-[var(--appkit-color-text)] dark:text-[var(--appkit-color-text)] transition hover:text-primary", children: [_jsx(Span, { children: faq.question }), _jsx(Span, { className: `flex-shrink-0 transition-transform ${isOpen ? "rotate-180" : ""}`, children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: _jsx("path", { d: "M4 6l4 4 4-4", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) }) })] }), isOpen && (_jsxs(Div, { textSize: "sm", className: "text-[var(--appkit-color-text-muted)]", padding: "b-md", children: [faq.tags && faq.tags.length > 0 && (_jsx(Row, { wrap: true, gap: "sm", className: "mb-3", children: faq.tags.map((tag) => (_jsx(Span, { className: "bg-[var(--appkit-color-surface)] py-[0.25rem] text-[var(--appkit-color-text-muted)]", rounded: "md", padding: "x-xs", size: "xs", children: tag }, tag))) })), faq.answer.format === "html" ? (_jsx(RichText, { html: resolvePublishedCopy(faq.answer.text, publishedValues), className: "prose-sm" })) : (_jsx(Text, { className: "whitespace-pre-line", children: resolvePublishedCopy(faq.answer.text, publishedValues) })), _jsx(Row, { justify: "end", className: "mt-3", children: _jsx(RecordOwnerQuickActions, { resource: "FAQ", recordId: faq.id, placement: "detail", recordLabel: faq.question }) }), renderExpandedFooter?.(faq)] }))] }));
|
|
8
33
|
}
|
|
9
|
-
export function FAQAccordion({ faqs, className = "", renderExpandedFooter, labels, }) {
|
|
34
|
+
export function FAQAccordion({ faqs, className = "", renderExpandedFooter, labels, publishedValues, }) {
|
|
10
35
|
const [openId, setOpenId] = useState(null);
|
|
11
36
|
if (faqs.length === 0 && labels?.noResults) {
|
|
12
37
|
return (_jsx(Div, { surface: "card", padding: "xl", className: "text-center", children: _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", children: labels.noResults }) }));
|
|
13
38
|
}
|
|
14
|
-
return (_jsx(Div, { surface: "card", className: `divide-y divide-neutral-100 divide-[var(--appkit-color-border)] ${className}`, padding: "x-md", children: faqs.map((faq) => (_jsx(FAQAccordionItem, { faq: faq, isOpen: openId === faq.id, onToggle: () => setOpenId(openId === faq.id ? null : faq.id), renderExpandedFooter: renderExpandedFooter }, faq.id))) }));
|
|
39
|
+
return (_jsx(Div, { surface: "card", className: `divide-y divide-neutral-100 divide-[var(--appkit-color-border)] ${className}`, padding: "x-md", children: faqs.map((faq) => (_jsx(FAQAccordionItem, { faq: faq, isOpen: openId === faq.id, onToggle: () => setOpenId(openId === faq.id ? null : faq.id), renderExpandedFooter: renderExpandedFooter, publishedValues: publishedValues }, faq.id))) }));
|
|
15
40
|
}
|
|
16
41
|
const ALL_TAB_VALUE = "__all__";
|
|
17
42
|
export function FAQCategoryTabs({ categories, active, onSelect, labels = {}, }) {
|