@mohasinac/appkit 4.12.0 → 4.12.2
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/_internal/server/features/auctions/service.d.ts +7 -1
- package/dist/_internal/server/features/auctions/service.js +13 -5
- package/dist/_internal/server/features/checkout/actions.d.ts +0 -15
- package/dist/_internal/server/features/checkout/actions.js +7 -3
- package/dist/_internal/server/features/orders/actions.js +5 -3
- package/dist/_internal/server/functions/firestore.d.ts +1 -2
- package/dist/_internal/server/functions/firestore.js +1 -9
- package/dist/_internal/server/jobs/core/index.d.ts +0 -1
- package/dist/_internal/server/jobs/core/index.js +0 -1
- package/dist/_internal/server/jobs/core/offerExpiry.js +33 -1
- package/dist/_internal/server/jobs/handlers/index.d.ts +1 -2
- package/dist/_internal/server/jobs/handlers/index.js +1 -2
- package/dist/_internal/shared/features/auctions/config.d.ts +34 -0
- package/dist/_internal/shared/features/auctions/config.js +47 -3
- package/dist/_internal/shared/listing-types/auction/config.js +1 -1
- package/dist/_internal/shared/listing-types/digital-code/config.js +1 -1
- package/dist/_internal/shared/listing-types/live/config.js +1 -1
- package/dist/_internal/shared/listing-types/pre-order/config.js +1 -1
- package/dist/features/account/components/NotificationBell.js +1 -1
- package/dist/features/admin/components/AdminNotificationsView.js +4 -1
- package/dist/features/admin/components/AdminOrderEditorView.d.ts +7 -1
- package/dist/features/admin/components/AdminOrderEditorView.js +16 -4
- package/dist/features/admin/components/AdminOrdersView.js +2 -1
- package/dist/features/auctions/actions/bid-actions.js +71 -9
- package/dist/features/auctions/components/AuctionDetailPageView.js +8 -2
- package/dist/features/auctions/components/MarketplaceAuctionCard.js +3 -3
- package/dist/features/auctions/components/PlaceBidFormClient.js +89 -14
- package/dist/features/auctions/schemas/bid-input.d.ts +19 -6
- package/dist/features/auctions/schemas/bid-input.js +19 -7
- package/dist/features/auth/repository/user.repository.js +6 -0
- package/dist/features/faq/components/FAQHelpfulButtons.js +2 -2
- package/dist/features/filters/FilterFacetSection.js +1 -1
- package/dist/features/homepage/components/WhatsAppCommunitySection.js +1 -1
- package/dist/features/layout/BackToTop.js +1 -1
- package/dist/features/layout/BottomActions.js +1 -1
- package/dist/features/layout/FooterLayout.js +2 -2
- package/dist/features/layout/TitleBarLayout.js +1 -1
- package/dist/features/pre-orders/components/MarketplacePreorderCard.js +3 -3
- package/dist/features/products/components/MarketplaceBundleCard.js +2 -2
- package/dist/features/products/components/MarketplacePrizeDrawCard.js +2 -2
- package/dist/features/products/components/PrizeDrawCollage.js +1 -1
- package/dist/features/products/components/PrizeDrawItemsEditor.js +1 -1
- package/dist/features/products/components/ProductDetailPageView.js +1 -1
- package/dist/features/products/components/ProductGrid.js +3 -3
- package/dist/features/products/repository/products.repository.d.ts +0 -4
- package/dist/features/products/repository/products.repository.js +0 -15
- package/dist/features/promotions/repository/coupons.repository.d.ts +12 -0
- package/dist/features/promotions/repository/coupons.repository.js +12 -0
- package/dist/features/seller/components/SellerSidebar.js +1 -1
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +279 -0
- package/dist/features/whatsapp-bot/components/WhatsAppChatButton.js +1 -1
- package/dist/jobs.d.ts +1 -1
- package/dist/jobs.js +1 -1
- package/dist/seed/homepage-sections-seed-data.js +5 -1
- package/dist/seed/site-settings-seed-data.js +18 -0
- package/dist/styles.css +55 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/tokens/color-pairs.js +1 -0
- package/dist/tokens/tokens.css +33 -0
- package/dist/ui/components/Button.style.css +21 -0
- package/dist/ui/components/ImageLightbox.js +7 -4
- package/dist/ui/components/surface-tokens.d.ts +7 -0
- package/dist/ui/components/surface-tokens.js +7 -0
- package/package.json +1 -1
- package/dist/_internal/server/jobs/core/onBidPlaced.d.ts +0 -15
- package/dist/_internal/server/jobs/core/onBidPlaced.js +0 -63
- package/dist/_internal/server/jobs/handlers/onBidPlaced.d.ts +0 -3
- package/dist/_internal/server/jobs/handlers/onBidPlaced.js +0 -9
|
@@ -7,7 +7,7 @@ import { ROUTES } from "../../../next";
|
|
|
7
7
|
import { formatCurrency } from "../../../utils";
|
|
8
8
|
import { getDefaultCurrency } from "../../../core/baseline-resolver";
|
|
9
9
|
const CLS_BUNDLE_PILL = "inline-flex items-center rounded-full bg-violet-600 px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-white";
|
|
10
|
-
const CLS_STOCK_OK = "bg-success-
|
|
10
|
+
const CLS_STOCK_OK = "bg-success-solid text-success-on-solid";
|
|
11
11
|
// audit-content-alignment-ok: button label text, not page content — buttons are universally center-labeled
|
|
12
12
|
const CLS_VIEW_BTN = "mt-2 w-full cursor-pointer rounded-md bg-violet-600 py-[var(--appkit-space-1-5)] text-center text-[length:var(--appkit-text-xs)] font-semibold text-white transition-colors hover:bg-violet-700 active:scale-[0.98]";
|
|
13
13
|
import { BaseListingCard, Div, Row, Span, Stack, Text, TextLink } from "../../../ui";
|
|
@@ -49,7 +49,7 @@ export function MarketplaceBundleCard({ bundle, className = "", variant = "grid"
|
|
|
49
49
|
}
|
|
50
50
|
router.push(String(detailHref));
|
|
51
51
|
}, [detailHref, onNavigate, router]);
|
|
52
|
-
return (_jsxs(BaseListingCard, { isSelected: isSelected, variant: variant, className: className, onMouseDown: !isSelected ? longPress.onMouseDown : undefined, onMouseUp: !isSelected ? longPress.onMouseUp : undefined, onMouseLeave: !isSelected ? longPress.onMouseLeave : undefined, onTouchStart: !isSelected ? longPress.onTouchStart : undefined, onTouchEnd: !isSelected ? longPress.onTouchEnd : undefined, onTouchCancel: !isSelected ? longPress.onTouchCancel : undefined, children: [_jsxs(BaseListingCard.Hero, { aspect: "square", variant: variant, children: [_jsx(TextLink, { href: String(detailHref), className: "absolute inset-0 block", children: showCollage ? (_jsx(Div, { layout: "grid", className: `h-full w-full gap-[0.125rem] bg-[var(--appkit-color-surface-muted)] ${collageTiles.length === 2 ? "grid-cols-2 grid-rows-1" : "grid-cols-2 grid-rows-2"}`, children: collageTiles.map((tile, i) => (_jsxs(Div, { className: "relative overflow-hidden bg-[var(--appkit-color-surface-muted)]", children: [_jsx(MediaImage, { src: tile.imageURL, alt: tile.title ?? `${bundle.name} item ${i + 1}`, size: "card" }), i === collageTiles.length - 1 && overflow > 0 && (_jsxs(Row, { textWeight: "semibold", textSize: "sm", surface: "overlay-md", className: "absolute inset-0 text-white", align: "center", justify: "center", children: ["+", overflow] }))] }, `${tile.productId}-${i}`))) })) : cover ? (_jsx(Div, { className: "absolute inset-0 bg-[var(--appkit-color-surface-muted)]", children: _jsx(MediaImage, { src: cover, alt: bundle.name, size: "card" }) })) : (_jsx(Row, { className: "absolute inset-0 bg-[var(--appkit-color-surface-muted)]", align: "center", justify: "center", children: _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", children: "No image" }) })) }), discount && (_jsx(Span, { size: "xs", weight: "bold",
|
|
52
|
+
return (_jsxs(BaseListingCard, { isSelected: isSelected, variant: variant, className: className, onMouseDown: !isSelected ? longPress.onMouseDown : undefined, onMouseUp: !isSelected ? longPress.onMouseUp : undefined, onMouseLeave: !isSelected ? longPress.onMouseLeave : undefined, onTouchStart: !isSelected ? longPress.onTouchStart : undefined, onTouchEnd: !isSelected ? longPress.onTouchEnd : undefined, onTouchCancel: !isSelected ? longPress.onTouchCancel : undefined, children: [_jsxs(BaseListingCard.Hero, { aspect: "square", variant: variant, children: [_jsx(TextLink, { href: String(detailHref), className: "absolute inset-0 block", children: showCollage ? (_jsx(Div, { layout: "grid", className: `h-full w-full gap-[0.125rem] bg-[var(--appkit-color-surface-muted)] ${collageTiles.length === 2 ? "grid-cols-2 grid-rows-1" : "grid-cols-2 grid-rows-2"}`, children: collageTiles.map((tile, i) => (_jsxs(Div, { className: "relative overflow-hidden bg-[var(--appkit-color-surface-muted)]", children: [_jsx(MediaImage, { src: tile.imageURL, alt: tile.title ?? `${bundle.name} item ${i + 1}`, size: "card" }), i === collageTiles.length - 1 && overflow > 0 && (_jsxs(Row, { textWeight: "semibold", textSize: "sm", surface: "overlay-md", className: "absolute inset-0 text-white", align: "center", justify: "center", children: ["+", overflow] }))] }, `${tile.productId}-${i}`))) })) : cover ? (_jsx(Div, { className: "absolute inset-0 bg-[var(--appkit-color-surface-muted)]", children: _jsx(MediaImage, { src: cover, alt: bundle.name, size: "card" }) })) : (_jsx(Row, { className: "absolute inset-0 bg-[var(--appkit-color-surface-muted)]", align: "center", justify: "center", children: _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", children: "No image" }) })) }), discount && (_jsx(Span, { size: "xs", weight: "bold", className: "absolute left-2 top-2 bg-success-solid text-success-on-solid", rounded: "full", padding: "pill-xs", children: BUNDLE_COPY.detail.discountBadge(discount.percent) })), _jsxs(Stack, { className: "absolute right-2 top-2", align: "end", gap: "xs", children: [_jsx(Span, { size: "xs", weight: "medium", className: CLS_BUNDLE_PILL, children: mergedLabels.bundleBadge }), _jsx(Span, { layout: "inline-flex", color: "inverse", className: `${stock === "out_of_stock" ? "bg-neutral-500 " : CLS_STOCK_OK}`, rounded: "full", padding: "pill-xs", size: "xs", weight: "medium", children: stock === "out_of_stock" ? mergedLabels.outOfStockBadge : mergedLabels.inStockBadge })] }), stock === "out_of_stock" && (_jsx(Row, { surface: "overlay-xs", className: "absolute inset-0", align: "center", justify: "center", children: _jsx(Span, { color: "inverse", size: "xs", weight: "bold", className: "bg-neutral-900/80 tracking-wider", rounded: "default", padding: "pill-md", transform: "uppercase", children: mergedLabels.outOfStockBadge }) })), onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (event) => {
|
|
53
53
|
event.stopPropagation();
|
|
54
54
|
onSelect(bundle.id, !isSelected);
|
|
55
55
|
}, className: selectable || isSelected
|
|
@@ -22,12 +22,12 @@ const DEFAULT_LABELS = {
|
|
|
22
22
|
function statusVariant(status) {
|
|
23
23
|
switch (status) {
|
|
24
24
|
case "open":
|
|
25
|
-
return "bg-success-
|
|
25
|
+
return "bg-success-solid text-success-on-solid";
|
|
26
26
|
case "closed":
|
|
27
27
|
return "bg-zinc-500 text-white";
|
|
28
28
|
case "pending":
|
|
29
29
|
default:
|
|
30
|
-
return "bg-warning-
|
|
30
|
+
return "bg-warning-solid text-warning-on-solid";
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
function statusLabel(status, labels) {
|
|
@@ -22,7 +22,7 @@ import { ImageLightbox } from "../../../ui/components/ImageLightbox";
|
|
|
22
22
|
const __P = {
|
|
23
23
|
p6: "p-[var(--appkit-space-6)]",
|
|
24
24
|
};
|
|
25
|
-
const CLS_WON_STAMP = "rotate-[-12deg] rounded bg-error-
|
|
25
|
+
const CLS_WON_STAMP = "rotate-[-12deg] rounded bg-error-solid px-[var(--appkit-space-3)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-bold uppercase tracking-wider text-error-on-solid shadow";
|
|
26
26
|
function makePrizeItemClickHandler(it, idx, onItemClick, setLightboxIndex) {
|
|
27
27
|
return () => {
|
|
28
28
|
if (onItemClick) {
|
|
@@ -13,7 +13,7 @@ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
|
13
13
|
import { useCallback } from "react";
|
|
14
14
|
import { Button, Div, FormField, Heading, Input, Row, Span, Stack, Text } from "../../../ui";
|
|
15
15
|
const CLS_WARN_BOX = "rounded border border-warning/40 bg-warning-surface px-[var(--appkit-space-3)] py-[var(--appkit-space-2)] text-[length:var(--appkit-text-sm)] text-warning dark:bg-warning-surface dark:text-warning";
|
|
16
|
-
const CLS_WON_BADGE = "rounded bg-error-
|
|
16
|
+
const CLS_WON_BADGE = "rounded bg-error-solid px-[var(--appkit-space-3)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-semibold uppercase tracking-wide text-error-on-solid";
|
|
17
17
|
import { ImageUpload } from "../../media";
|
|
18
18
|
const MIN_ITEMS = 2;
|
|
19
19
|
const MAX_ITEMS = 16;
|
|
@@ -19,7 +19,7 @@ const CLS_BUNDLE_ICON = "text-success dark:text-success";
|
|
|
19
19
|
const CLS_BUNDLE_TITLE = "text-[length:var(--appkit-text-sm)] font-semibold text-success dark:text-success";
|
|
20
20
|
const CLS_BUNDLE_ROW = "flex items-center justify-between gap-[var(--appkit-space-4)] rounded-lg border border-success dark:border-success/50 bg-white dark:bg-success-surface px-[var(--appkit-space-4)] py-[var(--appkit-space-3)]";
|
|
21
21
|
const CLS_BUNDLE_LABEL = "text-[length:var(--appkit-text-xs)] text-success dark:text-success font-medium uppercase tracking-wide mb-0.5";
|
|
22
|
-
const CLS_BUNDLE_CTA = "flex-shrink-0 rounded-lg bg-success-
|
|
22
|
+
const CLS_BUNDLE_CTA = "flex-shrink-0 rounded-lg bg-success-solid hover:brightness-110 px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-semibold text-success-on-solid transition-colors";
|
|
23
23
|
import { ROUTES } from "../../../next";
|
|
24
24
|
import { getDefaultCurrency } from "../../../core/baseline-resolver";
|
|
25
25
|
import { Button, Container, Div, Heading, Main, Nav, PriceDisplay, RichText, Row, Section, Span, Stack, Text, Ul, Ol, Li, Dl, Dt, Dd, } from "../../../ui";
|
|
@@ -21,10 +21,10 @@ const __P = {
|
|
|
21
21
|
const __O = {
|
|
22
22
|
hidden: "overflow-hidden",
|
|
23
23
|
};
|
|
24
|
-
const CLS_BADGE_NEW = "rounded-full bg-error-
|
|
25
|
-
const CLS_BADGE_SALE = "rounded-full bg-success-
|
|
24
|
+
const CLS_BADGE_NEW = "rounded-full bg-error-solid px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-[10px] text-error-on-solid shadow-sm";
|
|
25
|
+
const CLS_BADGE_SALE = "rounded-full bg-success-solid px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-[10px] font-bold text-success-on-solid shadow-sm";
|
|
26
26
|
const CLS_BADGE_TRENDING = "rounded-full bg-indigo-600 px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-[10px] font-bold text-white shadow-sm";
|
|
27
|
-
const CLS_BADGE_LIMITED = "rounded-full bg-info-
|
|
27
|
+
const CLS_BADGE_LIMITED = "rounded-full bg-info-solid px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-[10px] font-bold text-info-on-solid shadow-sm";
|
|
28
28
|
const CLS_HEART_ACTIVE = "bg-[var(--appkit-color-surface-elevated)]/90 text-error hover:bg-[var(--appkit-color-surface-elevated)]";
|
|
29
29
|
const CLS_HEART_IDLE = "bg-[var(--appkit-color-surface-elevated)]/90 text-[var(--appkit-color-text-muted)] hover:text-error hover:bg-[var(--appkit-color-surface-elevated)]";
|
|
30
30
|
const CLS_STAR = "text-[11px] text-warning";
|
|
@@ -354,10 +354,6 @@ export declare class ProductRepository extends BaseRepository<ProductDocument> {
|
|
|
354
354
|
* Cloud Functions: stage a status update into a caller-owned WriteBatch.
|
|
355
355
|
*/
|
|
356
356
|
updateStatusInBatch(batch: WriteBatch, id: string, status: string): void;
|
|
357
|
-
/**
|
|
358
|
-
* Cloud Functions: stage an atomic bid-count increment into a caller-owned WriteBatch.
|
|
359
|
-
*/
|
|
360
|
-
incrementBidCountInBatch(batch: WriteBatch, productId: string, currentBid: number, leadingBidderId?: string): void;
|
|
361
357
|
/**
|
|
362
358
|
* SB6-C / SB4-H — atomically bump `prizeCurrentEntries` by `count`. The
|
|
363
359
|
* checkout transaction calls this after pool-cap validation; the reveal
|
|
@@ -439,21 +439,6 @@ export class ProductRepository extends BaseRepository {
|
|
|
439
439
|
updatedAt: serverTimestamp(),
|
|
440
440
|
});
|
|
441
441
|
}
|
|
442
|
-
/**
|
|
443
|
-
* Cloud Functions: stage an atomic bid-count increment into a caller-owned WriteBatch.
|
|
444
|
-
*/
|
|
445
|
-
incrementBidCountInBatch(batch, productId, currentBid, leadingBidderId) {
|
|
446
|
-
batch.update(this.db.collection(this.collection).doc(productId), {
|
|
447
|
-
currentBid,
|
|
448
|
-
bidCount: increment(1),
|
|
449
|
-
...(leadingBidderId && { leadingBidderId }),
|
|
450
|
-
// SB-UNI-H 2026-05-13 — flip `bidsHaveStarted` when any bid lands.
|
|
451
|
-
// Idempotent on subsequent bids; the BIN button is hidden by the PDP
|
|
452
|
-
// as soon as this is true (eBay-style hybrid auction rule).
|
|
453
|
-
bidsHaveStarted: true,
|
|
454
|
-
updatedAt: serverTimestamp(),
|
|
455
|
-
});
|
|
456
|
-
}
|
|
457
442
|
/**
|
|
458
443
|
* SB6-C / SB4-H — atomically bump `prizeCurrentEntries` by `count`. The
|
|
459
444
|
* checkout transaction calls this after pool-cap validation; the reveal
|
|
@@ -62,6 +62,18 @@ export declare class CouponsRepository extends BaseRepository<CouponDocument> {
|
|
|
62
62
|
private _fetchAndValidateCoupon;
|
|
63
63
|
/**
|
|
64
64
|
* Validate coupon against a flat order total (no cart breakdown).
|
|
65
|
+
*
|
|
66
|
+
* NOT the authoritative check. `minPurchase` here is measured against the whole
|
|
67
|
+
* amount passed in, whereas `validateCouponForCart` measures it against the
|
|
68
|
+
* subtotal of the items the coupon is actually *eligible* for — so on a mixed
|
|
69
|
+
* cart the two can disagree, and the cart one wins because it is what
|
|
70
|
+
* `/api/cart/coupon` and checkout run. It also cannot enforce
|
|
71
|
+
* `applicableProducts` / `excludeCategories`, having no items to test.
|
|
72
|
+
*
|
|
73
|
+
* Verified 2026-08-21: no UI calls this path, so the disagreement is currently
|
|
74
|
+
* unreachable. Do not wire it into a buyer-facing surface — use
|
|
75
|
+
* `validateCouponForCart` there, or a buyer will be promised a discount that
|
|
76
|
+
* checkout then refuses.
|
|
65
77
|
*/
|
|
66
78
|
validateCoupon(code: string, userId: string, orderTotal: number): Promise<{
|
|
67
79
|
valid: boolean;
|
|
@@ -190,6 +190,18 @@ export class CouponsRepository extends BaseRepository {
|
|
|
190
190
|
// ---------------------------------------------------------------------------
|
|
191
191
|
/**
|
|
192
192
|
* Validate coupon against a flat order total (no cart breakdown).
|
|
193
|
+
*
|
|
194
|
+
* NOT the authoritative check. `minPurchase` here is measured against the whole
|
|
195
|
+
* amount passed in, whereas `validateCouponForCart` measures it against the
|
|
196
|
+
* subtotal of the items the coupon is actually *eligible* for — so on a mixed
|
|
197
|
+
* cart the two can disagree, and the cart one wins because it is what
|
|
198
|
+
* `/api/cart/coupon` and checkout run. It also cannot enforce
|
|
199
|
+
* `applicableProducts` / `excludeCategories`, having no items to test.
|
|
200
|
+
*
|
|
201
|
+
* Verified 2026-08-21: no UI calls this path, so the disagreement is currently
|
|
202
|
+
* unreachable. Do not wire it into a buyer-facing surface — use
|
|
203
|
+
* `validateCouponForCart` there, or a buyer will be promised a discount that
|
|
204
|
+
* checkout then refuses.
|
|
193
205
|
*/
|
|
194
206
|
async validateCoupon(code, userId, orderTotal) {
|
|
195
207
|
const check = await this._fetchAndValidateCoupon(code, userId);
|
|
@@ -19,7 +19,7 @@ const CLS_STORE_AVATAR = "relative h-8 w-8 overflow-hidden rounded-md bg-cover b
|
|
|
19
19
|
const CLS_STORE_FALLBACK = "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-md bg-primary/10 text-[length:var(--appkit-text-sm)] font-bold text-primary";
|
|
20
20
|
const CLS_STORE_NAME = "text-[length:var(--appkit-text-sm)] font-semibold text-[var(--appkit-color-text)] truncate";
|
|
21
21
|
const CLS_NAV_ACTIVE = "bg-warning-surface dark:bg-warning-surface text-warning dark:text-warning";
|
|
22
|
-
const CLS_NAV_BADGE = "shrink-0 rounded-full bg-warning-
|
|
22
|
+
const CLS_NAV_BADGE = "shrink-0 rounded-full bg-warning-solid px-[var(--appkit-space-1-5)] py-[var(--appkit-space-0-5)] text-[10px] text-warning-on-solid leading-none";
|
|
23
23
|
const CLS_NAV_ICON_ACTIVE = "text-warning dark:text-warning";
|
|
24
24
|
function NavLink({ item, isActive, onClick }) {
|
|
25
25
|
return (_jsxs(Link, { href: item.href, onClick: onClick, className: `flex items-center justify-end gap-[var(--appkit-space-2-5)] rounded-lg px-[var(--appkit-space-3)] py-[var(--appkit-space-2)] text-[0.8125rem] font-medium leading-tight transition-colors ${isActive
|
|
@@ -336,6 +336,12 @@ const rawTesterChecklistItems = [
|
|
|
336
336
|
label: "Product image gallery thumbnails load reliably (no broken-image icons) and click-to-zoom/rotate works in the lightbox",
|
|
337
337
|
description: "The thumbnail strip occasionally showed a broken-image icon instead of the photo (transient 3rd-party fetch failures with no retry, fixed 2026-08-19). Reload an auction or product detail page a few times and confirm every thumbnail — not just the main image — renders correctly each time.",
|
|
338
338
|
},
|
|
339
|
+
{
|
|
340
|
+
key: "image-gallery-thumbnails-not-blank",
|
|
341
|
+
label: "The mini thumbnail strip under the main gallery image shows the ACTUAL photos — not empty/blank boxes",
|
|
342
|
+
description: "Fixed 2026-08-21 — the thumbnails rendered as blank bordered squares while the large main image above them was fine. Cause: a wrapper element inside the appkit <Button> primitive collapsed to zero size, so every image inside a button-shaped image tile had nothing to fill. Open \"Beyblade Burst B-01 Valkyrie\" (product-beyblade-burst-valkyrie) or any product with 2-3 photos and confirm each mini thumbnail below the main image shows its own picture, that the active one is outlined, and that clicking one swaps the large image.",
|
|
343
|
+
href: "/products/product-beyblade-burst-valkyrie",
|
|
344
|
+
},
|
|
339
345
|
{
|
|
340
346
|
key: "video-playback",
|
|
341
347
|
label: "A product's video slide opens in theater mode with playback, zoom, and rotate controls",
|
|
@@ -478,6 +484,18 @@ const rawTesterChecklistItems = [
|
|
|
478
484
|
label: "Each order row has a visible \"View Details\" button (in addition to the row itself being clickable) that opens that exact order's detail page",
|
|
479
485
|
href: "/user/orders",
|
|
480
486
|
},
|
|
487
|
+
{
|
|
488
|
+
key: "orders-type-tabs-actually-filter",
|
|
489
|
+
label: "The Type filter (All / Normal / Auction wins / Offer wins) on My Orders actually narrows the list — picking \"Auction wins\" must show only auction-won orders, not the full list",
|
|
490
|
+
description: "Fixed 2026-08-21. The tabs rendered and were clickable but the server never read the orderType filter, so every tab returned the complete unfiltered list with no error. Pick each tab in turn and confirm the row count and contents genuinely change. Against the tester sandbox, \"Auction wins\" should surface the won-auction order and \"Normal\" should not.",
|
|
491
|
+
href: "/user/orders",
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
key: "orders-normal-tab-includes-legacy",
|
|
495
|
+
label: "The \"Normal\" Type tab also lists older orders placed before order types existed — it must not silently drop them",
|
|
496
|
+
description: "Legacy orders carry no orderType value at all, so a strict database match would exclude every one of them. \"Normal\" is deliberately resolved differently for that reason. Confirm your oldest seeded orders still appear under \"Normal\", not only under \"All\".",
|
|
497
|
+
href: "/user/orders",
|
|
498
|
+
},
|
|
481
499
|
{
|
|
482
500
|
key: "dashboard-recent-orders-linked",
|
|
483
501
|
label: "The My Account dashboard's \"Recent Orders\" widget rows are clickable and each shows a \"View Details\" button linking to the correct order — not a dead, non-interactive preview",
|
|
@@ -584,6 +602,51 @@ const rawTesterChecklistItems = [
|
|
|
584
602
|
key: "bid-increment-live-tier-change",
|
|
585
603
|
label: "The displayed \"min increment\" on an open auction updates live (without a page refresh) if another bidder's bid pushes the current bid across a tier boundary",
|
|
586
604
|
},
|
|
605
|
+
{
|
|
606
|
+
key: "bid-presets-are-increment-multiples",
|
|
607
|
+
label: "The quick-bid buttons above the amount field are multiples of the auction's OWN minimum increment — never a flat +₹1 / +₹5 / +₹10",
|
|
608
|
+
description: "Fixed 2026-08-21 — the presets were always 1x/5x/10x of the effective increment, but `resolveTieredBidIncrement` treated an empty `auctionConfig.bidIncrementTiers` array as \"no rule\" and fell through to the ₹1 last-resort constant, so every auction on the site rendered +₹1 / +₹5 / +₹10. On an auction whose current bid is in the ₹100-₹1,000 band (₹100 increment) the three buttons must read +₹100 / +₹500 / +₹1,000; on one above ₹10,000 (₹1,000 increment) they must read +₹1,000 / +₹5,000 / +₹10,000. Each button also shows the resulting bid amount underneath the step.",
|
|
609
|
+
href: "/auctions/auction-tester-sandbox-cycle-1",
|
|
610
|
+
},
|
|
611
|
+
{
|
|
612
|
+
key: "bid-preset-follows-live-price",
|
|
613
|
+
label: "With a quick-bid preset selected, another bidder raising the price updates the amount in the field too — it does not leave a now-too-low number behind",
|
|
614
|
+
description: "Fixed 2026-08-21 — the amount was only ever written on mount or on button click, while the current bid keeps updating over SSE. Open the auction in two tabs, select the +1x preset in tab A, place a higher bid from tab B, then watch tab A: the preset button labels, the \"minimum next bid\" helper text AND the number in the field must all move up together. Before the fix the buttons relabelled but the field kept its stale value, so pressing Place Bid was a guaranteed \"bid must exceed the current winning bid\" rejection.",
|
|
615
|
+
href: "/auctions/auction-tester-sandbox-cycle-2",
|
|
616
|
+
},
|
|
617
|
+
{
|
|
618
|
+
key: "bid-below-current-plus-increment-rejected",
|
|
619
|
+
label: "A bid below current bid + minimum increment is rejected with a clear inline error on the amount field — and the rejection is identical whether it is typed in Custom mode or forced through the API",
|
|
620
|
+
description: "Switch the preset row to \"Custom\", type an amount between the current bid and current+increment, and submit. Expect an inline error on the field (not a toast, not a silent no-op). Also confirm an amount BELOW the current bid gives the distinct \"must exceed the current winning bid\" message rather than the increment one.",
|
|
621
|
+
href: "/auctions/auction-tester-sandbox-cycle-1",
|
|
622
|
+
},
|
|
623
|
+
{
|
|
624
|
+
key: "bid-custom-need-not-be-exact-multiple",
|
|
625
|
+
label: "In Custom mode any amount at or above the minimum is accepted — it does NOT have to be an exact multiple of the increment",
|
|
626
|
+
description: "With a ₹100 increment and a ₹1,000 current bid, ₹1,137 must be accepted (it is above the ₹1,100 minimum). The helper text under the field states this explicitly. A client-side rule claiming to enforce exact multiples existed but was a no-op; it was removed rather than made real, because the server never enforced multiples and a real client rule would have rejected bids the server accepts.",
|
|
627
|
+
href: "/auctions/auction-tester-sandbox-cycle-1",
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
key: "first-bid-can-equal-starting-bid",
|
|
631
|
+
label: "On an auction with NO bids yet, the seller's starting bid is itself an acceptable opening bid — you are not forced to bid starting bid + increment",
|
|
632
|
+
description: "Changed 2026-08-21. On a bid-free auction the card's big number is labelled \"Starting bid\" (not \"Current bid\" beside an identical \"Starting bid\"), the first quick-bid button reads \"Minimum\" rather than \"+₹0\", and bidding exactly the starting bid succeeds. Previously a ₹100 auction could only be opened at ₹110, contradicting the \"Starting bid ₹100\" label right next to the field. The increment applies from the second bid onward — confirm the second bidder IS held to current + increment.",
|
|
633
|
+
},
|
|
634
|
+
{
|
|
635
|
+
key: "first-bid-displays-at-starting-price",
|
|
636
|
+
label: "The opening bid displays at the starting price, even when the opening bidder's maximum is much higher",
|
|
637
|
+
description: "Proxy-bid semantics: the amount submitted is a MAXIMUM, and with no competition the visible price should sit at the seller's starting price. On a bid-free ₹100 auction with a ₹100 increment, bid ₹500 — the auction's current bid must then read ₹100, not ₹200. Place a second, competing bid from another account and confirm the price then steps up in increments against that ₹500 proxy maximum as expected.",
|
|
638
|
+
},
|
|
639
|
+
{
|
|
640
|
+
key: "bid-count-increments-by-one",
|
|
641
|
+
label: "Placing one bid increases the auction's bid count by exactly ONE, and the current bid never moves backwards",
|
|
642
|
+
description: "Fixed 2026-08-21 — the `onBidPlaced` Firestore trigger duplicated work `placeBid` already did atomically: it incremented the bid count a second time (so one bid read as two), force-marked every new bid as winning, and overwrote the current bid with the new bid's amount. On the proxy path where a new bid LOSES to a standing higher maximum, that last part actually lowered the current bid to the loser's amount and named the loser as the leading bidder. Place a single bid and confirm the count goes up by 1, not 2. Then, from a second account, bid BELOW the standing proxy maximum: your bid must be recorded as outbid, and the auction's current bid must go UP (or hold), never down.",
|
|
643
|
+
href: "/auctions/auction-tester-sandbox-cycle-3",
|
|
644
|
+
},
|
|
645
|
+
{
|
|
646
|
+
key: "outbid-notification-goes-to-outbid-user",
|
|
647
|
+
label: "The outbid notification goes to the bidder who actually lost the lead — and only when someone genuinely takes the lead from them",
|
|
648
|
+
description: "Fixed 2026-08-21 alongside bid-count-increments-by-one — the notification used to be sent from a Firestore trigger that re-read \"who is winning\" AFTER the fact, racing the write that had just changed it, so it could notify the wrong account or nobody. It now fires from the same code path that decides the outcome. Check both directions: (a) outbid the leader and confirm THEY get the notification; (b) place a bid that loses to a standing proxy maximum and confirm the existing leader is NOT sent an \"outbid\" notification, since they never lost the lead.",
|
|
649
|
+
},
|
|
587
650
|
],
|
|
588
651
|
},
|
|
589
652
|
{
|
|
@@ -836,6 +899,12 @@ const rawTesterChecklistItems = [
|
|
|
836
899
|
description: "Repeat on COD, UPI-manual and Razorpay. Acceptance test for the whole pricing change — if any two of those four disagree, something is reading a different source than it charges from.",
|
|
837
900
|
href: "/checkout",
|
|
838
901
|
},
|
|
902
|
+
{
|
|
903
|
+
key: "addons-follow-cart-not-checkout-request",
|
|
904
|
+
label: "Add-on fees are charged strictly according to the per-store checkboxes on the cart page — leaving the cart and returning, or re-entering checkout, never adds or drops an add-on on its own",
|
|
905
|
+
description: "Hardened 2026-08-21. The add-on booleans used to be accepted in the checkout request body as well as being stored per-store on the cart, so two sources could disagree about what the buyer had actually selected — and the cart's selection is the only one the buyer ever saw. The request body no longer carries them at all. To test: tick gift wrap for seller A only, go to checkout and note the Total, press Back to the cart, then return to checkout — the same single gift-wrap fee must still be there, for seller A only, with an unchanged Total. Repeat having ticked nothing and confirm no add-on line ever appears.",
|
|
906
|
+
href: "/cart",
|
|
907
|
+
},
|
|
839
908
|
],
|
|
840
909
|
},
|
|
841
910
|
{
|
|
@@ -1206,12 +1275,49 @@ const rawTesterChecklistItems = [
|
|
|
1206
1275
|
{ key: "seller-quick-add-drawer-flips", label: "With Left-hand mode ON, the seller's quick-add-listing side drawer opens from the left instead of the right" },
|
|
1207
1276
|
],
|
|
1208
1277
|
},
|
|
1278
|
+
{
|
|
1279
|
+
pageKey: "seller-bids-bundles-filters",
|
|
1280
|
+
pageLabel: "Seller — Bids & Bundles Filtering",
|
|
1281
|
+
href: "/store/bids",
|
|
1282
|
+
cases: [
|
|
1283
|
+
{
|
|
1284
|
+
key: "seller-bids-status-filter",
|
|
1285
|
+
label: "The status filter on the seller Bids page actually narrows the list",
|
|
1286
|
+
description: "Fixed 2026-08-21. The status chips, the sort dropdown AND the bidder search box on this page were all inert — the server read none of the three, so every control silently returned the same unfiltered list. Pick a status and confirm the rows genuinely change.",
|
|
1287
|
+
href: "/store/bids",
|
|
1288
|
+
},
|
|
1289
|
+
{
|
|
1290
|
+
key: "seller-bids-sort-dropdown",
|
|
1291
|
+
label: "Changing the Sort dropdown on the seller Bids page reorders the rows",
|
|
1292
|
+
description: "The sort was hardcoded to bid date server-side regardless of what the dropdown said. Switch to a different sort (e.g. bid amount) and confirm the ordering visibly changes.",
|
|
1293
|
+
href: "/store/bids",
|
|
1294
|
+
},
|
|
1295
|
+
{
|
|
1296
|
+
key: "seller-bids-bidder-search",
|
|
1297
|
+
label: "Typing a bidder's name into the search box on the seller Bids page and pressing Enter narrows the list to that bidder",
|
|
1298
|
+
description: "Search matches from the START of the bidder name (a database limitation — mid-name fragments will not match), so type the first part of the name rather than a middle fragment.",
|
|
1299
|
+
href: "/store/bids",
|
|
1300
|
+
},
|
|
1301
|
+
{
|
|
1302
|
+
key: "seller-bundles-active-filter",
|
|
1303
|
+
label: "The Active / Inactive and \"Sold out\" chips on the seller Bundles page actually narrow the list",
|
|
1304
|
+
description: "Fixed 2026-08-21 — these chips existed and were clickable but the seller endpoint ignored them entirely, unlike the admin Bundles page which honoured the same chips. Compare the two pages: they should now behave identically.",
|
|
1305
|
+
href: "/store/bundles",
|
|
1306
|
+
},
|
|
1307
|
+
],
|
|
1308
|
+
},
|
|
1209
1309
|
{
|
|
1210
1310
|
pageKey: "seller-orders",
|
|
1211
1311
|
pageLabel: "Seller Order Management & Shipping/Tracking",
|
|
1212
1312
|
href: "/store/orders",
|
|
1213
1313
|
cases: [
|
|
1214
1314
|
{ key: "view-orders", label: "Seller order list shows accurate incoming orders", href: "/store/orders" },
|
|
1315
|
+
{
|
|
1316
|
+
key: "seller-auction-forfeit-notification",
|
|
1317
|
+
label: "When a winning bidder fails to pay by the deadline, the SELLER also receives a notification that the win was forfeited and the item is unsold",
|
|
1318
|
+
description: "Added 2026-08-21. Only the buyer was told, so to a seller a forfeited win looked like a completed sale that simply never paid out. Check the seller's notification bell after an auction win lapses — the message should say the item can be relisted or offered to the next highest bidder.",
|
|
1319
|
+
href: "/store/orders",
|
|
1320
|
+
},
|
|
1215
1321
|
{
|
|
1216
1322
|
key: "seller-order-manual-payment-badge",
|
|
1217
1323
|
label: "A manual-payment (UPI/Cash) order's seller detail shows a payment badge — Awaiting payment / Awaiting verification / Verified / Re-upload requested / Rejected — next to the Payment heading, plus the UTR once the buyer submits one",
|
|
@@ -1570,6 +1676,17 @@ const rawTesterChecklistItems = [
|
|
|
1570
1676
|
{ key: "error-states", label: "Error states (404, form validation) look correct" },
|
|
1571
1677
|
{ key: "card-row-heights-aligned", label: "Product/auction/pre-order/event cards in the same row are the same height, with their action button (Reserve now, Place bid, View details, etc.) aligned along a common bottom edge even when one card's description is longer than another's" },
|
|
1572
1678
|
{ key: "image-watermark-subtle", label: "Product and listing images show a small, subtle watermark (not oversized or fully opaque, doesn't obscure the image)" },
|
|
1679
|
+
{
|
|
1680
|
+
key: "clickable-image-tiles-not-blank",
|
|
1681
|
+
label: "Every CLICKABLE image tile across the site shows its picture, not an empty box — check all six surfaces listed below",
|
|
1682
|
+
description: "Fixed 2026-08-21. All of these are the same shape (a small image inside a clickable tile) and all broke together, so they must be re-checked together: (1) the mini thumbnail strip under a product's main gallery image; (2) the \"Photos (N)\" grid on a review detail page, AND the thumbnail strip along the bottom of that page's photo lightbox; (3) the image thumbnails inside a review popup/modal; (4) the tile grid on a prize-draw's collage of prizes; (5) the tile grid on a bundle's collage of included items; (6) the \"choose an existing file\" grid in the media picker when adding an image as a seller/admin. In every case the picture itself must be visible — a bordered but empty square is a fail.",
|
|
1683
|
+
href: "/products/product-beyblade-burst-valkyrie",
|
|
1684
|
+
},
|
|
1685
|
+
{
|
|
1686
|
+
key: "icon-button-label-spacing",
|
|
1687
|
+
label: "Buttons that show an icon next to their text have a normal gap between the icon and the word — the two are not jammed together",
|
|
1688
|
+
description: "Same 2026-08-21 fix as the blank image tiles: the button's spacing setting had stopped reaching its contents, so icon+label buttons were rendering with no gap at all. Scan a few pages with icon buttons (product detail actions, dashboard toolbars, the cart) and confirm the spacing looks deliberate rather than cramped.",
|
|
1689
|
+
},
|
|
1573
1690
|
{ key: "section-cta-buttons-visible", label: "Homepage section \"View all →\" / \"Go to…\" buttons use a solid primary-colored fill so they're clearly identifiable as clickable CTAs, not a plain white/outline box that blends into the page" },
|
|
1574
1691
|
{
|
|
1575
1692
|
key: "mobile-search-bar-proportions",
|
|
@@ -1670,6 +1787,105 @@ const rawTesterChecklistItems = [
|
|
|
1670
1787
|
},
|
|
1671
1788
|
],
|
|
1672
1789
|
},
|
|
1790
|
+
{
|
|
1791
|
+
pageKey: "status-badge-legibility",
|
|
1792
|
+
pageLabel: "Listing Tags & Status Chips — Light vs Dark Mode",
|
|
1793
|
+
href: "/products",
|
|
1794
|
+
cases: [
|
|
1795
|
+
{
|
|
1796
|
+
key: "listing-type-tags-readable-light",
|
|
1797
|
+
label: "IN LIGHT MODE: on the main Products grid, every listing-type tag on a card — Auction, Pre-Order, Live Item, Digital Code, Prize Draw, Classified, Art Print, Sticker Sheet — is clearly readable, with a solid colored pill behind dark-enough text",
|
|
1798
|
+
description: "This is the headline fix for 2026-08-21. Reported as \"the auction tag / live tag is white text on a white pill, or a faint grey you cannot read.\" Switch the site to LIGHT mode first (theme toggle in the header) — the bug only appeared there. Read every tag out loud; a tag you have to squint at, tilt the screen for, or that shows text with no pill behind it at all, is still a bug.",
|
|
1799
|
+
href: "/products",
|
|
1800
|
+
},
|
|
1801
|
+
{
|
|
1802
|
+
key: "listing-type-tags-readable-dark",
|
|
1803
|
+
label: "IN DARK MODE: the same listing-type tags on the Products grid are still clearly readable — fixing light mode must not have broken dark mode",
|
|
1804
|
+
description: "Regression check, and a real risk here: the two modes use inverted color tokens, so it is genuinely possible to fix one and break the other. Toggle between light and dark a few times on the same page and confirm every tag survives both.",
|
|
1805
|
+
href: "/products",
|
|
1806
|
+
},
|
|
1807
|
+
{
|
|
1808
|
+
key: "live-item-tag-has-background",
|
|
1809
|
+
label: "The \"Live Item\" tag has an actual colored pill behind it — not bare red text floating directly on the product photo with no background",
|
|
1810
|
+
description: "This tag was a separate bug from the others: its background color name did not exist at all, so the browser threw the style away and rendered nothing behind the text. Easiest to spot on a card whose photo is busy or light-colored.",
|
|
1811
|
+
href: "/live",
|
|
1812
|
+
},
|
|
1813
|
+
{
|
|
1814
|
+
key: "promo-badges-readable",
|
|
1815
|
+
label: "IN LIGHT MODE: the small promotional badges on product cards — NEW, SALE, LIMITED, the bundle discount percentage, an auction's \"Ending soon\" / \"Reserve\" pill — are all readable against their colored background",
|
|
1816
|
+
href: "/products",
|
|
1817
|
+
},
|
|
1818
|
+
{
|
|
1819
|
+
key: "notification-count-bubbles-readable",
|
|
1820
|
+
label: "The unread-count bubbles (the small number circles on the header bell icon, the Messages nav item, and the cart/bottom action bar) show a readable number in BOTH light and dark mode — not a colored blob with an invisible digit",
|
|
1821
|
+
description: "You need at least one unread notification or message for the bubble to appear. Check both themes.",
|
|
1822
|
+
href: "/user/messages",
|
|
1823
|
+
},
|
|
1824
|
+
{
|
|
1825
|
+
key: "admin-status-chips-colored",
|
|
1826
|
+
label: "Admin and seller listing pages show status chips (Pending / Verified / Resolved / High priority / Featured / Sale, etc.) as genuinely COLORED pills — amber for pending, green for success, red for problems — not plain uncolored text",
|
|
1827
|
+
description: "Found 2026-08-21: a configuration gap meant every one of these colored chips authored inside the shared component library compiled to no CSS at all, so they had silently always rendered as plain unstyled text. Spot-check across a few pages: /admin/support-tickets, /admin/scammers, /admin/orders, /store/orders.",
|
|
1828
|
+
href: "/admin/support-tickets",
|
|
1829
|
+
},
|
|
1830
|
+
{
|
|
1831
|
+
key: "faq-helpful-buttons-readable",
|
|
1832
|
+
label: "On a FAQ, after clicking Yes or No on \"Was this helpful?\", the button you picked stays readable in BOTH light and dark mode — the selected button is a solid color with legible text, not white-on-pale",
|
|
1833
|
+
href: "/faqs",
|
|
1834
|
+
},
|
|
1835
|
+
{
|
|
1836
|
+
key: "detail-page-tags-readable",
|
|
1837
|
+
label: "Opening an individual auction, pre-order, prize draw, classified, digital code and live-item page, each one's type tag and status chips are readable in light mode",
|
|
1838
|
+
description: "Card grids and detail pages don't always share the same component, so check both. Use the sandbox listings linked from the Tester Hub if you need one of each type.",
|
|
1839
|
+
href: "/auctions/auction-beyblade-original-dragoon-storm",
|
|
1840
|
+
},
|
|
1841
|
+
{
|
|
1842
|
+
key: "whatsapp-community-member-pill",
|
|
1843
|
+
label: "IN LIGHT MODE: on the homepage's green WhatsApp community card, the member-count pill in its top-right corner (e.g. \"5,000+ members\") is readable — white text on a translucent darker pill, not white text on a white pill",
|
|
1844
|
+
description: "Reported alongside the tag bug: this pill was invisible in light mode. The people icon next to the number should be visible too.",
|
|
1845
|
+
href: "/",
|
|
1846
|
+
},
|
|
1847
|
+
{
|
|
1848
|
+
key: "image-lightbox-close-hover",
|
|
1849
|
+
label: "Opening a product image in the full-screen lightbox and hovering the X close button, the X stays visible as the button turns red — it does not vanish on hover",
|
|
1850
|
+
href: "/products/product-beyblade-original-dranzer-s",
|
|
1851
|
+
},
|
|
1852
|
+
],
|
|
1853
|
+
},
|
|
1854
|
+
{
|
|
1855
|
+
pageKey: "back-to-top-button",
|
|
1856
|
+
pageLabel: "Back-to-Top Button",
|
|
1857
|
+
href: "/products",
|
|
1858
|
+
cases: [
|
|
1859
|
+
{
|
|
1860
|
+
key: "appears-and-clickable-on-long-page",
|
|
1861
|
+
label: "Scrolling down a long listing page, the floating back-to-top arrow appears in the bottom corner AND actually responds to a click — it is not sitting behind the bottom navigation bar, a sticky toolbar, or a buy bar",
|
|
1862
|
+
description: "Fixed 2026-08-21 — it previously shared a stacking level with the bottom navigation bar, so on many pages it was either partly covered or completely unclickable. Try it on a page that also has a sticky bottom bar (a product page scrolled down) — that is where it used to fail.",
|
|
1863
|
+
href: "/products",
|
|
1864
|
+
},
|
|
1865
|
+
{
|
|
1866
|
+
key: "above-sticky-buy-bar",
|
|
1867
|
+
label: "On a product page scrolled far enough for BOTH the sticky buy bar and the back-to-top arrow to be showing, the arrow sits above/clear of the buy bar rather than being hidden underneath it",
|
|
1868
|
+
href: "/products/product-beyblade-original-dranzer-s",
|
|
1869
|
+
},
|
|
1870
|
+
{
|
|
1871
|
+
key: "scrolls-to-top",
|
|
1872
|
+
label: "Clicking the back-to-top arrow smoothly scrolls the page back to the very top",
|
|
1873
|
+
href: "/products",
|
|
1874
|
+
},
|
|
1875
|
+
{
|
|
1876
|
+
key: "dismiss-returns-on-navigation",
|
|
1877
|
+
label: "Clicking the small X next to the back-to-top arrow hides it for the current page, and navigating to a different page brings it back",
|
|
1878
|
+
description: "Regression check on an older fix — dismissing it must not hide it for the rest of the browsing session.",
|
|
1879
|
+
href: "/products",
|
|
1880
|
+
},
|
|
1881
|
+
{
|
|
1882
|
+
key: "not-covering-toast-or-modal-actions",
|
|
1883
|
+
label: "The back-to-top arrow does not cover anything you need to click — check that a toast message, an open confirmation dialog's buttons, and the footer's bottom-right links are all still fully clickable while it is on screen",
|
|
1884
|
+
description: "It was deliberately raised to the frontmost layer, so this is the specific risk that change introduces. It should only ever float in the empty corner gutter.",
|
|
1885
|
+
href: "/products",
|
|
1886
|
+
},
|
|
1887
|
+
],
|
|
1888
|
+
},
|
|
1673
1889
|
{
|
|
1674
1890
|
pageKey: "form-validation-errors",
|
|
1675
1891
|
pageLabel: "Form Validation & Error Summary",
|
|
@@ -1759,6 +1975,27 @@ const rawTesterChecklistItems = [
|
|
|
1759
1975
|
description: "Added 2026-08-20 — GITHUB was a new socialUrls key with no footer icon before this. Confirm the icon renders (not a broken/missing glyph) and the link target is a real, working github.com profile, not a placeholder or dead link.",
|
|
1760
1976
|
href: "/",
|
|
1761
1977
|
},
|
|
1978
|
+
{
|
|
1979
|
+
key: "footer-text-weight-readable",
|
|
1980
|
+
label: "Footer text is comfortably readable, not thin and washed out — the link lists, the brand description paragraph, the copyright line and the small sitemap/legal links at the very bottom all render in a slightly heavier weight than plain body text",
|
|
1981
|
+
description: "Changed 2026-08-21 — the footer's greyed text was previously at the lightest weight, which made it hard to read against the page background, especially on a laptop screen at an angle. Compare the footer against the same grey text elsewhere on the page; the footer should read as more solid, without looking bold.",
|
|
1982
|
+
href: "/",
|
|
1983
|
+
},
|
|
1984
|
+
{
|
|
1985
|
+
key: "footer-column-headings-stand-out",
|
|
1986
|
+
label: "On DESKTOP, each footer link column's heading (Shop, Company, Support, etc.) clearly stands out as a heading above its list of links, rather than looking like just another link in the list",
|
|
1987
|
+
href: "/",
|
|
1988
|
+
},
|
|
1989
|
+
{
|
|
1990
|
+
key: "footer-weight-both-themes",
|
|
1991
|
+
label: "The footer text weight change looks right in BOTH light and dark mode — heavier but not bold, and it never turns into an unreadable smudge in dark mode",
|
|
1992
|
+
href: "/",
|
|
1993
|
+
},
|
|
1994
|
+
{
|
|
1995
|
+
key: "footer-mobile-accordions",
|
|
1996
|
+
label: "ON MOBILE, the footer's collapsible link sections still open and close correctly, and their links are as readable as the desktop columns",
|
|
1997
|
+
href: "/",
|
|
1998
|
+
},
|
|
1762
1999
|
],
|
|
1763
2000
|
},
|
|
1764
2001
|
{
|
|
@@ -1871,6 +2108,12 @@ const rawTesterChecklistItems = [
|
|
|
1871
2108
|
href: "/",
|
|
1872
2109
|
cases: [
|
|
1873
2110
|
{ key: "homepage-loads", label: "The homepage loads without errors on both desktop and mobile", href: "/" },
|
|
2111
|
+
{
|
|
2112
|
+
key: "whatsapp-community-link-real",
|
|
2113
|
+
label: "The \"Join the Community\" WhatsApp button on the homepage opens the real LetItRip WhatsApp group, not a dead link",
|
|
2114
|
+
description: "Fixed 2026-08-21 — the seeded link was a placeholder that was never a valid WhatsApp invite code, so the button opened a page that could not resolve to any group. Tap it on a device with WhatsApp installed and confirm it offers to join an actual group. The Contact page's WhatsApp link must open the same group.",
|
|
2115
|
+
href: "/",
|
|
2116
|
+
},
|
|
1874
2117
|
{ key: "about-us-in-main-nav", label: "\"About Us\" appears as an item in the main public top navigation, not just the footer", href: "/about" },
|
|
1875
2118
|
{
|
|
1876
2119
|
key: "about-team-real-founder",
|
|
@@ -2225,6 +2468,18 @@ const rawTesterChecklistItems = [
|
|
|
2225
2468
|
href: "/admin/orders",
|
|
2226
2469
|
cases: [
|
|
2227
2470
|
{ key: "orders-status-change", label: "Admin orders list shows accurate orders and status changes save correctly", href: "/admin/orders" },
|
|
2471
|
+
{
|
|
2472
|
+
key: "admin-emi-order-reviewable",
|
|
2473
|
+
label: "Opening an EMI order in admin shows the full Payment Proof panel — screenshot, UTR, expected-vs-reported UPI, and the Verify / Request re-upload / Reject-as-fraud buttons",
|
|
2474
|
+
description: "Fixed 2026-08-21. The panel used to check only for cash and UPI orders and omitted EMI entirely, so an admin could see an EMI order sitting in the queue labelled \"Awaiting verification\" and have literally no way to action it. Check BOTH the row drawer and the full /admin/orders/[id]/view page — they must behave identically.",
|
|
2475
|
+
href: "/admin/orders",
|
|
2476
|
+
},
|
|
2477
|
+
{
|
|
2478
|
+
key: "admin-decided-order-no-live-buttons",
|
|
2479
|
+
label: "An order already rejected as fraud, or already sent back for re-upload, shows that state instead of offering live Verify / Reject buttons again — on BOTH the row drawer and the full /admin/orders/[id]/view page",
|
|
2480
|
+
description: "Previously only the payment status was consulted, so a decided order still rendered as a fresh \"please verify\" and could be actioned a second time. Reject one order as fraud, reopen it, and confirm it now reads as rejected with no action buttons. Check both surfaces separately: as of 2026-08-21 the full-page variant did not receive paymentReviewOutcome at all, so it kept offering live Verify/Reject on an already-decided order even once the drawer had been fixed — a double-action risk visible only if you open the standalone page rather than the drawer.",
|
|
2481
|
+
href: "/admin/orders",
|
|
2482
|
+
},
|
|
2228
2483
|
{
|
|
2229
2484
|
key: "admin-orders-payment-review-filters",
|
|
2230
2485
|
label: "The Admin Orders filter drawer has a \"Manual payment\" chip group with \"Awaiting payment\" and \"Awaiting verification\" — and each one actually returns the right orders",
|
|
@@ -2413,6 +2668,30 @@ const rawTesterChecklistItems = [
|
|
|
2413
2668
|
{ key: "maintenance-pages-admin", label: "The maintenance pages (analysis, client-errors, cloud-logs, function-errors, payment-rollbacks, server-errors + detail) all load correctly", href: "/admin/maintenance" },
|
|
2414
2669
|
{ key: "copilot-admin", label: "Admin copilot page works correctly", href: "/admin/copilot" },
|
|
2415
2670
|
{ key: "team-admin", label: "Admin team page works correctly", href: "/admin/team" },
|
|
2671
|
+
{
|
|
2672
|
+
key: "team-permission-group-filter",
|
|
2673
|
+
label: "The permission-group filter on /admin/team actually narrows the employee list",
|
|
2674
|
+
description: "Fixed 2026-08-21. The filter emitted a field the database layer was never told to accept, so the clause was discarded silently and every group showed the complete employee list. Pick a group and confirm the rows genuinely change.",
|
|
2675
|
+
href: "/admin/team",
|
|
2676
|
+
},
|
|
2677
|
+
{
|
|
2678
|
+
key: "audit-log-actor-search",
|
|
2679
|
+
label: "The \"Search by actor uid\" box on /admin/audit-log narrows results to that actor",
|
|
2680
|
+
description: "Fixed 2026-08-21 — the box was inert, the endpoint never read it. Paste a full actor uid from a visible row and confirm only that actor's entries remain.",
|
|
2681
|
+
href: "/admin/audit-log",
|
|
2682
|
+
},
|
|
2683
|
+
{
|
|
2684
|
+
key: "notifications-user-search",
|
|
2685
|
+
label: "The search box on /admin/notifications narrows results to one user, and its placeholder reads \"Search by user ID\"",
|
|
2686
|
+
description: "Fixed 2026-08-21 — the box was inert. Its placeholder also used to promise title search, which is not possible against this collection, so the wording was corrected rather than left as a promise the search cannot keep. Paste a user ID from a visible row and confirm only that user's notifications remain.",
|
|
2687
|
+
href: "/admin/notifications",
|
|
2688
|
+
},
|
|
2689
|
+
{
|
|
2690
|
+
key: "carousel-edit-and-delete",
|
|
2691
|
+
label: "A named carousel can be renamed, switched between draft and active, and deleted after it has been created",
|
|
2692
|
+
description: "Fixed 2026-08-21 — the editor could only CREATE. There was no way to load an existing carousel back, so a typo'd or draft carousel was stuck permanently. Create one, reopen it, rename it, publish it, then delete it.",
|
|
2693
|
+
href: "/admin/carousels",
|
|
2694
|
+
},
|
|
2416
2695
|
{ key: "guide-pages-admin", label: "The 8 admin guide pages all load correctly", href: "/admin/guide" },
|
|
2417
2696
|
{ key: "tester-checklist-crud-admin", label: "Admin can create, edit, and toggle adminOnly on tester checklist items", href: "/admin/tester-checklist" },
|
|
2418
2697
|
{ key: "tester-feedback-report-export", label: "Admin tester-feedback report shows Yes/No analytics grouped correctly and the Download Report export works", href: "/admin/tester-feedback" },
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import Link from "next/link";
|
|
3
|
-
const CLS_WA_BTN = "inline-flex items-center gap-[var(--appkit-space-2)] rounded-lg bg-success-
|
|
3
|
+
const CLS_WA_BTN = "inline-flex items-center gap-[var(--appkit-space-2)] rounded-lg bg-success-solid px-[var(--appkit-space-4)] py-[var(--appkit-space-2)] text-[length:var(--appkit-text-sm)] font-semibold text-success-on-solid transition-colors hover:brightness-110";
|
|
4
4
|
export function WhatsAppChatButton({ waNumber, message = "Hi! I have a question.", label = "Chat on WhatsApp", className, }) {
|
|
5
5
|
const href = `https://wa.me/${waNumber}?text=${encodeURIComponent(message)}`;
|
|
6
6
|
return (_jsxs(Link, { href: href, target: "_blank", rel: "noopener noreferrer", className: `${CLS_WA_BTN} ${className ?? ""}`, children: [_jsx("svg", { className: "h-4 w-4", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: _jsx("path", { d: "M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z" }) }), label] }));
|
package/dist/jobs.d.ts
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* listingProcessorHandler,
|
|
21
21
|
* } from "@mohasinac/appkit/jobs";
|
|
22
22
|
*/
|
|
23
|
-
export { couponExpiryHandler, offerExpiryHandler, cartPruneHandler, notificationPruneHandler, dailyDataCleanupHandler, cleanupRtdbEventsHandler, auctionSettlementHandler, autoPayoutEligibilityHandler, countersReconcileHandler, onOrderCreateHandler, onOrderStatusChangeHandler,
|
|
23
|
+
export { couponExpiryHandler, offerExpiryHandler, cartPruneHandler, notificationPruneHandler, dailyDataCleanupHandler, cleanupRtdbEventsHandler, auctionSettlementHandler, autoPayoutEligibilityHandler, countersReconcileHandler, onOrderCreateHandler, onOrderStatusChangeHandler, onReviewWriteHandler, promotionsHandler, mediaTmpCleanupHandler, pendingOrderTimeoutHandler, productStatsSyncHandler, positionsReconcileHandler, payoutBatchHandler, weeklyPayoutEligibilityHandler, onCategoryWriteHandler, onProductWriteHandler, onStoreWriteHandler, adminAnalyticsHandler, storeAnalyticsHandler, listingProcessorHandler, supportedListingCollections, onPrizeDrawPaymentConfirmedHandler, prizeDrawSoldOutRevealHandler, prizeDrawExpiryRevealHandler, bundleStockSyncHandler, onProductStockChangeHandler, triggerEventRaffleHandler, assignSpinPrizeHandler, draftPruneHandler, onSupportTicketCreateHandler, onSupportTicketUpdateHandler, onUserBanChangeHandler, onScamReportCreateHandler, onScamReportUpdateHandler, bindSchedule, bindDocumentWritten, bindDocumentCreated, bindDocumentUpdated, bindCallable, bindHttps, bindToFirebase, } from "./_internal/server/jobs/index.js";
|
|
24
24
|
export { defineFunction, mergeFunctionRegistries, bindAllFromRegistry, APPKIT_FUNCTIONS, APPKIT_FUNCTIONS_BY_NAME, } from "./_internal/server/functions/index.js";
|
|
25
25
|
export { orderRepository, siteSettingsRepository } from "./repositories/index.js";
|
|
26
26
|
export type { OrderDocument, OrderDocumentItem } from "./features/orders/index.js";
|
package/dist/jobs.js
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
export {
|
|
24
24
|
// S4–S5: Job handlers (pure, framework-agnostic)
|
|
25
|
-
couponExpiryHandler, offerExpiryHandler, cartPruneHandler, notificationPruneHandler, dailyDataCleanupHandler, cleanupRtdbEventsHandler, auctionSettlementHandler, autoPayoutEligibilityHandler, countersReconcileHandler, onOrderCreateHandler, onOrderStatusChangeHandler,
|
|
25
|
+
couponExpiryHandler, offerExpiryHandler, cartPruneHandler, notificationPruneHandler, dailyDataCleanupHandler, cleanupRtdbEventsHandler, auctionSettlementHandler, autoPayoutEligibilityHandler, countersReconcileHandler, onOrderCreateHandler, onOrderStatusChangeHandler, onReviewWriteHandler, promotionsHandler, mediaTmpCleanupHandler, pendingOrderTimeoutHandler, productStatsSyncHandler, positionsReconcileHandler, payoutBatchHandler, weeklyPayoutEligibilityHandler, onCategoryWriteHandler, onProductWriteHandler, onStoreWriteHandler, adminAnalyticsHandler, storeAnalyticsHandler, listingProcessorHandler, supportedListingCollections,
|
|
26
26
|
// SB1-L (S7-PrizeDraws) — prize-draw reveal redesigned to a fully-automatic model.
|
|
27
27
|
onPrizeDrawPaymentConfirmedHandler, prizeDrawSoldOutRevealHandler, prizeDrawExpiryRevealHandler, bundleStockSyncHandler,
|
|
28
28
|
// SB-UNI-V — Firestore onWrite trigger for product stock changes.
|