@mohasinac/appkit 4.60.1 → 4.61.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/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 +84 -57
- 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/features/promotions/schema.d.ts +66 -0
- package/dist/_internal/shared/features/promotions/schema.js +7 -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/core/unit-of-work.js +15 -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/admin/components/AdminCouponEditorView.js +8 -0
- package/dist/features/admin/components/AdminGatewayEventsView.js +2 -2
- package/dist/features/admin/components/DataListingView.js +2 -0
- package/dist/features/admin/components/analytics/AdminStatCard.js +4 -1
- 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/auctions/components/MarketplaceAuctionCard.js +1 -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/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 +72 -45
- 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/obligations/repository/obligations.repository.js +10 -1
- package/dist/features/orders/schemas/firestore.d.ts +19 -0
- package/dist/features/orders/schemas/firestore.js +19 -0
- 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/MarketplaceBundleCard.js +1 -1
- package/dist/features/products/components/MarketplacePrizeDrawCard.js +1 -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/schemas/coupon-form.d.ts +5 -0
- package/dist/features/seller/schemas/coupon-form.js +18 -0
- package/dist/features/shell/SectionForm.js +31 -9
- package/dist/features/sticker-trace/server.d.ts +2 -0
- package/dist/http/api-handler.js +4 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/next/routing/route-map.d.ts +0 -2
- package/dist/next/routing/route-map.js +10 -1
- package/dist/schemas/registry.d.ts +11 -6
- package/dist/server.d.ts +0 -1
- package/dist/server.js +5 -3
- package/dist/styles.css +51 -0
- package/dist/ui/components/PaginatedSelect.js +0 -0
- 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
|
@@ -3,7 +3,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
3
3
|
import { Row } from "@mohasinac/appkit/ui";
|
|
4
4
|
import { useRef } from "react";
|
|
5
5
|
import { useTranslations } from "next-intl";
|
|
6
|
-
import { Alert, Checkbox, Div, FormField, FormGroup, Heading, Label, RichTextEditor, Stack, TagInput, Text, Toggle, } from "../../../ui/index.js";
|
|
6
|
+
import { Alert, Checkbox, Div, FormField, FormGroup, Heading, Label, Span, RichTextEditor, Stack, TagInput, Text, Toggle, } from "../../../ui/index.js";
|
|
7
7
|
import { isFinalSale } from "../constants/final-sale.js";
|
|
8
8
|
import { PRODUCT_MAX_IMAGES, PRODUCT_MAX_VIDEOS, PRODUCT_IMAGE_INDEX_MAX, } from "../../../_internal/shared/media/limits.js";
|
|
9
9
|
import { CustomSectionsEditor } from "./CustomSectionsEditor.js";
|
|
@@ -313,5 +313,5 @@ canEditTax = false, renderDescriptionEditor, renderCategorySelector, renderPrice
|
|
|
313
313
|
disabled: isReadonly,
|
|
314
314
|
}), !isAuctionListing(product) && renderGroupSettings?.(product), _jsx(ProductFeaturesSelector, { value: product.features ?? [], onChange: (features) => update({ features }), productType: LISTING_TYPE_TO_FEATURE_PRODUCT_TYPE[normalizeListingType(product)], storeId: product.storeId, disabled: isReadonly }), _jsxs(Div, { children: [_jsx(Heading, { level: 3, className: "mb-1", color: "muted", size: "sm", weight: "semibold", children: "Custom Sections" }), _jsx(Text, { className: "mb-3", color: "muted", size: "xs", children: "Add up to 3 custom tabs to your product page \u2014 e.g. \"Box Contents\", \"Compatibility\", \"Grading Details\"." }), _jsx(CustomSectionsEditor, { sections: product.customSections ?? [], onChange: (sections) => update({ customSections: sections }) })] }), _jsx(FormField, { name: "shippingInfo", label: t("formShipping"), type: "textarea", value: product.shippingInfo || "", onChange: (value) => update({ shippingInfo: value }), disabled: isReadonly, placeholder: "Shipping information..." }), _jsxs(Div, { children: [_jsx(Toggle, { checked: !isFinalSale(product), onChange: (checked) => update({ finalSale: !checked }), disabled: isReadonly, label: "Accept change-of-mind returns" }), _jsx(Text, { className: "mt-1", size: "xs", color: "muted", children: isFinalSale(product)
|
|
315
315
|
? "Final sale (default). Not-received, damaged, wrong-item, not-as-described and counterfeit claims are still accepted."
|
|
316
|
-
: "Buyers can return this listing within the platform return window for any reason." })] }), _jsx(FormField, { name: "returnPolicy", label: t("formReturnPolicy"), type: "textarea", value: product.returnPolicy || "", onChange: (value) => update({ returnPolicy: value }), disabled: isReadonly, placeholder: "Return policy details..." }), _jsxs(Div, { children: [_jsx(Heading, { level: 3, className: "mb-1", color: "muted", size: "sm", weight: "semibold", children: "Physical Inventory" }), existingLocation ? (_jsxs(Text, { className: "mb-3", color: "muted", size: "xs", children: ["Legacy location on this listing: ", _jsx("
|
|
316
|
+
: "Buyers can return this listing within the platform return window for any reason." })] }), _jsx(FormField, { name: "returnPolicy", label: t("formReturnPolicy"), type: "textarea", value: product.returnPolicy || "", onChange: (value) => update({ returnPolicy: value }), disabled: isReadonly, placeholder: "Return policy details..." }), _jsxs(Div, { children: [_jsx(Heading, { level: 3, className: "mb-1", color: "muted", size: "sm", weight: "semibold", children: "Physical Inventory" }), existingLocation ? (_jsxs(Text, { className: "mb-3", color: "muted", size: "xs", children: ["Legacy location on this listing: ", _jsx(Span, { weight: "semibold", children: existingLocation }), ". It is still printed on labels, but it is free text with no box behind it \u2014 place this item in a storage box to replace it."] })) : (_jsx(Text, { className: "mb-3", color: "muted", size: "xs", children: "Where this item is stored is recorded by placing it in a storage box, not typed here \u2014 a placement has a quantity, and one product can sit in two boxes. Use the receive station or the box's own page." }))] }), product.storeName && (_jsx(FormField, { name: "storeName", label: t("formSeller"), type: "text", value: product.storeName, onChange: () => { }, disabled: true }))] }));
|
|
317
317
|
}
|
|
@@ -184,7 +184,7 @@ export function ProductCard({ product, href, onClick, onAddToWishlist, isWishlis
|
|
|
184
184
|
* `justify-end` keeps it on the side the eye already uses for card actions;
|
|
185
185
|
* `pt-0` because the card body's own bottom padding is the gap.
|
|
186
186
|
*/
|
|
187
|
-
return (_jsxs(Div, {
|
|
187
|
+
return (_jsxs(Div, { layout: "flex-col", className: "h-full", children: [wrapped, _jsx(Div, { layout: "flex", justify: "end", className: "px-[var(--appkit-space-3)] pb-[var(--appkit-space-3)]", children: ownerActions })] }));
|
|
188
188
|
}
|
|
189
189
|
function ProductListRow({ product, onClick, onAddToWishlist, isWishlisted, }) {
|
|
190
190
|
const discount = product.originalPrice && product.originalPrice > product.price
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { BaseRepository, prepareForFirestore, } from "../../../providers/db-firebase/index.js";
|
|
9
9
|
import { normalizeError } from "../../../errors/normalize.js";
|
|
10
|
+
import { serverLogger } from "../../../monitoring/index.js";
|
|
10
11
|
import { CLAIMED_COUPONS_COLLECTION, createClaimedCouponId } from "../schemas/index.js";
|
|
11
12
|
import { DatabaseError } from "../../../errors/index.js";
|
|
12
13
|
import { COUPON_USAGE_FIELDS } from "../../../constants/field-names.js";
|
|
@@ -128,10 +129,39 @@ export class ClaimedCouponsRepository extends BaseRepository {
|
|
|
128
129
|
if (snap.empty)
|
|
129
130
|
return 0;
|
|
130
131
|
const writer = this.db.bulkWriter();
|
|
132
|
+
/*
|
|
133
|
+
* 🛑 `onWriteError` FIRST, before any write is queued.
|
|
134
|
+
*
|
|
135
|
+
* `BulkWriter.update()` returns a promise per write and the contract is
|
|
136
|
+
* not to await them individually — `close()` is what flushes. But an
|
|
137
|
+
* un-awaited promise that rejects is an unhandled rejection, which is
|
|
138
|
+
* what `@typescript-eslint/no-floating-promises` was reporting, and
|
|
139
|
+
* `void`-ing it would silence the lint while keeping the hazard.
|
|
140
|
+
*
|
|
141
|
+
* Registering the handler is the real fix: a failed row is RECORDED and
|
|
142
|
+
* the batch continues, which is the right behaviour for a denormalised
|
|
143
|
+
* snapshot refresh — one stale coupon card is not worth abandoning the
|
|
144
|
+
* other 499, and the method's docstring already says it must not throw.
|
|
145
|
+
*/
|
|
146
|
+
let failed = 0;
|
|
147
|
+
writer.onWriteError((err) => {
|
|
148
|
+
failed++;
|
|
149
|
+
serverLogger.warn("claimedCoupons: snapshot refresh failed for one row", {
|
|
150
|
+
documentPath: err.documentRef.path,
|
|
151
|
+
error: err.message,
|
|
152
|
+
});
|
|
153
|
+
return false; // do not retry — the next refresh picks it up
|
|
154
|
+
});
|
|
131
155
|
for (const doc of snap.docs) {
|
|
132
|
-
writer.update(doc.ref, { couponSnapshot: snapshot, updatedAt: new Date() });
|
|
156
|
+
void writer.update(doc.ref, { couponSnapshot: snapshot, updatedAt: new Date() });
|
|
133
157
|
}
|
|
134
158
|
await writer.close();
|
|
159
|
+
if (failed > 0) {
|
|
160
|
+
serverLogger.warn("claimedCoupons: snapshot refresh partially failed", {
|
|
161
|
+
failed,
|
|
162
|
+
total: snap.size,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
135
165
|
return snap.size;
|
|
136
166
|
}
|
|
137
167
|
catch (error) {
|
|
@@ -12,6 +12,21 @@ export interface DiscountConfig {
|
|
|
12
12
|
export interface BXGYConfig {
|
|
13
13
|
buyQuantity: number;
|
|
14
14
|
getQuantity: number;
|
|
15
|
+
/**
|
|
16
|
+
* Per-UNIT price ceiling, in decimal rupees. A unit priced above this does
|
|
17
|
+
* not qualify: it neither counts toward a block nor can be given free.
|
|
18
|
+
*
|
|
19
|
+
* 🛑 This is a UNIT price, not a line total and not the order total — the
|
|
20
|
+
* promotion it exists for is "buy 3 get 1 free on items under ₹2,000", and
|
|
21
|
+
* `discount.minPurchase` already covers the order-total reading. A line of
|
|
22
|
+
* quantity 3 at ₹1,500 contributes three qualifying units, not one ₹4,500
|
|
23
|
+
* item that fails the ceiling.
|
|
24
|
+
*
|
|
25
|
+
* Omitted means no ceiling, which is what every BOGO coupon written before
|
|
26
|
+
* 2026-10-22 means. Do not default it to a number — a ceiling nobody asked
|
|
27
|
+
* for would silently shrink an existing coupon's discount.
|
|
28
|
+
*/
|
|
29
|
+
maxItemPrice?: number;
|
|
15
30
|
applicableProducts?: string[];
|
|
16
31
|
applicableCategories?: string[];
|
|
17
32
|
}
|
|
@@ -194,6 +209,13 @@ export interface DiscountableLine {
|
|
|
194
209
|
*
|
|
195
210
|
* 🛑 An INCOMPLETE block earns nothing. "Buy 2 get 1" on 2 units is a block of
|
|
196
211
|
* 2 against a required 3, so the discount is 0 — not a partial credit.
|
|
212
|
+
*
|
|
213
|
+
* 🛑 `bxgy.maxItemPrice` filters the UNIT POOL before blocks are formed, so an
|
|
214
|
+
* over-ceiling unit neither counts toward a block nor can be free. Filtering
|
|
215
|
+
* only the free-unit selection instead would let four ₹5,000 tops earn a free
|
|
216
|
+
* one under an "items under ₹2,000" promotion, because the cheapest of four
|
|
217
|
+
* dear units is still dear. The ceiling defines what the promotion is ABOUT,
|
|
218
|
+
* not merely what it pays out.
|
|
197
219
|
*/
|
|
198
220
|
export declare function calculateBxgyDiscount(bxgy: BXGYConfig | undefined, lines: readonly DiscountableLine[]): number;
|
|
199
221
|
export declare function calculateDiscount(coupon: CouponDocument, orderTotal: number,
|
|
@@ -159,6 +159,13 @@ export function canUserUseCoupon(coupon, userUsageCount) {
|
|
|
159
159
|
*
|
|
160
160
|
* 🛑 An INCOMPLETE block earns nothing. "Buy 2 get 1" on 2 units is a block of
|
|
161
161
|
* 2 against a required 3, so the discount is 0 — not a partial credit.
|
|
162
|
+
*
|
|
163
|
+
* 🛑 `bxgy.maxItemPrice` filters the UNIT POOL before blocks are formed, so an
|
|
164
|
+
* over-ceiling unit neither counts toward a block nor can be free. Filtering
|
|
165
|
+
* only the free-unit selection instead would let four ₹5,000 tops earn a free
|
|
166
|
+
* one under an "items under ₹2,000" promotion, because the cheapest of four
|
|
167
|
+
* dear units is still dear. The ceiling defines what the promotion is ABOUT,
|
|
168
|
+
* not merely what it pays out.
|
|
162
169
|
*/
|
|
163
170
|
export function calculateBxgyDiscount(bxgy, lines) {
|
|
164
171
|
if (!bxgy)
|
|
@@ -169,6 +176,16 @@ export function calculateBxgyDiscount(bxgy, lines) {
|
|
|
169
176
|
// unit free, which is a misconfiguration, not a promotion.
|
|
170
177
|
if (!Number.isFinite(buy) || !Number.isFinite(get) || buy <= 0 || get <= 0)
|
|
171
178
|
return 0;
|
|
179
|
+
/*
|
|
180
|
+
* A non-finite or non-positive ceiling is a misconfiguration, and treating
|
|
181
|
+
* it as "0" would disqualify every unit and silently zero the coupon. Treat
|
|
182
|
+
* it as absent — the same thing an omitted ceiling means.
|
|
183
|
+
*/
|
|
184
|
+
const ceiling = typeof bxgy.maxItemPrice === "number" &&
|
|
185
|
+
Number.isFinite(bxgy.maxItemPrice) &&
|
|
186
|
+
bxgy.maxItemPrice > 0
|
|
187
|
+
? bxgy.maxItemPrice
|
|
188
|
+
: undefined;
|
|
172
189
|
// Expand to one entry per UNIT — a line of quantity 3 contributes 3 prices,
|
|
173
190
|
// because the free units are counted in units, not in lines.
|
|
174
191
|
const unitPrices = [];
|
|
@@ -178,6 +195,11 @@ export function calculateBxgyDiscount(bxgy, lines) {
|
|
|
178
195
|
continue;
|
|
179
196
|
if (!Number.isFinite(line.price) || line.price < 0)
|
|
180
197
|
continue;
|
|
198
|
+
// Inclusive: "under ₹2,000" is authored as maxItemPrice 1999 when the
|
|
199
|
+
// boundary matters. An exclusive test here would make a ₹2,000 ceiling
|
|
200
|
+
// reject a ₹2,000 item, which is not how a price cap reads to an admin.
|
|
201
|
+
if (ceiling !== undefined && line.price > ceiling)
|
|
202
|
+
continue;
|
|
181
203
|
for (let i = 0; i < qty; i++)
|
|
182
204
|
unitPrices.push(line.price);
|
|
183
205
|
}
|