@mohasinac/appkit 4.60.1 → 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/features/auctions/actions.d.ts +1 -1
- package/dist/_internal/server/features/auctions/actions.js +12 -1
- package/dist/_internal/server/features/blog/actions.d.ts +1 -1
- package/dist/_internal/server/features/blog/actions.js +12 -1
- package/dist/_internal/server/features/brands/actions.d.ts +1 -1
- package/dist/_internal/server/features/brands/actions.js +12 -1
- package/dist/_internal/server/features/cart/actions.d.ts +1 -1
- package/dist/_internal/server/features/cart/actions.js +12 -1
- package/dist/_internal/server/features/checkout/actions.js +0 -1
- package/dist/_internal/server/features/events/actions.d.ts +1 -1
- package/dist/_internal/server/features/events/actions.js +12 -1
- package/dist/_internal/server/features/gateway-events/redact.js +14 -2
- package/dist/_internal/server/features/history/actions.d.ts +1 -1
- package/dist/_internal/server/features/history/actions.js +12 -1
- package/dist/_internal/server/features/lottery/actions.d.ts +1 -1
- package/dist/_internal/server/features/lottery/actions.js +12 -1
- package/dist/_internal/server/features/orders/actions.d.ts +1 -1
- package/dist/_internal/server/features/orders/actions.js +12 -1
- package/dist/_internal/server/features/orders/cancellation-refund.js +0 -1
- package/dist/_internal/server/features/orders/service.js +31 -1
- package/dist/_internal/server/features/payouts/actions.d.ts +1 -1
- package/dist/_internal/server/features/payouts/actions.js +12 -1
- package/dist/_internal/server/features/points/award.js +9 -1
- package/dist/_internal/server/features/pre-orders/actions.d.ts +1 -1
- package/dist/_internal/server/features/pre-orders/actions.js +12 -1
- package/dist/_internal/server/features/prize-draws/actions.d.ts +1 -1
- package/dist/_internal/server/features/prize-draws/actions.js +12 -1
- package/dist/_internal/server/features/products/actions.d.ts +1 -1
- package/dist/_internal/server/features/products/actions.js +12 -1
- package/dist/_internal/server/features/promotions/actions.d.ts +1 -1
- package/dist/_internal/server/features/promotions/actions.js +12 -1
- package/dist/_internal/server/features/protection-claims/actions.d.ts +2 -2
- package/dist/_internal/server/features/protection-claims/actions.js +21 -12
- package/dist/_internal/server/features/raffle/actions.d.ts +1 -1
- package/dist/_internal/server/features/raffle/actions.js +12 -1
- package/dist/_internal/server/features/refunds/actions.d.ts +1 -3
- package/dist/_internal/server/features/refunds/actions.js +14 -2
- package/dist/_internal/server/features/reviews/actions.d.ts +1 -1
- package/dist/_internal/server/features/reviews/actions.js +12 -1
- package/dist/_internal/server/features/wishlist/actions.d.ts +1 -1
- package/dist/_internal/server/features/wishlist/actions.js +12 -1
- package/dist/_internal/server/jobs/core/obligationSweep.js +0 -1
- package/dist/_internal/server/jobs/core/paymentReconcile.js +93 -76
- package/dist/_internal/server/jobs/core/testDataCleanup.d.ts +1 -1
- package/dist/_internal/server/jobs/core/testDataCleanup.js +22 -1
- package/dist/_internal/server/jobs/handlers/onShipmentAllocationSync.js +16 -4
- package/dist/_internal/shared/actions/action-registry.js +149 -9
- package/dist/_internal/shared/features/promotions/schema.d.ts +66 -0
- package/dist/_internal/shared/features/promotions/schema.js +7 -0
- package/dist/_internal/shared/media/limits.d.ts +10 -0
- package/dist/_internal/shared/media/limits.js +10 -0
- package/dist/_internal/shared/obligations/deadlines.js +11 -1
- package/dist/_internal/shared/tester/window.d.ts +25 -0
- package/dist/_internal/shared/tester/window.js +66 -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/core/unit-of-work.js +15 -1
- package/dist/errors/error-mapping.js +26 -1
- package/dist/errors/firestore-codes.d.ts +37 -0
- package/dist/errors/firestore-codes.js +52 -0
- package/dist/errors/index.d.ts +0 -1
- package/dist/errors/index.js +12 -2
- 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/AdminCouponEditorView.js +8 -0
- package/dist/features/admin/components/AdminFulfillmentView.js +3 -2
- package/dist/features/admin/components/AdminGatewayEventsView.js +2 -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/DataListingView.js +2 -0
- package/dist/features/admin/components/ReferenceRecordEditor.js +1 -1
- package/dist/features/admin/components/analytics/AdminStatCard.js +4 -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/repository/site-settings.repository.d.ts +2 -1
- package/dist/features/admin/repository/site-settings.repository.js +7 -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/auctions/components/MarketplaceAuctionCard.js +1 -1
- package/dist/features/auth/components/RegisterForm.js +2 -1
- package/dist/features/catalogue/actions/approve-catalogue-listing.d.ts +1 -1
- package/dist/features/catalogue/actions/approve-catalogue-listing.js +12 -1
- package/dist/features/catalogue/actions/list-from-catalogue.d.ts +1 -1
- package/dist/features/catalogue/actions/list-from-catalogue.js +12 -1
- package/dist/features/catalogue/actions/submit-for-approval.d.ts +1 -1
- package/dist/features/catalogue/actions/submit-for-approval.js +12 -1
- package/dist/features/catalogue/components/CatalogueItemEditorView.js +3 -3
- package/dist/features/categories/repository/price-observation.repository.d.ts +19 -0
- package/dist/features/categories/repository/price-observation.repository.js +21 -2
- package/dist/features/events/actions/event-actions.js +4 -1
- package/dist/features/events/components/AdminEventEditorView.js +79 -50
- package/dist/features/events/components/EventsListPageView.js +2 -1
- package/dist/features/events/schemas/firestore.d.ts +17 -0
- package/dist/features/events/schemas/firestore.js +1 -0
- package/dist/features/events/utils/sort-pinned.d.ts +33 -0
- package/dist/features/events/utils/sort-pinned.js +46 -0
- package/dist/features/gateway-events/repository/gateway-events.repository.js +3 -40
- package/dist/features/gateway-events/schemas/index.d.ts +2 -2
- package/dist/features/inventory/components/AdminStorageBoxesView.js +55 -39
- package/dist/features/inventory/components/ReceiveStationView.js +61 -17
- package/dist/features/inventory/repository/storage-boxes.repository.d.ts +3 -1
- package/dist/features/inventory/repository/storage-boxes.repository.js +36 -3
- package/dist/features/layout/hooks/useFormBottomActions.js +20 -1
- package/dist/features/obligations/repository/obligations.repository.js +10 -1
- package/dist/features/orders/components/EmiInstallmentsPanel.js +25 -7
- package/dist/features/orders/components/RefundRequestView.js +4 -4
- package/dist/features/orders/schemas/firestore.d.ts +19 -0
- package/dist/features/orders/schemas/firestore.js +19 -0
- package/dist/features/orders/schemas/index.d.ts +6 -6
- package/dist/features/payment-cases/repository/payment-cases.repository.d.ts +1 -0
- package/dist/features/payment-cases/repository/payment-cases.repository.js +3 -14
- package/dist/features/pre-orders/components/MarketplacePreorderCard.js +1 -1
- package/dist/features/products/components/GroupMemberPicker.js +4 -1
- package/dist/features/products/components/MarketplaceBundleCard.js +1 -1
- package/dist/features/products/components/MarketplacePrizeDrawCard.js +1 -1
- package/dist/features/products/components/PrizeDrawEntryActions.js +2 -1
- package/dist/features/products/components/ProductDetailActions.js +2 -1
- package/dist/features/products/components/ProductForm.js +2 -2
- package/dist/features/products/components/ProductGrid.js +1 -1
- package/dist/features/promotions/repository/claimed-coupons.repository.js +31 -1
- package/dist/features/promotions/schemas/firestore.d.ts +22 -0
- package/dist/features/promotions/schemas/firestore.js +22 -0
- package/dist/features/promotions/schemas/index.d.ts +479 -6
- package/dist/features/promotions/schemas/index.js +153 -0
- package/dist/features/protection-claims/repository/protection-claims.repository.js +22 -15
- 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/seller/schemas/coupon-form.d.ts +5 -0
- package/dist/features/seller/schemas/coupon-form.js +18 -0
- package/dist/features/shell/SectionForm.js +35 -9
- 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/sticker-trace/server.d.ts +2 -0
- package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +3 -2
- package/dist/http/api-handler.js +4 -1
- package/dist/index.d.ts +8 -4
- package/dist/index.js +6 -4
- package/dist/next/routing/route-map.d.ts +4 -2
- package/dist/next/routing/route-map.js +19 -1
- package/dist/schemas/registry.d.ts +15 -10
- package/dist/schemas/sieve.d.ts +3 -3
- package/dist/server.d.ts +1 -2
- package/dist/server.js +6 -4
- package/dist/styles.css +51 -0
- 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 +0 -0
- 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/scripts/seed-cli.mjs +1822 -1812
- package/dist/features/tester/utils/phases.d.ts +0 -48
- package/dist/features/tester/utils/phases.js +0 -139
|
@@ -6,7 +6,18 @@
|
|
|
6
6
|
* handlers also work in a Vercel lambda (10s cap honoured by raffle pool
|
|
7
7
|
* size).
|
|
8
8
|
*/
|
|
9
|
-
|
|
9
|
+
/*
|
|
10
|
+
* 🛑 From the DEFINING module, never "@mohasinac/appkit/server".
|
|
11
|
+
*
|
|
12
|
+
* appkit importing its own public server barrel is a self-reference that drags
|
|
13
|
+
* the whole server surface in behind it — `dist/server.js` reaches next/og,
|
|
14
|
+
* next/navigation, next/headers and driver.js. Twenty `actions.ts` files did
|
|
15
|
+
* this for two symbols, and because `obligationSweep` reaches one of them, the
|
|
16
|
+
* Firebase Functions tsup bundle (noExternal: appkit) had to resolve Next:
|
|
17
|
+
* it failed on two `react-server-dom-webpack/*` requires and two @vercel/og
|
|
18
|
+
* .wasm files, naming none of the responsible files. Root Cause #18.
|
|
19
|
+
*/
|
|
20
|
+
import { wrapAction } from "../../../shared/types/action-result.js";
|
|
10
21
|
import { getAdminDb } from "../../../../providers/db-firebase/admin.js";
|
|
11
22
|
import { runTriggerEventRaffle, } from "../../jobs/core/triggerEventRaffle.js";
|
|
12
23
|
import { runAssignSpinPrize, } from "../../jobs/core/assignSpinPrize.js";
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { type ActionResult } from "
|
|
2
|
-
import type { RefundType } from "../../../../features/orders/schemas";
|
|
1
|
+
import { type ActionResult } from "../../../shared/types/action-result";
|
|
3
2
|
import { type ReturnReason } from "../../../shared/features/orders/return-reasons";
|
|
4
3
|
export type ProcessRefundInput = {
|
|
5
4
|
orderId: string;
|
|
6
|
-
type: RefundType;
|
|
7
5
|
/** Decimal rupees. Must be > 0 and ≤ remaining refundable amount on the order. */
|
|
8
6
|
amount: number;
|
|
9
7
|
/**
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
"use server";
|
|
2
|
-
|
|
2
|
+
/*
|
|
3
|
+
* 🛑 From the DEFINING module, never "@mohasinac/appkit/server".
|
|
4
|
+
*
|
|
5
|
+
* appkit importing its own public server barrel is a self-reference that drags
|
|
6
|
+
* the whole server surface in behind it — `dist/server.js` reaches next/og,
|
|
7
|
+
* next/navigation, next/headers and driver.js. Twenty `actions.ts` files did
|
|
8
|
+
* this for two symbols, and because `obligationSweep` reaches one of them, the
|
|
9
|
+
* Firebase Functions tsup bundle (noExternal: appkit) had to resolve Next:
|
|
10
|
+
* it failed on two `react-server-dom-webpack/*` requires and two @vercel/og
|
|
11
|
+
* .wasm files, naming none of the responsible files. Root Cause #18.
|
|
12
|
+
*/
|
|
13
|
+
import { wrapAction } from "../../../shared/types/action-result.js";
|
|
3
14
|
/**
|
|
4
15
|
* processRefundAction — append a refund event to an order.
|
|
5
16
|
*
|
|
@@ -235,7 +246,8 @@ export async function processRefundAction(input) {
|
|
|
235
246
|
const isFull = wouldBeFullRefund(order, input.amount, siteSettings?.commissions?.gstPercent);
|
|
236
247
|
const event = {
|
|
237
248
|
refundId,
|
|
238
|
-
|
|
249
|
+
// Derived from the authoritative `isFull` above, never from a caller.
|
|
250
|
+
type: isFull ? "full" : "partial",
|
|
239
251
|
amount: input.amount,
|
|
240
252
|
...(input.itemIds?.length ? { itemIds: input.itemIds } : {}),
|
|
241
253
|
reasonCode: input.reasonCode,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ActionResult } from "
|
|
1
|
+
import { type ActionResult } from "../../../shared/types/action-result";
|
|
2
2
|
export declare function createReviewAction(input: unknown): Promise<ActionResult<unknown>>;
|
|
3
3
|
export declare function replyToReviewAction(input: unknown): Promise<ActionResult<unknown>>;
|
|
4
4
|
export declare function deleteReviewAction(input: unknown): Promise<ActionResult<unknown>>;
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
"use server";
|
|
2
|
-
|
|
2
|
+
/*
|
|
3
|
+
* 🛑 From the DEFINING module, never "@mohasinac/appkit/server".
|
|
4
|
+
*
|
|
5
|
+
* appkit importing its own public server barrel is a self-reference that drags
|
|
6
|
+
* the whole server surface in behind it — `dist/server.js` reaches next/og,
|
|
7
|
+
* next/navigation, next/headers and driver.js. Twenty `actions.ts` files did
|
|
8
|
+
* this for two symbols, and because `obligationSweep` reaches one of them, the
|
|
9
|
+
* Firebase Functions tsup bundle (noExternal: appkit) had to resolve Next:
|
|
10
|
+
* it failed on two `react-server-dom-webpack/*` requires and two @vercel/og
|
|
11
|
+
* .wasm files, naming none of the responsible files. Root Cause #18.
|
|
12
|
+
*/
|
|
13
|
+
import { wrapAction } from "../../../shared/types/action-result.js";
|
|
3
14
|
import { requireRoleUser } from "../../../../providers/auth-firebase/helpers.js";
|
|
4
15
|
import { isAdminUser } from "../../../../features/auth/role-predicates.js";
|
|
5
16
|
import { reviewRepository, productRepository, userRepository, } from "../../../../repositories/index.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ActionResult } from "
|
|
1
|
+
import { type ActionResult } from "../../../shared/types/action-result";
|
|
2
2
|
import { type WishlistItem } from "../../../../repositories";
|
|
3
3
|
export declare function addToWishlistAction(productId: string, extras?: {
|
|
4
4
|
productType?: WishlistItem["productType"];
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
"use server";
|
|
2
2
|
import { normalizeError } from "../../../../errors/normalize.js";
|
|
3
|
-
|
|
3
|
+
/*
|
|
4
|
+
* 🛑 From the DEFINING module, never "@mohasinac/appkit/server".
|
|
5
|
+
*
|
|
6
|
+
* appkit importing its own public server barrel is a self-reference that drags
|
|
7
|
+
* the whole server surface in behind it — `dist/server.js` reaches next/og,
|
|
8
|
+
* next/navigation, next/headers and driver.js. Twenty `actions.ts` files did
|
|
9
|
+
* this for two symbols, and because `obligationSweep` reaches one of them, the
|
|
10
|
+
* Firebase Functions tsup bundle (noExternal: appkit) had to resolve Next:
|
|
11
|
+
* it failed on two `react-server-dom-webpack/*` requires and two @vercel/og
|
|
12
|
+
* .wasm files, naming none of the responsible files. Root Cause #18.
|
|
13
|
+
*/
|
|
14
|
+
import { wrapAction } from "../../../shared/types/action-result.js";
|
|
4
15
|
import { requireRoleUser } from "../../../../providers/auth-firebase/helpers.js";
|
|
5
16
|
import { wishlistRepository, WishlistFullError, } from "../../../../repositories/index.js";
|
|
6
17
|
import { WISHLIST_MAX } from "../../../shared/features/wishlist/config.js";
|
|
@@ -279,7 +279,6 @@ async function performAction(ctx, obligation, order) {
|
|
|
279
279
|
}
|
|
280
280
|
const result = await processRefundAction({
|
|
281
281
|
orderId: order.id,
|
|
282
|
-
type: "full",
|
|
283
282
|
amount: capacity.remaining,
|
|
284
283
|
reasonCode: order.returnReasonCode ?? "not_as_described",
|
|
285
284
|
reasonNote: `Auto-resolved in the buyer's favour — ${policy.describe}`,
|
|
@@ -36,7 +36,8 @@ import { gatewayEventsRepository } from "../../../../features/gateway-events/rep
|
|
|
36
36
|
import { GATEWAY_EVENT_MAX_ATTEMPTS, } from "../../../../features/gateway-events/schemas/firestore.js";
|
|
37
37
|
import { paymentCasesRepository } from "../../../../features/payment-cases/repository/payment-cases.repository.js";
|
|
38
38
|
import { orderRepository } from "../../../../features/orders/repository/orders.repository.js";
|
|
39
|
-
import { REFUND_CLAIMS_COLLECTION } from "../../../../features/orders/schemas/firestore.js";
|
|
39
|
+
import { REFUND_CLAIMS_COLLECTION, REFUND_CLAIM_FIELDS, ORDER_COLLECTION, } from "../../../../features/orders/schemas/firestore.js";
|
|
40
|
+
import { ORDER_FIELDS } from "../../../../constants/field-names.js";
|
|
40
41
|
import { reconcileDisputes } from "../../features/payment-cases/disputes.js";
|
|
41
42
|
import { refundableBase, committedRefundTotal, MONEY_EPSILON, } from "../../../shared/checkout/refundable-base.js";
|
|
42
43
|
/** How far back R2 looks. Two days of overlap, so a run that fails is covered by the next. */
|
|
@@ -223,76 +224,9 @@ async function reconcileGatewayPayments(ctx) {
|
|
|
223
224
|
continue;
|
|
224
225
|
}
|
|
225
226
|
for (const order of orders) {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
* pricing pipeline here and disagree with it the day a rate changes,
|
|
230
|
-
* which is a sixth copy of the money rule (Root Cause #75).
|
|
231
|
-
*/
|
|
232
|
-
const recorded = order.totalPrice ?? 0;
|
|
233
|
-
if (Math.abs(recorded - record.amount) > MONEY_EPSILON) {
|
|
234
|
-
amountMismatch += 1;
|
|
235
|
-
await paymentCasesRepository().openOrObserve({
|
|
236
|
-
kind: "amount_mismatch",
|
|
237
|
-
subjectId: `${record.id}:${order.id}`,
|
|
238
|
-
severity: "error",
|
|
239
|
-
title: `Captured ${record.amount} but recorded ${recorded} — ${order.id}`,
|
|
240
|
-
detail: [
|
|
241
|
-
`The gateway captured a different amount from the one the order records.`,
|
|
242
|
-
`Either the buyer was over-charged or the order under-states what was paid.`,
|
|
243
|
-
"",
|
|
244
|
-
`Gateway: ${record.gateway}`,
|
|
245
|
-
`Payment: ${record.id}`,
|
|
246
|
-
`Order: ${order.id}`,
|
|
247
|
-
`Captured: ${record.amount} ${record.currency}`,
|
|
248
|
-
`Order total: ${recorded}`,
|
|
249
|
-
`Difference: ${(record.amount - recorded).toFixed(2)}`,
|
|
250
|
-
].join("\n"),
|
|
251
|
-
entityType: "order",
|
|
252
|
-
entityId: order.id,
|
|
253
|
-
amountAtRisk: Math.abs(record.amount - recorded),
|
|
254
|
-
orderIds: [order.id],
|
|
255
|
-
gatewayPaymentId: record.id,
|
|
256
|
-
now: ctx.now,
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
/*
|
|
260
|
-
* Refund check — the gateway's `amount_refunded` against our own
|
|
261
|
-
* non-failed refund events. `committedRefundTotal` is the same helper
|
|
262
|
-
* the refund cap uses, so "what we think we refunded" has ONE
|
|
263
|
-
* definition rather than a second one written here.
|
|
264
|
-
*/
|
|
265
|
-
const ours = committedRefundTotal(order);
|
|
266
|
-
if (Math.abs(ours - record.amountRefunded) > MONEY_EPSILON) {
|
|
267
|
-
refundMismatch += 1;
|
|
268
|
-
await paymentCasesRepository().openOrObserve({
|
|
269
|
-
kind: "refund_mismatch",
|
|
270
|
-
subjectId: `${record.id}:refunds`,
|
|
271
|
-
severity: record.amountRefunded > ours ? "error" : "warning",
|
|
272
|
-
title: `Refund total disagrees with the gateway — ${order.id}`,
|
|
273
|
-
detail: [
|
|
274
|
-
record.amountRefunded > ours
|
|
275
|
-
? `🛑 The gateway has refunded MORE than our records show. Money has left that we did not record.`
|
|
276
|
-
: `Our records show more refunded than the gateway did — a refund may have been reserved and never completed.`,
|
|
277
|
-
"",
|
|
278
|
-
`Gateway: ${record.gateway}`,
|
|
279
|
-
`Payment: ${record.id}`,
|
|
280
|
-
`Order: ${order.id}`,
|
|
281
|
-
`Gateway refunded: ${record.amountRefunded}`,
|
|
282
|
-
`Our committed refunds: ${ours}`,
|
|
283
|
-
`Refundable base: ${refundableBase(order)}`,
|
|
284
|
-
record.refundStatus ? `Gateway refund status: ${record.refundStatus}` : null,
|
|
285
|
-
]
|
|
286
|
-
.filter((l) => l !== null)
|
|
287
|
-
.join("\n"),
|
|
288
|
-
entityType: "order",
|
|
289
|
-
entityId: order.id,
|
|
290
|
-
amountAtRisk: Math.abs(record.amountRefunded - ours),
|
|
291
|
-
orderIds: [order.id],
|
|
292
|
-
gatewayPaymentId: record.id,
|
|
293
|
-
now: ctx.now,
|
|
294
|
-
});
|
|
295
|
-
}
|
|
227
|
+
const outcome = await reconcilePaymentAgainstOrder(record, order, ctx);
|
|
228
|
+
amountMismatch += outcome.amountMismatch;
|
|
229
|
+
refundMismatch += outcome.refundMismatch;
|
|
296
230
|
}
|
|
297
231
|
}
|
|
298
232
|
catch (err) {
|
|
@@ -304,6 +238,89 @@ async function reconcileGatewayPayments(ctx) {
|
|
|
304
238
|
}
|
|
305
239
|
return { examined, missing, amountMismatch, refundMismatch, reads };
|
|
306
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
|
+
}
|
|
307
324
|
/* ──────────────── R3: refund claimed, never settled ─────────────────────── */
|
|
308
325
|
/**
|
|
309
326
|
* A `refundClaims` row still `claimed` an hour later is a crash between
|
|
@@ -321,8 +338,8 @@ async function reconcileStaleRefundClaims(ctx) {
|
|
|
321
338
|
try {
|
|
322
339
|
const snap = await ctx.db
|
|
323
340
|
.collection(REFUND_CLAIMS_COLLECTION)
|
|
324
|
-
.where(
|
|
325
|
-
.where(
|
|
341
|
+
.where(REFUND_CLAIM_FIELDS.STATE, "==", "claimed")
|
|
342
|
+
.where(REFUND_CLAIM_FIELDS.CLAIMED_AT, "<", cutoff)
|
|
326
343
|
.limit(50)
|
|
327
344
|
.get();
|
|
328
345
|
for (const doc of snap.docs) {
|
|
@@ -404,9 +421,9 @@ async function reconcileUnpaidCancellations(ctx) {
|
|
|
404
421
|
let stale = 0;
|
|
405
422
|
try {
|
|
406
423
|
const snap = await ctx.db
|
|
407
|
-
.collection(
|
|
408
|
-
.where(
|
|
409
|
-
.where(
|
|
424
|
+
.collection(ORDER_COLLECTION)
|
|
425
|
+
.where(ORDER_FIELDS.STATUS, "==", "cancelled")
|
|
426
|
+
.where(ORDER_FIELDS.REFUND_STATUS, "==", "pending")
|
|
410
427
|
.limit(100)
|
|
411
428
|
.get();
|
|
412
429
|
/*
|
|
@@ -14,7 +14,7 @@ import type { JobContext } from "../runtime/types";
|
|
|
14
14
|
* (This used to be shared with testerSandboxRefresh.ts, which was removed when
|
|
15
15
|
* the tester runner took over fixture state — see scheduled.ts.)
|
|
16
16
|
*/
|
|
17
|
-
export declare const TEST_DATA_COLLECTIONS: readonly ["categories", "stores", "products", "blogPosts", "events"];
|
|
17
|
+
export declare const TEST_DATA_COLLECTIONS: readonly ["categories", "stores", "products", "blogPosts", "events", "orders", "obligations", "protectionClaims"];
|
|
18
18
|
export interface TestDataCleanupOptions {
|
|
19
19
|
/** true = wipe every isTestData:true doc regardless of testDataExpiresAt (manual force-purge). */
|
|
20
20
|
force?: boolean;
|
|
@@ -41,7 +41,28 @@ import { BID_FIELDS } from "../../../../constants/field-names.js";
|
|
|
41
41
|
* is both `test` AND `isTestData`. `audit-seed-purpose` R6 now asserts every
|
|
42
42
|
* entry here declares the field, so the `offers` shape cannot return.
|
|
43
43
|
*/
|
|
44
|
-
|
|
44
|
+
/*
|
|
45
|
+
* 🛑 Every schema that declares `isTestData` must appear here, or the field is
|
|
46
|
+
* WRITE-ONLY and the row is never swept — a flag that reads as "this will be
|
|
47
|
+
* cleaned up" and never is.
|
|
48
|
+
*
|
|
49
|
+
* `orders`, `obligations` and `protectionClaims` were added 2026-10-22 after
|
|
50
|
+
* `audit-seed-purpose`'s inverse rule found all three declaring the field with
|
|
51
|
+
* no entry. They are the propagation chain: an order carries the flag, writing
|
|
52
|
+
* an obligation copies it (`orders.repository.ts` → `repo.open({ …isTestData
|
|
53
|
+
* })`), and a claim inherits it in turn — so a test order left three
|
|
54
|
+
* generations of descendants behind that nothing would ever collect.
|
|
55
|
+
*/
|
|
56
|
+
export const TEST_DATA_COLLECTIONS = [
|
|
57
|
+
"categories",
|
|
58
|
+
"stores",
|
|
59
|
+
"products",
|
|
60
|
+
"blogPosts",
|
|
61
|
+
"events",
|
|
62
|
+
"orders",
|
|
63
|
+
"obligations",
|
|
64
|
+
"protectionClaims",
|
|
65
|
+
];
|
|
45
66
|
const BID_CHUNK_SIZE = 30; // Firestore `in` query cap
|
|
46
67
|
export async function runTestDataCleanup(ctx, opts = {}) {
|
|
47
68
|
const cutoff = opts.force ? null : new Date();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { allocateShipmentCosts } from "../../../../features/shipments/utils/cost-allocation.js";
|
|
2
2
|
import { siteSettingsRepository } from "../../../../features/admin/repository/site-settings.repository.js";
|
|
3
|
+
import { safeRead } from "../../../../errors/safe-read.js";
|
|
3
4
|
/*
|
|
4
5
|
* ── D58: the admin saw 6 while this used 4 ───────────────────────────────
|
|
5
6
|
*
|
|
@@ -97,10 +98,21 @@ async function recomputeShipmentAllocation(shipmentId, ctx) {
|
|
|
97
98
|
* single shipment, not a sweep, so the read is bounded — and the
|
|
98
99
|
* alternative (keep ignoring the admin control) is the defect.
|
|
99
100
|
*/
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
/*
|
|
102
|
+
* `safeRead`, not `.catch(() => undefined)`.
|
|
103
|
+
*
|
|
104
|
+
* The fallback is unchanged — a settings read that fails must not stop the
|
|
105
|
+
* allocation, and `settingsMaxHours` being absent simply means "no admin
|
|
106
|
+
* ceiling". What changes is that the failure is now RECORDED: a bare catch
|
|
107
|
+
* returning `undefined` is indistinguishable from an admin who never set a
|
|
108
|
+
* ceiling, so the labour cap could silently stop being enforced with
|
|
109
|
+
* nothing anywhere to say so. `installDegradedReadReporter` persists it.
|
|
110
|
+
*/
|
|
111
|
+
const laborSettings = await safeRead(() => siteSettingsRepository.getSingleton().then((s) => s.laborRate), {
|
|
112
|
+
route: "job:onShipmentAllocationSync",
|
|
113
|
+
key: "siteSettings:laborRate",
|
|
114
|
+
fallback: undefined,
|
|
115
|
+
});
|
|
104
116
|
const settingsMaxHours = laborSettings?.maxHoursPerDay;
|
|
105
117
|
const { perLot, totals } = allocateShipmentCosts({
|
|
106
118
|
lots: allocationInputs,
|
|
@@ -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": {
|