@mohasinac/appkit 4.61.0 → 4.62.0
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/client/features/lottery/LotteryAdminEditView.js +19 -3
- package/dist/_internal/client/features/maintenance/views/AnalysisRunnerView.js +1 -1
- package/dist/_internal/client/features/maintenance/views/CloudLogsListView.js +1 -1
- package/dist/_internal/server/jobs/core/paymentReconcile.js +87 -97
- package/dist/_internal/shared/actions/action-registry.js +149 -9
- package/dist/_internal/shared/media/limits.d.ts +10 -0
- package/dist/_internal/shared/media/limits.js +10 -0
- package/dist/_internal/shared/types/action-result.js +15 -1
- package/dist/client/api/surface-error.js +16 -2
- package/dist/client/api/useApiMutation.js +17 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/constants/api-endpoints.d.ts +3 -0
- package/dist/constants/api-endpoints.js +1 -0
- package/dist/constants/field-names.d.ts +17 -0
- package/dist/constants/field-names.js +23 -0
- package/dist/core/server-action.js +19 -3
- package/dist/errors/error-mapping.js +26 -1
- package/dist/features/account/components/UserOffersPanel.js +5 -3
- package/dist/features/admin/components/AdminActionIndexView.js +100 -5
- package/dist/features/admin/components/AdminAddressClustersView.js +21 -2
- package/dist/features/admin/components/AdminBlogEditorView.js +8 -8
- package/dist/features/admin/components/AdminCarouselEditorView.js +1 -1
- package/dist/features/admin/components/AdminCarouselView.js +96 -4
- package/dist/features/admin/components/AdminFulfillmentView.js +3 -2
- package/dist/features/admin/components/AdminMediaView.js +36 -3
- package/dist/features/admin/components/AdminNavigationView.js +4 -1
- package/dist/features/admin/components/AdminOffersView.js +2 -1
- package/dist/features/admin/components/AdminPaymentClustersView.js +19 -2
- package/dist/features/admin/components/AdminProductEditorView.js +2 -2
- package/dist/features/admin/components/AdminSectionsView.d.ts +10 -0
- package/dist/features/admin/components/AdminSectionsView.js +486 -224
- package/dist/features/admin/components/ReferenceRecordEditor.js +1 -1
- package/dist/features/admin/hooks/useOptimisticRowFlag.d.ts +33 -0
- package/dist/features/admin/hooks/useOptimisticRowFlag.js +87 -0
- package/dist/features/admin/repository/notification.repository.d.ts +15 -0
- package/dist/features/admin/repository/notification.repository.js +32 -0
- package/dist/features/admin/schemas/action-index-form.d.ts +34 -0
- package/dist/features/admin/schemas/action-index-form.js +115 -0
- package/dist/features/auth/components/RegisterForm.js +2 -1
- package/dist/features/catalogue/components/CatalogueItemEditorView.js +3 -3
- package/dist/features/events/components/AdminEventEditorView.js +8 -6
- package/dist/features/layout/hooks/useFormBottomActions.js +20 -1
- package/dist/features/orders/components/EmiInstallmentsPanel.js +25 -7
- package/dist/features/orders/components/RefundRequestView.js +4 -4
- package/dist/features/orders/schemas/index.d.ts +6 -6
- package/dist/features/products/components/GroupMemberPicker.js +4 -1
- package/dist/features/products/components/PrizeDrawEntryActions.js +2 -1
- package/dist/features/products/components/ProductDetailActions.js +2 -1
- package/dist/features/seller/components/FulfillmentView.js +3 -2
- package/dist/features/seller/components/SellerAddressesView.js +32 -4
- package/dist/features/seller/components/SellerAnalyticsAlertsView.js +62 -3
- package/dist/features/seller/components/SellerBundlesView.js +53 -14
- package/dist/features/seller/components/SellerGroupedListingsView.js +23 -3
- package/dist/features/seller/components/SellerListingTypeView.js +56 -10
- package/dist/features/seller/components/SellerPayoutMethodsView.js +33 -1
- package/dist/features/seller/components/SellerPayoutSettingsView.js +48 -9
- package/dist/features/seller/components/SellerProductsView.js +5 -1
- package/dist/features/seller/components/SellerShippingConfigsView.js +33 -1
- package/dist/features/seller/components/SellerShippingView.js +2 -1
- package/dist/features/seller/components/SellerStoreCategoriesView.js +33 -1
- package/dist/features/seller/components/SellerSupportView.js +69 -2
- package/dist/features/shell/SectionForm.js +20 -16
- package/dist/features/shell/build-sections.js +14 -1
- package/dist/features/shipments/components/AdminShipmentLotItemsView.js +54 -1
- package/dist/features/shipments/components/AdminShipmentProjectionsView.js +3 -2
- package/dist/features/sticker-trace/components/StickerCommitPanel.js +2 -1
- package/dist/features/sticker-trace/components/StickerPrintPanel.js +2 -1
- package/dist/features/sticker-trace/components/StickerRetryPanel.js +24 -4
- package/dist/features/sticker-trace/components/StickerTraceStudio.js +2 -1
- package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +3 -2
- package/dist/index.d.ts +5 -1
- package/dist/index.js +3 -1
- package/dist/next/routing/route-map.d.ts +4 -0
- package/dist/next/routing/route-map.js +9 -0
- package/dist/schemas/registry.d.ts +4 -4
- package/dist/schemas/sieve.d.ts +3 -3
- package/dist/server.d.ts +1 -1
- package/dist/server.js +1 -1
- package/dist/ui/components/Checkbox.js +10 -2
- package/dist/ui/components/DateInput.js +8 -1
- package/dist/ui/components/Input.js +22 -2
- package/dist/ui/components/PaginatedSelect.d.ts +8 -19
- package/dist/ui/components/PaginatedSelect.js +43 -9
- package/dist/ui/components/Radio.js +8 -1
- package/dist/ui/components/Select.js +10 -2
- package/dist/ui/components/TagInput.d.ts +8 -1
- package/dist/ui/components/TagInput.js +4 -3
- package/dist/ui/components/Textarea.js +10 -2
- package/dist/ui/forms/FieldCheckbox.js +1 -1
- package/dist/ui/forms/FormErrorList.d.ts +0 -16
- package/dist/ui/forms/FormErrorList.js +34 -1
- package/dist/ui/forms/apply-zod-issues.d.ts +19 -1
- package/dist/ui/forms/apply-zod-issues.js +23 -1
- package/package.json +1 -1
package/dist/.build-stamp.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"stampVersion": 1,
|
|
3
|
-
"version": "4.
|
|
4
|
-
"head": "
|
|
3
|
+
"version": "4.62.0",
|
|
4
|
+
"head": "2ffd384e9122be6b68eba81be0eb152e5d1f6a3e",
|
|
5
5
|
"branch": "main",
|
|
6
6
|
"dirty": false,
|
|
7
|
-
"builtAt": "2026-10-
|
|
7
|
+
"builtAt": "2026-10-22T11:28:37.098Z"
|
|
8
8
|
}
|
|
@@ -96,6 +96,15 @@ export function LotteryAdminEditView({ eventId, initialData, onSubmit, }) {
|
|
|
96
96
|
* `audit-code-quality`'s DEEP_NESTING rule exists to stop.
|
|
97
97
|
*/
|
|
98
98
|
function SlotTable({ slots, showPrice, onSlotsChange, }) {
|
|
99
|
+
/*
|
|
100
|
+
* Read the form context directly. `SlotTable` is its own component, so the
|
|
101
|
+
* `shellCtx` the parent destructures is out of scope here — but the parent
|
|
102
|
+
* mounts `FormShellContext.Provider` above this, so the errors are
|
|
103
|
+
* reachable. `useContext` rather than `useFormShell()`: this table is
|
|
104
|
+
* also usable outside a form and must not require a provider.
|
|
105
|
+
*/
|
|
106
|
+
const formCtx = React.useContext(FormShellContext);
|
|
107
|
+
const slotError = (idx, field) => formCtx?.errors[`slots.${idx}.${field}`];
|
|
99
108
|
/**
|
|
100
109
|
* Next number is max+1, not length+1.
|
|
101
110
|
*
|
|
@@ -146,19 +155,26 @@ function SlotTable({ slots, showPrice, onSlotsChange, }) {
|
|
|
146
155
|
{
|
|
147
156
|
key: "name",
|
|
148
157
|
header: "Prize name",
|
|
149
|
-
|
|
158
|
+
/*
|
|
159
|
+
* These inputs live in DataTable COLUMN renderers, which get no
|
|
160
|
+
* `errors` argument — a section render would. They read the context
|
|
161
|
+
* this component itself mounts, with the dotted INDEXED key
|
|
162
|
+
* `applyZodIssues` produces (`slots.2.price`), so a rejected slot marks
|
|
163
|
+
* its own row rather than the first one.
|
|
164
|
+
*/
|
|
165
|
+
render: (s) => (_jsx(Input, { type: "text", className: "w-full", placeholder: `Prize for slot ${s.slotNumber}`, value: s.name, error: slotError(s._idx, "name"), onChange: (e) => updateSlot(s._idx, "name", e.target.value), "aria-label": `Slot ${s.slotNumber} prize name` })),
|
|
150
166
|
},
|
|
151
167
|
{
|
|
152
168
|
key: "image",
|
|
153
169
|
header: "Image",
|
|
154
|
-
render: (s) => (_jsx(Input, { type: "text", className: "w-full", placeholder: "/media/\u2026 or https://\u2026", value: s.image ?? "", onChange: (e) => updateSlot(s._idx, "image", e.target.value), "aria-label": `Slot ${s.slotNumber} prize image URL` })),
|
|
170
|
+
render: (s) => (_jsx(Input, { type: "text", className: "w-full", placeholder: "/media/\u2026 or https://\u2026", value: s.image ?? "", error: slotError(s._idx, "image"), onChange: (e) => updateSlot(s._idx, "image", e.target.value), "aria-label": `Slot ${s.slotNumber} prize image URL` })),
|
|
155
171
|
},
|
|
156
172
|
...(showPrice
|
|
157
173
|
? [
|
|
158
174
|
{
|
|
159
175
|
key: "price",
|
|
160
176
|
header: "Price (₹)",
|
|
161
|
-
render: (s) => (_jsx(Input, { type: "number", className: "w-24", placeholder: "0", value: s.price, onChange: (e) => updateSlot(s._idx, "price", e.target.value), "aria-label": `Slot ${s.slotNumber} price` })),
|
|
177
|
+
render: (s) => (_jsx(Input, { type: "number", className: "w-24", placeholder: "0", value: s.price, error: slotError(s._idx, "price"), onChange: (e) => updateSlot(s._idx, "price", e.target.value), "aria-label": `Slot ${s.slotNumber} price` })),
|
|
162
178
|
},
|
|
163
179
|
]
|
|
164
180
|
: []),
|
|
@@ -23,7 +23,7 @@ export function AnalysisRunnerView() {
|
|
|
23
23
|
});
|
|
24
24
|
const body = (await res.json());
|
|
25
25
|
if (!res.ok || !body.ok) {
|
|
26
|
-
setError(("
|
|
26
|
+
setError(toUserMessage("code" in body ? String(body.code) : undefined, undefined, { fallback: `The analysis run failed (HTTP ${res.status}).` }));
|
|
27
27
|
setRunning(false);
|
|
28
28
|
return;
|
|
29
29
|
}
|
|
@@ -32,7 +32,7 @@ export function CloudLogsListView({ title, subtitle, initialEntries, initialNext
|
|
|
32
32
|
const res = await fetch(url.toString());
|
|
33
33
|
const body = (await res.json());
|
|
34
34
|
if (!res.ok || !body.ok) {
|
|
35
|
-
setError(("
|
|
35
|
+
setError(toUserMessage("code" in body ? String(body.code) : undefined, undefined, { fallback: `Could not load logs (HTTP ${res.status}).` }));
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
38
38
|
setEntries((prev) => [...prev, ...body.data.entries]);
|
|
@@ -138,81 +138,7 @@ export async function reconcileStuckGatewayEvents(ctx) {
|
|
|
138
138
|
}
|
|
139
139
|
return { redispatched, escalated, reads: stuck.length };
|
|
140
140
|
}
|
|
141
|
-
|
|
142
|
-
* "The gateway captured a different amount from the one the order records."
|
|
143
|
-
*
|
|
144
|
-
* Extracted from the sweep below, where it sat inside
|
|
145
|
-
* fn → `for record` → `for order` → `if` and the whole case literal was a
|
|
146
|
-
* brace too deep. Pure construction — no branching, and `recorded` is passed
|
|
147
|
-
* in rather than re-derived, so the one money rule still lives at the call
|
|
148
|
-
* site (Root Cause #75).
|
|
149
|
-
*/
|
|
150
|
-
async function openAmountMismatchCase(record, orderId, recorded, now) {
|
|
151
|
-
await paymentCasesRepository().openOrObserve({
|
|
152
|
-
kind: "amount_mismatch",
|
|
153
|
-
subjectId: `${record.id}:${orderId}`,
|
|
154
|
-
severity: "error",
|
|
155
|
-
title: `Captured ${record.amount} but recorded ${recorded} — ${orderId}`,
|
|
156
|
-
detail: [
|
|
157
|
-
`The gateway captured a different amount from the one the order records.`,
|
|
158
|
-
`Either the buyer was over-charged or the order under-states what was paid.`,
|
|
159
|
-
"",
|
|
160
|
-
`Gateway: ${record.gateway}`,
|
|
161
|
-
`Payment: ${record.id}`,
|
|
162
|
-
`Order: ${orderId}`,
|
|
163
|
-
`Captured: ${record.amount} ${record.currency}`,
|
|
164
|
-
`Order total: ${recorded}`,
|
|
165
|
-
`Difference: ${(record.amount - recorded).toFixed(2)}`,
|
|
166
|
-
].join("\n"),
|
|
167
|
-
entityType: "order",
|
|
168
|
-
entityId: orderId,
|
|
169
|
-
amountAtRisk: Math.abs(record.amount - recorded),
|
|
170
|
-
orderIds: [orderId],
|
|
171
|
-
gatewayPaymentId: record.id,
|
|
172
|
-
now,
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* "Our refund total disagrees with the gateway's."
|
|
177
|
-
*
|
|
178
|
-
* Same extraction as `openAmountMismatchCase`. `ours` and `base` are both
|
|
179
|
-
* computed by the caller from the shared helpers, so this never re-derives a
|
|
180
|
-
* refund figure of its own.
|
|
181
|
-
*
|
|
182
|
-
* 🛑 The severity split is the whole point and must not be flattened: the
|
|
183
|
-
* gateway having refunded MORE than we recorded is money that left without a
|
|
184
|
-
* record (`error`); our side showing more is a refund reserved and never
|
|
185
|
-
* completed (`warning`).
|
|
186
|
-
*/
|
|
187
|
-
async function openRefundMismatchCase(record, orderId, ours, base, now) {
|
|
188
|
-
await paymentCasesRepository().openOrObserve({
|
|
189
|
-
kind: "refund_mismatch",
|
|
190
|
-
subjectId: `${record.id}:refunds`,
|
|
191
|
-
severity: record.amountRefunded > ours ? "error" : "warning",
|
|
192
|
-
title: `Refund total disagrees with the gateway — ${orderId}`,
|
|
193
|
-
detail: [
|
|
194
|
-
record.amountRefunded > ours
|
|
195
|
-
? `🛑 The gateway has refunded MORE than our records show. Money has left that we did not record.`
|
|
196
|
-
: `Our records show more refunded than the gateway did — a refund may have been reserved and never completed.`,
|
|
197
|
-
"",
|
|
198
|
-
`Gateway: ${record.gateway}`,
|
|
199
|
-
`Payment: ${record.id}`,
|
|
200
|
-
`Order: ${orderId}`,
|
|
201
|
-
`Gateway refunded: ${record.amountRefunded}`,
|
|
202
|
-
`Our committed refunds: ${ours}`,
|
|
203
|
-
`Refundable base: ${base}`,
|
|
204
|
-
record.refundStatus ? `Gateway refund status: ${record.refundStatus}` : null,
|
|
205
|
-
]
|
|
206
|
-
.filter((l) => l !== null)
|
|
207
|
-
.join("\n"),
|
|
208
|
-
entityType: "order",
|
|
209
|
-
entityId: orderId,
|
|
210
|
-
amountAtRisk: Math.abs(record.amountRefunded - ours),
|
|
211
|
-
orderIds: [orderId],
|
|
212
|
-
gatewayPaymentId: record.id,
|
|
213
|
-
now,
|
|
214
|
-
});
|
|
215
|
-
}
|
|
141
|
+
/* ──────────────── R2: the gateway has it — do we? ───────────────────────── */
|
|
216
142
|
async function reconcileGatewayPayments(ctx) {
|
|
217
143
|
const payment = getProviders().payment;
|
|
218
144
|
let examined = 0;
|
|
@@ -298,28 +224,9 @@ async function reconcileGatewayPayments(ctx) {
|
|
|
298
224
|
continue;
|
|
299
225
|
}
|
|
300
226
|
for (const order of orders) {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
* pricing pipeline here and disagree with it the day a rate changes,
|
|
305
|
-
* which is a sixth copy of the money rule (Root Cause #75).
|
|
306
|
-
*/
|
|
307
|
-
const recorded = order.totalPrice ?? 0;
|
|
308
|
-
if (Math.abs(recorded - record.amount) > MONEY_EPSILON) {
|
|
309
|
-
amountMismatch += 1;
|
|
310
|
-
await openAmountMismatchCase(record, order.id, recorded, ctx.now);
|
|
311
|
-
}
|
|
312
|
-
/*
|
|
313
|
-
* Refund check — the gateway's `amount_refunded` against our own
|
|
314
|
-
* non-failed refund events. `committedRefundTotal` is the same helper
|
|
315
|
-
* the refund cap uses, so "what we think we refunded" has ONE
|
|
316
|
-
* definition rather than a second one written here.
|
|
317
|
-
*/
|
|
318
|
-
const ours = committedRefundTotal(order);
|
|
319
|
-
if (Math.abs(ours - record.amountRefunded) > MONEY_EPSILON) {
|
|
320
|
-
refundMismatch += 1;
|
|
321
|
-
await openRefundMismatchCase(record, order.id, ours, refundableBase(order), ctx.now);
|
|
322
|
-
}
|
|
227
|
+
const outcome = await reconcilePaymentAgainstOrder(record, order, ctx);
|
|
228
|
+
amountMismatch += outcome.amountMismatch;
|
|
229
|
+
refundMismatch += outcome.refundMismatch;
|
|
323
230
|
}
|
|
324
231
|
}
|
|
325
232
|
catch (err) {
|
|
@@ -331,6 +238,89 @@ async function reconcileGatewayPayments(ctx) {
|
|
|
331
238
|
}
|
|
332
239
|
return { examined, missing, amountMismatch, refundMismatch, reads };
|
|
333
240
|
}
|
|
241
|
+
/**
|
|
242
|
+
* The two per-order checks R2 runs against one gateway record.
|
|
243
|
+
*
|
|
244
|
+
* Extracted so each `openOrObserve` call is a statement in a function body
|
|
245
|
+
* rather than five braces deep inside `for record -> try -> for order -> if`.
|
|
246
|
+
* Returns the counts rather than mutating the caller's accumulators, so the
|
|
247
|
+
* loop above reads as a sum rather than as two side effects.
|
|
248
|
+
*/
|
|
249
|
+
async function reconcilePaymentAgainstOrder(record, order, ctx) {
|
|
250
|
+
let amountMismatch = 0;
|
|
251
|
+
let refundMismatch = 0;
|
|
252
|
+
/*
|
|
253
|
+
* Amount check. Compared against the order's own recorded total rather
|
|
254
|
+
* than recomputed from its lines — recomputing would re-derive the
|
|
255
|
+
* pricing pipeline here and disagree with it the day a rate changes,
|
|
256
|
+
* which is a sixth copy of the money rule (Root Cause #75).
|
|
257
|
+
*/
|
|
258
|
+
const recorded = order.totalPrice ?? 0;
|
|
259
|
+
if (Math.abs(recorded - record.amount) > MONEY_EPSILON) {
|
|
260
|
+
amountMismatch += 1;
|
|
261
|
+
await paymentCasesRepository().openOrObserve({
|
|
262
|
+
kind: "amount_mismatch",
|
|
263
|
+
subjectId: `${record.id}:${order.id}`,
|
|
264
|
+
severity: "error",
|
|
265
|
+
title: `Captured ${record.amount} but recorded ${recorded} — ${order.id}`,
|
|
266
|
+
detail: [
|
|
267
|
+
`The gateway captured a different amount from the one the order records.`,
|
|
268
|
+
`Either the buyer was over-charged or the order under-states what was paid.`,
|
|
269
|
+
"",
|
|
270
|
+
`Gateway: ${record.gateway}`,
|
|
271
|
+
`Payment: ${record.id}`,
|
|
272
|
+
`Order: ${order.id}`,
|
|
273
|
+
`Captured: ${record.amount} ${record.currency}`,
|
|
274
|
+
`Order total: ${recorded}`,
|
|
275
|
+
`Difference: ${(record.amount - recorded).toFixed(2)}`,
|
|
276
|
+
].join("\n"),
|
|
277
|
+
entityType: "order",
|
|
278
|
+
entityId: order.id,
|
|
279
|
+
amountAtRisk: Math.abs(record.amount - recorded),
|
|
280
|
+
orderIds: [order.id],
|
|
281
|
+
gatewayPaymentId: record.id,
|
|
282
|
+
now: ctx.now,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
/*
|
|
286
|
+
* Refund check — the gateway's `amount_refunded` against our own
|
|
287
|
+
* non-failed refund events. `committedRefundTotal` is the same helper
|
|
288
|
+
* the refund cap uses, so "what we think we refunded" has ONE
|
|
289
|
+
* definition rather than a second one written here.
|
|
290
|
+
*/
|
|
291
|
+
const ours = committedRefundTotal(order);
|
|
292
|
+
if (Math.abs(ours - record.amountRefunded) > MONEY_EPSILON) {
|
|
293
|
+
refundMismatch += 1;
|
|
294
|
+
await paymentCasesRepository().openOrObserve({
|
|
295
|
+
kind: "refund_mismatch",
|
|
296
|
+
subjectId: `${record.id}:refunds`,
|
|
297
|
+
severity: record.amountRefunded > ours ? "error" : "warning",
|
|
298
|
+
title: `Refund total disagrees with the gateway — ${order.id}`,
|
|
299
|
+
detail: [
|
|
300
|
+
record.amountRefunded > ours
|
|
301
|
+
? `🛑 The gateway has refunded MORE than our records show. Money has left that we did not record.`
|
|
302
|
+
: `Our records show more refunded than the gateway did — a refund may have been reserved and never completed.`,
|
|
303
|
+
"",
|
|
304
|
+
`Gateway: ${record.gateway}`,
|
|
305
|
+
`Payment: ${record.id}`,
|
|
306
|
+
`Order: ${order.id}`,
|
|
307
|
+
`Gateway refunded: ${record.amountRefunded}`,
|
|
308
|
+
`Our committed refunds: ${ours}`,
|
|
309
|
+
`Refundable base: ${refundableBase(order)}`,
|
|
310
|
+
record.refundStatus ? `Gateway refund status: ${record.refundStatus}` : null,
|
|
311
|
+
]
|
|
312
|
+
.filter((l) => l !== null)
|
|
313
|
+
.join("\n"),
|
|
314
|
+
entityType: "order",
|
|
315
|
+
entityId: order.id,
|
|
316
|
+
amountAtRisk: Math.abs(record.amountRefunded - ours),
|
|
317
|
+
orderIds: [order.id],
|
|
318
|
+
gatewayPaymentId: record.id,
|
|
319
|
+
now: ctx.now,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
return { amountMismatch, refundMismatch };
|
|
323
|
+
}
|
|
334
324
|
/* ──────────────── R3: refund claimed, never settled ─────────────────────── */
|
|
335
325
|
/**
|
|
336
326
|
* A `refundClaims` row still `claimed` an hour later is a crash between
|
|
@@ -1427,6 +1427,40 @@ export const ACTIONS = {
|
|
|
1427
1427
|
confirmKind: "danger",
|
|
1428
1428
|
},
|
|
1429
1429
|
},
|
|
1430
|
+
/*
|
|
1431
|
+
* 🛑 The confirmation names the consequence rather than the mechanism.
|
|
1432
|
+
* "Revoke session" means nothing to a buyer; "that device is signed out"
|
|
1433
|
+
* is the fact they need to weigh, and the warning about their own device
|
|
1434
|
+
* is the one mistake this control makes easy to make.
|
|
1435
|
+
*/
|
|
1436
|
+
"revoke-session": {
|
|
1437
|
+
iconKey: "lock",
|
|
1438
|
+
id: "user.revoke-session",
|
|
1439
|
+
label: "Sign out device",
|
|
1440
|
+
ariaLabel: "Sign out this device",
|
|
1441
|
+
description: "End an active session so that device has to sign in again.",
|
|
1442
|
+
kind: "danger",
|
|
1443
|
+
confirmation: {
|
|
1444
|
+
title: "Sign out this device?",
|
|
1445
|
+
body: "Whoever is using it will be signed out and will need your password to get back in. If this is the device you are on right now, you will be signed out too.",
|
|
1446
|
+
confirmLabel: "Sign out device",
|
|
1447
|
+
confirmKind: "danger",
|
|
1448
|
+
},
|
|
1449
|
+
},
|
|
1450
|
+
"delete-payment-method": {
|
|
1451
|
+
iconKey: "delete",
|
|
1452
|
+
id: "user.delete-payment-method",
|
|
1453
|
+
label: "Remove card",
|
|
1454
|
+
ariaLabel: "Remove this saved payment method",
|
|
1455
|
+
description: "Remove a saved payment method from the account.",
|
|
1456
|
+
kind: "danger",
|
|
1457
|
+
confirmation: {
|
|
1458
|
+
title: "Remove this payment method?",
|
|
1459
|
+
body: "It will no longer appear at checkout. Any order already placed with it is unaffected.",
|
|
1460
|
+
confirmLabel: "Remove",
|
|
1461
|
+
confirmKind: "danger",
|
|
1462
|
+
},
|
|
1463
|
+
},
|
|
1430
1464
|
"set-default-address": {
|
|
1431
1465
|
iconKey: "confirm",
|
|
1432
1466
|
id: "user.set-default-address",
|
|
@@ -2397,17 +2431,44 @@ export const ACTIONS = {
|
|
|
2397
2431
|
kind: "ghost",
|
|
2398
2432
|
permissions: ["admin"],
|
|
2399
2433
|
},
|
|
2434
|
+
"activate-carousel": {
|
|
2435
|
+
iconKey: "resume",
|
|
2436
|
+
id: "admin.activate-carousel",
|
|
2437
|
+
label: "Activate",
|
|
2438
|
+
ariaLabel: "Show this slide in the carousel",
|
|
2439
|
+
description: "Set a carousel slide to active so the hero carousel renders it.",
|
|
2440
|
+
kind: "primary",
|
|
2441
|
+
permissions: ["admin"],
|
|
2442
|
+
},
|
|
2443
|
+
"deactivate-carousel": {
|
|
2444
|
+
iconKey: "hide",
|
|
2445
|
+
id: "admin.deactivate-carousel",
|
|
2446
|
+
label: "Deactivate",
|
|
2447
|
+
ariaLabel: "Hide this slide from the carousel",
|
|
2448
|
+
description: "Set a carousel slide to inactive without deleting it.",
|
|
2449
|
+
kind: "secondary",
|
|
2450
|
+
permissions: ["admin"],
|
|
2451
|
+
},
|
|
2400
2452
|
/*
|
|
2401
|
-
* Homepage sections have no bucket of their own, so their
|
|
2402
|
-
*
|
|
2403
|
-
*
|
|
2453
|
+
* Homepage sections have no bucket of their own, so their leaves live here
|
|
2454
|
+
* beside the carousel ones for the same reason — both are homepage chrome
|
|
2455
|
+
* an admin edits from a list rather than from a detail page.
|
|
2456
|
+
*
|
|
2457
|
+
* 🛑 `delete-section` exists but is NOT reachable from the public homepage.
|
|
2458
|
+
* Section `order` is POSITIONAL, so removing one leaves a gap in its
|
|
2459
|
+
* neighbours' numbering — not something to offer from a public page, where
|
|
2460
|
+
* the only confirmation a person gets is a dialog.
|
|
2461
|
+
*
|
|
2462
|
+
* That prohibition is enforced INDEPENDENTLY of this registry, which is
|
|
2463
|
+
* why the leaf can exist: `RESOURCE_LEAVES.SECTION` in `OwnerQuickActions`
|
|
2464
|
+
* omits `remove`, and `showDelete` gates on `leaves.remove` rather than on
|
|
2465
|
+
* what a call site passes — so the pencil strip renders no bin regardless.
|
|
2466
|
+
* This comment read "there is deliberately NO delete-section counterpart"
|
|
2467
|
+
* until 2026-10-22, when the admin sections listing gained a real one.
|
|
2404
2468
|
*
|
|
2405
|
-
*
|
|
2406
|
-
*
|
|
2407
|
-
*
|
|
2408
|
-
* a person gets is a dialog. `OwnerQuickActions` enforces the absence by
|
|
2409
|
-
* omitting `remove` from the SECTION mapping, so no call site can pass a
|
|
2410
|
-
* delete handler and have it render.
|
|
2469
|
+
* The listing is the right home for it: it owns these records, it shows
|
|
2470
|
+
* the `order` column the gap appears in, and its Reorder panel has a
|
|
2471
|
+
* Reindex 1..N button beside it to close one.
|
|
2411
2472
|
*/
|
|
2412
2473
|
"edit-section": {
|
|
2413
2474
|
iconKey: "edit",
|
|
@@ -2418,6 +2479,62 @@ export const ACTIONS = {
|
|
|
2418
2479
|
kind: "ghost",
|
|
2419
2480
|
permissions: ["admin"],
|
|
2420
2481
|
},
|
|
2482
|
+
"enable-section": {
|
|
2483
|
+
iconKey: "resume",
|
|
2484
|
+
id: "admin.enable-section",
|
|
2485
|
+
label: "Enable",
|
|
2486
|
+
ariaLabel: "Enable this homepage section",
|
|
2487
|
+
description: "Render this section on its surface again.",
|
|
2488
|
+
kind: "primary",
|
|
2489
|
+
permissions: ["admin"],
|
|
2490
|
+
},
|
|
2491
|
+
"disable-section": {
|
|
2492
|
+
iconKey: "hide",
|
|
2493
|
+
id: "admin.disable-section",
|
|
2494
|
+
label: "Disable",
|
|
2495
|
+
ariaLabel: "Disable this homepage section",
|
|
2496
|
+
description: "Stop rendering this section without deleting it or its saved config.",
|
|
2497
|
+
kind: "secondary",
|
|
2498
|
+
permissions: ["admin"],
|
|
2499
|
+
},
|
|
2500
|
+
"delete-section": {
|
|
2501
|
+
iconKey: "delete",
|
|
2502
|
+
id: "admin.delete-section",
|
|
2503
|
+
label: "Delete Section",
|
|
2504
|
+
ariaLabel: "Delete this homepage section",
|
|
2505
|
+
description: "Permanently remove this homepage section and its saved config.",
|
|
2506
|
+
kind: "danger",
|
|
2507
|
+
permissions: ["admin"],
|
|
2508
|
+
confirmation: {
|
|
2509
|
+
title: "Delete this section?",
|
|
2510
|
+
/*
|
|
2511
|
+
* The last sentence is load-bearing: it names the reversible
|
|
2512
|
+
* alternative at the moment of the irreversible choice, and that
|
|
2513
|
+
* alternative is one click away in the same row.
|
|
2514
|
+
*/
|
|
2515
|
+
body: "The section and its saved config are removed permanently. Display order is positional, so the sections after it keep their current numbers and a gap is left — Reorder sections → Reindex 1..N closes it. To hide the section from the homepage while keeping its config, disable it instead.",
|
|
2516
|
+
confirmLabel: "Delete section",
|
|
2517
|
+
confirmKind: "danger",
|
|
2518
|
+
},
|
|
2519
|
+
},
|
|
2520
|
+
"show-nav-item": {
|
|
2521
|
+
iconKey: "view",
|
|
2522
|
+
id: "admin.show-nav-item",
|
|
2523
|
+
label: "Show",
|
|
2524
|
+
ariaLabel: "Show this navigation item",
|
|
2525
|
+
description: "Make a navigation item visible in the sidebar and menus.",
|
|
2526
|
+
kind: "primary",
|
|
2527
|
+
permissions: ["admin"],
|
|
2528
|
+
},
|
|
2529
|
+
"hide-nav-item": {
|
|
2530
|
+
iconKey: "hide",
|
|
2531
|
+
id: "admin.hide-nav-item",
|
|
2532
|
+
label: "Hide",
|
|
2533
|
+
ariaLabel: "Hide this navigation item",
|
|
2534
|
+
description: "Hide a navigation item without deleting it.",
|
|
2535
|
+
kind: "secondary",
|
|
2536
|
+
permissions: ["admin"],
|
|
2537
|
+
},
|
|
2421
2538
|
"delete-carousel": {
|
|
2422
2539
|
iconKey: "delete",
|
|
2423
2540
|
id: "admin.delete-carousel",
|
|
@@ -3085,6 +3202,29 @@ export const ACTIONS = {
|
|
|
3085
3202
|
confirmKind: "danger",
|
|
3086
3203
|
},
|
|
3087
3204
|
},
|
|
3205
|
+
/*
|
|
3206
|
+
* 🛑 Distinct from `clear-previews` and `discard-staged`, which are
|
|
3207
|
+
* SESSION/STAGING operations over files no record points at yet. This one
|
|
3208
|
+
* removes a FINALISED asset, so the confirmation has to say what nothing
|
|
3209
|
+
* else in this bucket has to: the bytes are referenced by whatever already
|
|
3210
|
+
* embedded them, and the proxy resolves `/media/<shortId>` through the
|
|
3211
|
+
* `mediaAssets` row — so deleting is how a live page gets a broken image.
|
|
3212
|
+
*/
|
|
3213
|
+
"delete-media": {
|
|
3214
|
+
iconKey: "delete",
|
|
3215
|
+
id: "media.delete-media",
|
|
3216
|
+
label: "Delete file",
|
|
3217
|
+
ariaLabel: "Delete this media file",
|
|
3218
|
+
description: "Permanently delete a media file and the asset row that resolves it.",
|
|
3219
|
+
kind: "danger",
|
|
3220
|
+
permissions: ["admin"],
|
|
3221
|
+
confirmation: {
|
|
3222
|
+
title: "Delete this file?",
|
|
3223
|
+
body: "The file is removed from storage and stops resolving. Any product, blog post or section still embedding it will show a broken image — this cannot be undone.",
|
|
3224
|
+
confirmLabel: "Delete file",
|
|
3225
|
+
confirmKind: "danger",
|
|
3226
|
+
},
|
|
3227
|
+
},
|
|
3088
3228
|
},
|
|
3089
3229
|
SUPPORT: {
|
|
3090
3230
|
"create-ticket": {
|
|
@@ -27,6 +27,16 @@ export type MediaKind = "image" | "video" | "pdf";
|
|
|
27
27
|
export declare const PRODUCT_MAX_IMAGES = 24;
|
|
28
28
|
/** Videos per listing. Bounded by `ProductDocument.video` being singular. */
|
|
29
29
|
export declare const PRODUCT_MAX_VIDEOS = 1;
|
|
30
|
+
/**
|
|
31
|
+
* Photos a buyer may attach to one review.
|
|
32
|
+
*
|
|
33
|
+
* 🛑 Deliberately NOT `PRODUCT_MAX_IMAGES`, which is 24 and is a SELLER's
|
|
34
|
+
* ceiling for a whole listing. `audit-media-limit-literals` suggests that
|
|
35
|
+
* constant for any `images: z.array(…).max(n)`, and taking the suggestion
|
|
36
|
+
* here would have quadrupled what a review accepts — the right shape, the
|
|
37
|
+
* wrong number. A review is a few photos of one thing.
|
|
38
|
+
*/
|
|
39
|
+
export declare const REVIEW_MAX_IMAGES = 6;
|
|
30
40
|
/**
|
|
31
41
|
* Highest legal `index` in a product media filename.
|
|
32
42
|
*
|
|
@@ -41,6 +41,16 @@ export const MAX_VIDEO_BYTES = 50 * MEGABYTE;
|
|
|
41
41
|
export const PRODUCT_MAX_IMAGES = 24;
|
|
42
42
|
/** Videos per listing. Bounded by `ProductDocument.video` being singular. */
|
|
43
43
|
export const PRODUCT_MAX_VIDEOS = 1;
|
|
44
|
+
/**
|
|
45
|
+
* Photos a buyer may attach to one review.
|
|
46
|
+
*
|
|
47
|
+
* 🛑 Deliberately NOT `PRODUCT_MAX_IMAGES`, which is 24 and is a SELLER's
|
|
48
|
+
* ceiling for a whole listing. `audit-media-limit-literals` suggests that
|
|
49
|
+
* constant for any `images: z.array(…).max(n)`, and taking the suggestion
|
|
50
|
+
* here would have quadrupled what a review accepts — the right shape, the
|
|
51
|
+
* wrong number. A review is a few photos of one thing.
|
|
52
|
+
*/
|
|
53
|
+
export const REVIEW_MAX_IMAGES = 6;
|
|
44
54
|
/**
|
|
45
55
|
* Highest legal `index` in a product media filename.
|
|
46
56
|
*
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { normalizeError } from "../../../errors/normalize.js";
|
|
2
2
|
import { mapToHttpError } from "../../../errors/error-mapping.js";
|
|
3
|
+
import { toApiIssues } from "../../../errors/error-envelope.js";
|
|
3
4
|
import { describeCauseChain } from "../../../errors/describe-cause.js";
|
|
4
5
|
import { reportActionError } from "./action-error-reporter.js";
|
|
5
6
|
/**
|
|
@@ -57,7 +58,20 @@ export async function wrapAction(fn) {
|
|
|
57
58
|
ok: false,
|
|
58
59
|
code: mapped.code,
|
|
59
60
|
error: mapped.message,
|
|
60
|
-
|
|
61
|
+
/*
|
|
62
|
+
* 🛑 `toApiIssues`, not the raw issues — this was the ONE producer in
|
|
63
|
+
* the codebase that skipped the scrubber, and it is the one with 221
|
|
64
|
+
* call sites.
|
|
65
|
+
*
|
|
66
|
+
* A raw `ZodIssue` carries `received`: the value the user actually
|
|
67
|
+
* submitted. On a failed signup, address save or payout-details save
|
|
68
|
+
* that is an email, a phone number or a bank account, echoed straight
|
|
69
|
+
* back into the client payload. `toApiIssues` narrows each issue to
|
|
70
|
+
* `{ message, path?, code? }`, which is exactly why it exists — every
|
|
71
|
+
* other producer (`routeHandler`, `createApiHandler`, the offer
|
|
72
|
+
* actions) already routes through it.
|
|
73
|
+
*/
|
|
74
|
+
...(mapped.issues ? { issues: toApiIssues(mapped.issues) } : {}),
|
|
61
75
|
};
|
|
62
76
|
}
|
|
63
77
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isApiError } from "./ApiError.js";
|
|
2
2
|
import { ApiClientError } from "../../http/ApiClient.js";
|
|
3
|
-
import { applyZodIssues, hasAttachableIssue } from "../../ui/forms/apply-zod-issues.js";
|
|
3
|
+
import { applyZodIssues, hasAttachableIssue, rootLevelMessages } from "../../ui/forms/apply-zod-issues.js";
|
|
4
4
|
import { normalizeError } from "../../errors/normalize.js";
|
|
5
5
|
import { getErrorDisplay, toUserMessage } from "../../errors/error-display-map.js";
|
|
6
6
|
import { HTTP_ERROR_CODES } from "../../errors/error-mapping.js";
|
|
@@ -36,7 +36,21 @@ export function surfaceError(err, opts) {
|
|
|
36
36
|
const issues = issuesOf(err);
|
|
37
37
|
if (setFieldError && hasAttachableIssue(issues)) {
|
|
38
38
|
applyZodIssues(issues, (name, message) => setFieldError(name, message ?? ""));
|
|
39
|
-
|
|
39
|
+
/*
|
|
40
|
+
* 🛑 Root-level issues are NOT covered by the line above and must still be
|
|
41
|
+
* surfaced, or a cross-field rule fails silently.
|
|
42
|
+
*
|
|
43
|
+
* `applyZodIssues` skips any issue with an empty `path` — there is no
|
|
44
|
+
* field to put it on. For a mixed response (one field error plus one
|
|
45
|
+
* cross-field `.refine()`, the shape of "passwords must match" or "end
|
|
46
|
+
* date must be after start date") `hasAttachableIssue` is true, so we used
|
|
47
|
+
* to take this branch and `return` with the refine message discarded: the
|
|
48
|
+
* save was refused and nothing on screen said why.
|
|
49
|
+
*/
|
|
50
|
+
for (const message of rootLevelMessages(issues)) {
|
|
51
|
+
showToast(message, "error");
|
|
52
|
+
}
|
|
53
|
+
return; // field errors inline; only the unattachable ones toast
|
|
40
54
|
}
|
|
41
55
|
if (hasStableErrorCode(err)) {
|
|
42
56
|
const display = getErrorDisplay(err.code);
|
|
@@ -3,6 +3,7 @@ import * as React from "react";
|
|
|
3
3
|
import { useMutation, } from "@tanstack/react-query";
|
|
4
4
|
import { useToast } from "../../ui/components/Toast.js";
|
|
5
5
|
import { surfaceError } from "./surface-error.js";
|
|
6
|
+
import { toUserMessage } from "../../errors/error-display-map.js";
|
|
6
7
|
export function useApiMutation(options) {
|
|
7
8
|
const { showToast, updateToast } = useToast();
|
|
8
9
|
const loadingIdRef = React.useRef(null);
|
|
@@ -40,7 +41,22 @@ export function useApiMutation(options) {
|
|
|
40
41
|
onSettled: (data, err, variables, context, ctx) => {
|
|
41
42
|
if (loadingIdRef.current) {
|
|
42
43
|
if (err) {
|
|
43
|
-
|
|
44
|
+
/*
|
|
45
|
+
* 🛑 This was `err instanceof Error ? err.message : "Action failed"`
|
|
46
|
+
* — verbatim the inverted ternary `audit-raw-error-text`'s own
|
|
47
|
+
* header calls "the dominant shape", sitting two lines below the
|
|
48
|
+
* correct `surfaceError` call in the same hook. The authored
|
|
49
|
+
* sentence showed only when the value was NOT an Error, i.e. almost
|
|
50
|
+
* never, so the normal case published the raw message.
|
|
51
|
+
*
|
|
52
|
+
* It was dormant only because `loadingMessage` currently has no
|
|
53
|
+
* call sites; the first consumer to set one would have got two
|
|
54
|
+
* toasts, one of them raw. `updateToast` is also absent from that
|
|
55
|
+
* audit's SINKS list, so nothing would have caught it.
|
|
56
|
+
*/
|
|
57
|
+
updateToast(loadingIdRef.current, "error", toUserMessage(err?.code, undefined, {
|
|
58
|
+
fallback: "Action failed",
|
|
59
|
+
}));
|
|
44
60
|
}
|
|
45
61
|
else if (!successMessage) {
|
|
46
62
|
updateToast(loadingIdRef.current, "success", "Done");
|
package/dist/client.d.ts
CHANGED
|
@@ -455,7 +455,7 @@ export type { OwnerQuickActionsProps, OwnerQuickActionsResource } from "./featur
|
|
|
455
455
|
export type { StoreListingEntitlements } from "./features/seller/hooks/useStoreListingEntitlements";
|
|
456
456
|
export type { ListingTypePlugin } from "./_internal/shared/listing-types/_registry";
|
|
457
457
|
export type { ListingType } from "./features/products/types/index";
|
|
458
|
-
export { MEGABYTE, MAX_IMAGE_BYTES, MAX_PDF_BYTES, MAX_VIDEO_BYTES, MAX_LABEL, MAX_BYTES, ALLOWED_IMAGE_MIMES, IMAGE_ACCEPT_ATTR, ALLOWED_VIDEO_MIMES, ALLOWED_DOC_MIMES, ALLOWED_MIMES, ALLOWED_TYPES_LABEL, MIME_TO_EXT, PDF_MAGIC, VIDEO_CONVERSION_HINTS, classifyMime, isAllowedMime, maxBytesFor, getConversionHint, PRODUCT_MAX_IMAGES, PRODUCT_MAX_VIDEOS, PRODUCT_IMAGE_INDEX_MAX, } from "./_internal/shared/media/limits";
|
|
458
|
+
export { MEGABYTE, MAX_IMAGE_BYTES, MAX_PDF_BYTES, MAX_VIDEO_BYTES, MAX_LABEL, MAX_BYTES, ALLOWED_IMAGE_MIMES, IMAGE_ACCEPT_ATTR, ALLOWED_VIDEO_MIMES, ALLOWED_DOC_MIMES, ALLOWED_MIMES, ALLOWED_TYPES_LABEL, MIME_TO_EXT, PDF_MAGIC, VIDEO_CONVERSION_HINTS, classifyMime, isAllowedMime, maxBytesFor, getConversionHint, PRODUCT_MAX_IMAGES, REVIEW_MAX_IMAGES, PRODUCT_MAX_VIDEOS, PRODUCT_IMAGE_INDEX_MAX, } from "./_internal/shared/media/limits";
|
|
459
459
|
export type { MediaKind, AllowedImageMime, AllowedVideoMime, AllowedDocMime, AllowedMime, } from "./_internal/shared/media/limits";
|
|
460
460
|
export { ScamAwarenessModal } from "./features/scams/components/ScamAwarenessModal";
|
|
461
461
|
export type { ScamAwarenessModalProps } from "./features/scams/components/ScamAwarenessModal";
|
package/dist/client.js
CHANGED
|
@@ -493,7 +493,7 @@ export { YourPurchaseStrip } from "./features/orders/components/YourPurchaseStri
|
|
|
493
493
|
export { ReviewFormDrawer, reviewFormSchema } from "./features/reviews/components/ReviewFormDrawer.js";
|
|
494
494
|
export { PurchaseReviewAffordance } from "./features/reviews/components/PurchaseReviewAffordance.js";
|
|
495
495
|
// Media upload limits — shared by client uploaders + server sign/finalize routes.
|
|
496
|
-
export { MEGABYTE, MAX_IMAGE_BYTES, MAX_PDF_BYTES, MAX_VIDEO_BYTES, MAX_LABEL, MAX_BYTES, ALLOWED_IMAGE_MIMES, IMAGE_ACCEPT_ATTR, ALLOWED_VIDEO_MIMES, ALLOWED_DOC_MIMES, ALLOWED_MIMES, ALLOWED_TYPES_LABEL, MIME_TO_EXT, PDF_MAGIC, VIDEO_CONVERSION_HINTS, classifyMime, isAllowedMime, maxBytesFor, getConversionHint, PRODUCT_MAX_IMAGES, PRODUCT_MAX_VIDEOS, PRODUCT_IMAGE_INDEX_MAX, } from "./_internal/shared/media/limits.js";
|
|
496
|
+
export { MEGABYTE, MAX_IMAGE_BYTES, MAX_PDF_BYTES, MAX_VIDEO_BYTES, MAX_LABEL, MAX_BYTES, ALLOWED_IMAGE_MIMES, IMAGE_ACCEPT_ATTR, ALLOWED_VIDEO_MIMES, ALLOWED_DOC_MIMES, ALLOWED_MIMES, ALLOWED_TYPES_LABEL, MIME_TO_EXT, PDF_MAGIC, VIDEO_CONVERSION_HINTS, classifyMime, isAllowedMime, maxBytesFor, getConversionHint, PRODUCT_MAX_IMAGES, REVIEW_MAX_IMAGES, PRODUCT_MAX_VIDEOS, PRODUCT_IMAGE_INDEX_MAX, } from "./_internal/shared/media/limits.js";
|
|
497
497
|
export { ScamAwarenessModal } from "./features/scams/components/ScamAwarenessModal.js";
|
|
498
498
|
export { SellerTrustBadge } from "./features/scams/components/SellerTrustBadge.js";
|
|
499
499
|
// [CLIENT-ONLY] — Admin panel components for action/nav permission management.
|
|
@@ -535,6 +535,7 @@ export declare const SELLER_ENDPOINTS: {
|
|
|
535
535
|
readonly BUNDLES: "/api/store/bundles";
|
|
536
536
|
/** Tickets raised ABOUT this store (W11). */
|
|
537
537
|
readonly SUPPORT: "/api/store/support";
|
|
538
|
+
readonly SUPPORT_TICKET_BY_ID: (id: string) => string;
|
|
538
539
|
readonly BUNDLE_BY_ID: (id: string) => string;
|
|
539
540
|
readonly ORDERS_ASSIGN: (orderId: string) => string;
|
|
540
541
|
readonly PRODUCT_BY_ID: (id: string) => string;
|
|
@@ -1061,6 +1062,7 @@ export declare const API_ENDPOINTS: {
|
|
|
1061
1062
|
readonly BUNDLES: "/api/store/bundles";
|
|
1062
1063
|
/** Tickets raised ABOUT this store (W11). */
|
|
1063
1064
|
readonly SUPPORT: "/api/store/support";
|
|
1065
|
+
readonly SUPPORT_TICKET_BY_ID: (id: string) => string;
|
|
1064
1066
|
readonly BUNDLE_BY_ID: (id: string) => string;
|
|
1065
1067
|
readonly ORDERS_ASSIGN: (orderId: string) => string;
|
|
1066
1068
|
readonly PRODUCT_BY_ID: (id: string) => string;
|
|
@@ -1608,6 +1610,7 @@ export declare const API_ROUTES: {
|
|
|
1608
1610
|
readonly BUNDLES: "/api/store/bundles";
|
|
1609
1611
|
/** Tickets raised ABOUT this store (W11). */
|
|
1610
1612
|
readonly SUPPORT: "/api/store/support";
|
|
1613
|
+
readonly SUPPORT_TICKET_BY_ID: (id: string) => string;
|
|
1611
1614
|
readonly BUNDLE_BY_ID: (id: string) => string;
|
|
1612
1615
|
readonly ORDERS_ASSIGN: (orderId: string) => string;
|
|
1613
1616
|
readonly PRODUCT_BY_ID: (id: string) => string;
|