@mohasinac/appkit 4.11.3 → 4.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_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 +36 -18
- package/dist/_internal/server/features/checkout/actions.js +127 -45
- package/dist/_internal/server/features/orders/actions.js +5 -3
- package/dist/_internal/server/features/orders/adapters.js +13 -0
- package/dist/_internal/server/features/products/list-public.d.ts +28 -0
- package/dist/_internal/server/features/products/list-public.js +92 -0
- 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/auctionSettlement.js +5 -2
- 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/checkout/lanes.d.ts +20 -0
- package/dist/_internal/shared/checkout/lanes.js +28 -0
- 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/features/checkout/config.d.ts +4 -0
- package/dist/_internal/shared/features/checkout/config.js +8 -0
- package/dist/_internal/shared/fees/calculator.d.ts +34 -2
- package/dist/_internal/shared/fees/calculator.js +45 -1
- 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/client.d.ts +7 -1
- package/dist/client.js +4 -1
- package/dist/constants/api-endpoints.d.ts +6 -0
- package/dist/constants/api-endpoints.js +2 -0
- package/dist/features/account/components/NotificationBell.js +1 -1
- package/dist/features/admin/components/AdminAddressesView.js +18 -10
- package/dist/features/admin/components/AdminArtView.d.ts +9 -0
- package/dist/features/admin/components/AdminArtView.js +11 -35
- package/dist/features/admin/components/AdminClassifiedView.d.ts +9 -0
- package/dist/features/admin/components/AdminClassifiedView.js +11 -35
- package/dist/features/admin/components/AdminDigitalCodesView.d.ts +9 -0
- package/dist/features/admin/components/AdminDigitalCodesView.js +11 -42
- package/dist/features/admin/components/AdminLiveView.d.ts +9 -0
- package/dist/features/admin/components/AdminLiveView.js +11 -41
- package/dist/features/admin/components/AdminNotificationsView.js +4 -1
- package/dist/features/admin/components/AdminOrderEditorView.d.ts +14 -1
- package/dist/features/admin/components/AdminOrderEditorView.js +18 -5
- package/dist/features/admin/components/AdminOrdersView.js +15 -1
- package/dist/features/admin/components/AdminPaymentMethodsView.js +15 -10
- package/dist/features/admin/components/AdminSiteConfigGuideView.js +1 -1
- package/dist/features/admin/components/AdminSiteSettingsView.js +4 -2
- package/dist/features/admin/components/AdminStickersView.d.ts +9 -0
- package/dist/features/admin/components/AdminStickersView.js +11 -35
- package/dist/features/admin/hooks/useAdminListing.js +7 -2
- package/dist/features/admin/schemas/firestore.d.ts +5 -0
- package/dist/features/admin/schemas/firestore.js +1 -0
- package/dist/features/auctions/actions/bid-actions.js +73 -11
- 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/cart/components/CartDrawer.d.ts +9 -1
- package/dist/features/cart/components/CartDrawer.js +4 -3
- package/dist/features/cart/components/CartPriceBreakdown.d.ts +61 -0
- package/dist/features/cart/components/CartPriceBreakdown.js +25 -0
- package/dist/features/cart/components/StoreAddonsPicker.d.ts +47 -0
- package/dist/features/cart/components/StoreAddonsPicker.js +21 -0
- package/dist/features/cart/components/index.d.ts +4 -0
- package/dist/features/cart/components/index.js +2 -0
- package/dist/features/cart/repository/cart.repository.d.ts +20 -2
- package/dist/features/cart/repository/cart.repository.js +49 -0
- package/dist/features/cart/schemas/firestore.d.ts +26 -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 +38 -5
- package/dist/features/layout/BottomActionsContext.d.ts +12 -0
- package/dist/features/layout/BottomActionsContext.js +5 -0
- package/dist/features/layout/FooterLayout.js +2 -2
- package/dist/features/layout/TitleBarLayout.js +1 -1
- package/dist/features/layout/hooks/useBottomActions.d.ts +6 -0
- package/dist/features/layout/hooks/useBottomActions.js +11 -1
- package/dist/features/orders/components/OrderAddonBadges.d.ts +36 -0
- package/dist/features/orders/components/OrderAddonBadges.js +28 -0
- package/dist/features/orders/components/index.d.ts +1 -0
- package/dist/features/orders/components/index.js +1 -0
- package/dist/features/orders/repository/orders.repository.d.ts +32 -0
- package/dist/features/orders/repository/orders.repository.js +14 -0
- package/dist/features/orders/types/index.d.ts +22 -0
- package/dist/features/pre-orders/components/MarketplacePreorderCard.js +3 -3
- package/dist/features/products/components/ArtStickersListView.js +5 -4
- 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/PrizeDrawsListingView.js +18 -33
- package/dist/features/products/components/ProductDetailPageView.js +1 -1
- package/dist/features/products/components/ProductGrid.js +3 -3
- package/dist/features/products/components/ProductsIndexListing.js +87 -11
- package/dist/features/products/components/ProductsIndexPageView.js +14 -2
- package/dist/features/products/config/listing-type-listing-config.d.ts +31 -0
- package/dist/features/products/config/listing-type-listing-config.js +74 -0
- package/dist/features/products/hooks/useProducts.js +14 -0
- package/dist/features/products/repository/products.repository.d.ts +0 -4
- package/dist/features/products/repository/products.repository.js +0 -15
- package/dist/features/products/types/index.d.ts +15 -0
- 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/SellerOrdersView.js +2 -1
- package/dist/features/seller/components/SellerSidebar.js +1 -1
- package/dist/features/stores/components/StoreArtStickersPageView.d.ts +21 -0
- package/dist/features/stores/components/StoreArtStickersPageView.js +30 -0
- package/dist/features/stores/components/StoreAuctionsPageView.d.ts +4 -1
- package/dist/features/stores/components/StoreAuctionsPageView.js +17 -17
- package/dist/features/stores/components/StoreClassifiedsListing.js +7 -0
- package/dist/features/stores/components/StoreClassifiedsPageView.d.ts +4 -1
- package/dist/features/stores/components/StoreClassifiedsPageView.js +16 -12
- package/dist/features/stores/components/StoreDetailLayoutView.js +4 -3
- package/dist/features/stores/components/StoreDigitalCodesListing.js +5 -0
- package/dist/features/stores/components/StoreDigitalCodesPageView.d.ts +4 -1
- package/dist/features/stores/components/StoreDigitalCodesPageView.js +16 -12
- package/dist/features/stores/components/StoreLiveItemsListing.js +7 -0
- package/dist/features/stores/components/StoreLiveItemsPageView.d.ts +4 -1
- package/dist/features/stores/components/StoreLiveItemsPageView.js +16 -12
- package/dist/features/stores/components/StorePreOrdersPageView.d.ts +4 -1
- package/dist/features/stores/components/StorePreOrdersPageView.js +17 -17
- package/dist/features/stores/components/StorePrizeDrawsPageView.d.ts +4 -8
- package/dist/features/stores/components/StorePrizeDrawsPageView.js +17 -22
- package/dist/features/stores/components/StoreProductsListing.d.ts +7 -1
- package/dist/features/stores/components/StoreProductsListing.js +3 -2
- package/dist/features/stores/components/StoreProductsPageView.d.ts +1 -1
- package/dist/features/stores/components/StoreProductsPageView.js +16 -21
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +875 -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/next/routing/route-map.d.ts +2 -0
- package/dist/next/routing/route-map.js +5 -0
- package/dist/seed/homepage-sections-seed-data.js +5 -1
- package/dist/seed/reviews-seed-data.js +52 -0
- package/dist/seed/site-settings-seed-data.js +18 -0
- package/dist/server-entry.d.ts +1 -0
- package/dist/server-entry.js +1 -0
- package/dist/styles.css +66 -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/Checkbox.d.ts +10 -1
- package/dist/ui/components/Checkbox.js +14 -2
- package/dist/ui/components/Checkbox.style.css +11 -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
|
@@ -41,11 +41,11 @@ export function FAQHelpfulButtons({ faqId, initialHelpful, initialNotHelpful, on
|
|
|
41
41
|
return (_jsxs(Div, { className: `${__P.p6}`, rounded: "lg", surface: "subtle", children: [_jsx(Text, { className: "mb-3", color: "muted", size: "sm", children: userVote
|
|
42
42
|
? (labels?.thanksForFeedback ?? "Thanks for your feedback")
|
|
43
43
|
: (labels?.wasThisHelpful ?? "Was this helpful?") }), _jsxs(Row, { gap: "3", children: [_jsxs(Button, { variant: "ghost", onClick: () => handleVote(true), disabled: isPending || userVote !== null, className: `flex-1 gap-[var(--appkit-space-2)] rounded-lg p-[var(--appkit-space-4)] transition-all ${userVote === "helpful"
|
|
44
|
-
? "bg-success text-
|
|
44
|
+
? "bg-success-solid text-success-on-solid"
|
|
45
45
|
: userVote
|
|
46
46
|
? "cursor-not-allowed bg-zinc-200 text-[var(--appkit-color-text-muted)] opacity-50 dark:bg-slate-600 text-[var(--appkit-color-text-muted)]"
|
|
47
47
|
: "bg-[var(--appkit-color-surface)] text-zinc-800 hover:bg-green-50 bg-[var(--appkit-color-surface-elevated)] text-[var(--appkit-color-text)] dark:hover:bg-green-900/20"}`, children: [_jsx(Span, { size: "sm", children: labels?.yes ?? "Yes" }), _jsxs(Span, { size: "sm", color: "muted", children: ["(", helpful, ")"] })] }), _jsxs(Button, { variant: "ghost", onClick: () => handleVote(false), disabled: isPending || userVote !== null, className: `flex-1 gap-[var(--appkit-space-2)] rounded-lg p-[var(--appkit-space-4)] transition-all ${userVote === "not-helpful"
|
|
48
|
-
? "bg-error text-
|
|
48
|
+
? "bg-error-solid text-error-on-solid"
|
|
49
49
|
: userVote
|
|
50
50
|
? "cursor-not-allowed bg-zinc-200 text-[var(--appkit-color-text-muted)] opacity-50 dark:bg-slate-600 text-[var(--appkit-color-text-muted)]"
|
|
51
51
|
: "bg-[var(--appkit-color-surface)] text-zinc-800 hover:bg-red-50 bg-[var(--appkit-color-surface-elevated)] text-[var(--appkit-color-text)] dark:hover:bg-red-900/20"}`, children: [_jsx(Span, { size: "sm", children: labels?.no ?? "No" }), _jsxs(Span, { size: "sm", color: "muted", children: ["(", notHelpful, ")"] })] })] })] }));
|
|
@@ -7,7 +7,7 @@ const __O = {
|
|
|
7
7
|
yAuto: "overflow-y-auto",
|
|
8
8
|
};
|
|
9
9
|
const CLS_OPTION_SELECTED = "border-success bg-success-surface text-success dark:border-success/60 dark:bg-success-surface dark:text-success";
|
|
10
|
-
const CLS_OPTION_DOT = "inline-flex h-4 w-4 items-center justify-center rounded-full bg-success-
|
|
10
|
+
const CLS_OPTION_DOT = "inline-flex h-4 w-4 items-center justify-center rounded-full bg-success-solid text-[10px] text-success-on-solid";
|
|
11
11
|
const CLS_CLEAR_BTN = "w-full py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] text-zinc-400 transition-colors hover:text-error";
|
|
12
12
|
const CLS_BADGE_COUNT = "inline-flex items-center justify-center w-5 h-5 rounded-full bg-success-surface dark:bg-success-surface text-success dark:text-success ring-1 ring-success/20";
|
|
13
13
|
const CLS_CLEAR_ICON = "inline-flex items-center justify-center w-5 h-5 p-[var(--appkit-space-0)] text-[var(--appkit-color-text-muted)] hover:text-error dark:hover:text-error hover:bg-[var(--appkit-color-surface)] dark:hover:bg-slate-700 transition-colors rounded-full";
|
|
@@ -16,5 +16,5 @@ export function WhatsAppCommunitySection({ title, descriptionHtml, memberCount,
|
|
|
16
16
|
if (groupLink)
|
|
17
17
|
window.open(groupLink, "_blank");
|
|
18
18
|
};
|
|
19
|
-
return (_jsx(Section, { paddingX: "x-md-2xl", className: className, surface: "subtle", paddingY: "y-4xl", children: _jsx(Div, { className: "w-full max-w-7xl mx-auto", children: _jsxs(Section, { color: "inverse", tone: "accent-banner", rounded: "2xl", shadow: "lg", className: "relative overflow-hidden", children: [_jsx(Div, { className: "absolute inset-0 opacity-[0.06] pointer-events-none bg-[repeating-linear-gradient(45deg,white_0,white_1px,transparent_0,transparent_50%)] bg-[size:10px_10px]", "aria-hidden": true }), _jsxs(Div, { className: "relative z-10 px-[1.5rem] sm:px-[2.5rem] sm:py-[3rem]", padding: "y-2xl", children: [_jsxs(Row, { align: "center", justify: "between", gap: "md", wrap: true, className: "mb-8", reverse: hand === "left", children: [_jsxs(Row, { align: "center", gap: "3", children: [_jsx(Row, { className: "w-12 h-12 bg-[var(--appkit-color-whatsapp)] flex-shrink-0", align: "center", justify: "center", rounded: "xl", shadow: "lg", children: _jsx("svg", { className: "w-7 h-7 text-white", fill: "currentColor", viewBox: "0 0 24 24", "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" }) }) }), _jsx(Span, { color: "inverse", size: "sm", weight: "medium", className: "/80 tracking-wide", transform: "uppercase", children: "WhatsApp Community" })] }), memberCount && (_jsxs(Row, { paddingY: "y-2xs-tall", className: "backdrop-blur-sm", surface: "
|
|
19
|
+
return (_jsx(Section, { paddingX: "x-md-2xl", className: className, surface: "subtle", paddingY: "y-4xl", children: _jsx(Div, { className: "w-full max-w-7xl mx-auto", children: _jsxs(Section, { color: "inverse", tone: "accent-banner", rounded: "2xl", shadow: "lg", className: "relative overflow-hidden", children: [_jsx(Div, { className: "absolute inset-0 opacity-[0.06] pointer-events-none bg-[repeating-linear-gradient(45deg,white_0,white_1px,transparent_0,transparent_50%)] bg-[size:10px_10px]", "aria-hidden": true }), _jsxs(Div, { className: "relative z-10 px-[1.5rem] sm:px-[2.5rem] sm:py-[3rem]", padding: "y-2xl", children: [_jsxs(Row, { align: "center", justify: "between", gap: "md", wrap: true, className: "mb-8", reverse: hand === "left", children: [_jsxs(Row, { align: "center", gap: "3", children: [_jsx(Row, { className: "w-12 h-12 bg-[var(--appkit-color-whatsapp)] flex-shrink-0", align: "center", justify: "center", rounded: "xl", shadow: "lg", children: _jsx("svg", { className: "w-7 h-7 text-white", fill: "currentColor", viewBox: "0 0 24 24", "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" }) }) }), _jsx(Span, { color: "inverse", size: "sm", weight: "medium", className: "/80 tracking-wide", transform: "uppercase", children: "WhatsApp Community" })] }), memberCount && (_jsxs(Row, { paddingY: "y-2xs-tall", className: "backdrop-blur-sm", surface: "frost", padding: "x-md", align: "center", gap: "sm", rounded: "full", children: [_jsx(Users, { className: "w-4 h-4 text-white/80" }), _jsxs(Span, { color: "inverse", size: "sm", weight: "semibold", children: [memberCount.toLocaleString(getDefaultLocale()), "+ ", memberCountLabel] })] }))] }), _jsx(Heading, { color: "inverse", level: 2, className: "mb-3 leading-tight", smSize: "3xl", size: "2xl", weight: "bold", children: title }), descriptionHtml && (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none prose-invert", className: "text-white/80 text-[length:var(--appkit-text-base)] mb-6 max-w-2xl leading-relaxed" })), benefits.length > 0 && (_jsx(Div, { layout: "grid", gap: "3", className: "grid-cols-1 sm:grid-cols-2 mb-8 max-w-2xl", children: benefits.slice(0, 4).map((benefit, i) => (_jsxs(Row, { align: "start", gap: "2.5", children: [_jsx(Span, { layout: "flex-center", className: "mt-0.5 flex-shrink-0 w-5 h-5 bg-[rgba(255,255,255,0.2)]", rounded: "full", children: _jsx("svg", { className: "w-3 h-3 text-white", fill: "currentColor", viewBox: "0 0 20 20", "aria-hidden": "true", children: _jsx("path", { fillRule: "evenodd", d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", clipRule: "evenodd" }) }) }), _jsx(Span, { color: "inverse", size: "sm", className: "/90 leading-snug", children: benefit })] }, i))) })), testimonial && (_jsx(Blockquote, { paddingX: "x-md", className: "border-l-2 border-white/30 mb-8 max-w-xl", children: _jsxs(Text, { color: "inverse", variant: "none", className: "/75 italic leading-relaxed", size: "sm", children: ["\u201C", testimonial, "\u201D"] }) })), groupLink && (_jsxs(Button, { gap: "md", variant: "primary", size: "lg", onClick: handleJoin, "aria-label": buttonText, className: "!bg-[var(--appkit-color-whatsapp)] hover:!bg-[var(--appkit-color-whatsapp-light)] !text-white !border-0 font-[600]", shadow: "lg", children: [_jsx("svg", { className: "w-5 h-5", fill: "currentColor", viewBox: "0 0 24 24", "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" }) }), buttonText] }))] })] }) }) }));
|
|
20
20
|
}
|
|
@@ -40,7 +40,7 @@ export function BackToTop({ threshold = 400, ariaLabel = "Back to top", classNam
|
|
|
40
40
|
const handleDismiss = () => {
|
|
41
41
|
setDismissed(true);
|
|
42
42
|
};
|
|
43
|
-
return (_jsxs(Div, { layout: "flex", align: "center", gap: "1", className: `fixed bottom-[calc(var(--keyboard-inset-height,0px)+var(--bottom-nav-height,4rem)+var(--appkit-space-4))] ${hand === "left" ? "left-4" : "right-4"} z-[var(--appkit-z-
|
|
43
|
+
return (_jsxs(Div, { layout: "flex", align: "center", gap: "1", className: `fixed bottom-[calc(var(--keyboard-inset-height,0px)+var(--bottom-nav-height,4rem)+var(--appkit-space-4))] ${hand === "left" ? "left-4" : "right-4"} z-[var(--appkit-z-back-to-top)] ${className}`, children: [_jsx(Button, { type: "button", variant: "outline", size: "sm", "aria-label": ariaLabel, onClick: () => window.scrollTo({ top: 0, behavior: "smooth" }), className: "flex items-center justify-center w-10 h-10 rounded-full bg-[var(--appkit-color-surface)] border border-[var(--appkit-color-border)] shadow-md text-[var(--appkit-color-text-muted)] hover:bg-[var(--appkit-color-surface)] dark:hover:bg-slate-700 transition-colors", children: _jsx(ChevronUp, { className: "w-5 h-5", "aria-hidden": "true" }) }), _jsx(Button, { type: "button", variant: "outline", size: "sm", "aria-label": "Hide back-to-top button", onClick: handleDismiss, className: "flex items-center justify-center w-6 h-6 rounded-full bg-[var(--appkit-color-surface)] border border-[var(--appkit-color-border)] shadow-md text-[var(--appkit-color-text-faint)] hover:text-[var(--appkit-color-text)] transition-colors", children: _jsx(X, { className: "w-3 h-3", "aria-hidden": "true" }) })] }));
|
|
44
44
|
}
|
|
45
45
|
/** SkipToMain — visually hidden link that becomes visible on focus for keyboard users. */
|
|
46
46
|
export function SkipToMain({ mainId = "main-content" }) {
|
|
@@ -29,7 +29,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
29
29
|
* // Automatically rendered by LayoutClient — no manual usage required.
|
|
30
30
|
* // Features use `useBottomActions` to register their actions.
|
|
31
31
|
*/
|
|
32
|
-
import { useState, useRef, useEffect } from "react";
|
|
32
|
+
import { useState, useRef, useEffect, useId } from "react";
|
|
33
33
|
import { X, ChevronUp, ChevronDown, Check } from "lucide-react";
|
|
34
34
|
import { useBottomActionsContext } from "./BottomActionsContext";
|
|
35
35
|
import { useClickOutside } from "../../react";
|
|
@@ -39,7 +39,7 @@ const BOTTOM_NAV_BG = "bg-[color-mix(in_srgb,var(--appkit-color-bg)_90%,transpar
|
|
|
39
39
|
const Z_BOTTOM_ACTIONS = "z-[var(--appkit-z-bottom-nav)]";
|
|
40
40
|
const BOTTOM_NAV_HEIGHT = "h-14";
|
|
41
41
|
const FLEX_CENTER = "flex items-center justify-center";
|
|
42
|
-
const CLS_COUNT_BADGE = "bg-error-
|
|
42
|
+
const CLS_COUNT_BADGE = "bg-error-solid text-error-on-solid";
|
|
43
43
|
/** How far the user must scroll before the desktop bar reveals itself. */
|
|
44
44
|
const DESKTOP_REVEAL_SCROLL_PX = 400;
|
|
45
45
|
// ─── Sub-components ───────────────────────────────────────────────────────────
|
|
@@ -68,6 +68,27 @@ function BulkPickerPanel({ bulkActions, selectedActionId, pickerOpen, onSelect,
|
|
|
68
68
|
.join(" "), children: [action.icon && (_jsx(Span, { className: `flex-shrink-0 w-5 h-5 ${FLEX_CENTER}`, "aria-hidden": "true", children: action.icon })), _jsx(Span, { className: "flex-1 truncate", children: action.label }), isSelected && (_jsx(Check, { className: "w-4 h-4 flex-shrink-0 text-primary-600 dark:text-primary-400", "aria-hidden": "true" }))] }, action.id));
|
|
69
69
|
}) }));
|
|
70
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Expandable detail that opens UPWARD from the bar.
|
|
73
|
+
*
|
|
74
|
+
* Same recipe as BulkPickerPanel above — absolutely positioned at `bottom-full`
|
|
75
|
+
* so it grows into the space above the bar rather than pushing the page, and
|
|
76
|
+
* animated on max-height/opacity so it can't be tabbed into while closed.
|
|
77
|
+
* Scrolls internally, since a many-store cart's breakdown can outgrow the
|
|
78
|
+
* screen.
|
|
79
|
+
*/
|
|
80
|
+
function InfoPanel({ id, open, children, }) {
|
|
81
|
+
return (_jsx(Div, { id: id, className: [
|
|
82
|
+
"absolute bottom-full left-0 right-0 overflow-hidden",
|
|
83
|
+
BOTTOM_NAV_BG,
|
|
84
|
+
"border-t border-zinc-200/80 border-[var(--appkit-color-border)]/80",
|
|
85
|
+
"shadow-[0_-8px_24px_rgba(0,0,0,0.10)] dark:shadow-[0_-8px_24px_rgba(0,0,0,0.35)]",
|
|
86
|
+
"transition-[max-height,opacity] duration-200 ease-out",
|
|
87
|
+
open ? "max-h-[60vh] opacity-100" : "max-h-0 opacity-0 pointer-events-none",
|
|
88
|
+
]
|
|
89
|
+
.filter(Boolean)
|
|
90
|
+
.join(" "), "aria-hidden": !open, children: _jsx(Div, { className: "max-h-[60vh]", overflow: "y-auto", padding: "md", children: children }) }));
|
|
91
|
+
}
|
|
71
92
|
function PageActionsRow({ pageActions, dispatchAction, }) {
|
|
72
93
|
return (_jsx(_Fragment, { children: pageActions.map((action) => {
|
|
73
94
|
const isIconOnly = !action.label;
|
|
@@ -86,10 +107,15 @@ function PageActionsRow({ pageActions, dispatchAction, }) {
|
|
|
86
107
|
export default function BottomActions() {
|
|
87
108
|
const { state, actionCallbacksRef, bulkCallbacksRef, bulkClearRef } = useBottomActionsContext();
|
|
88
109
|
const [pickerOpen, setPickerOpen] = useState(false);
|
|
110
|
+
const [infoOpen, setInfoOpen] = useState(false);
|
|
89
111
|
const [selectedActionId, setSelectedActionId] = useState(null);
|
|
90
112
|
const containerRef = useRef(null);
|
|
91
|
-
|
|
92
|
-
|
|
113
|
+
const infoPanelId = useId();
|
|
114
|
+
useClickOutside(containerRef, () => {
|
|
115
|
+
setPickerOpen(false);
|
|
116
|
+
setInfoOpen(false);
|
|
117
|
+
});
|
|
118
|
+
const { actions, bulk, infoLabel, secondaryLabel, infoPanel, desktop } = state;
|
|
93
119
|
// Desktop reveal-on-scroll. Kept as a plain passive listener to match BackToTop —
|
|
94
120
|
// there is no shared scroll-position hook in the codebase and one consumer doesn't
|
|
95
121
|
// justify inventing one. Only armed when a page actually opts into "after-scroll".
|
|
@@ -107,11 +133,18 @@ export default function BottomActions() {
|
|
|
107
133
|
}, [wantsScrollGate]);
|
|
108
134
|
const showOnDesktop = desktop === "always" || (wantsScrollGate && scrolledPastFold);
|
|
109
135
|
const isBulkMode = !!(bulk && bulk.selectedCount > 0);
|
|
136
|
+
const hasInfoPanel = infoPanel != null && !isBulkMode;
|
|
110
137
|
const bulkActions = bulk?.actions ?? [];
|
|
111
138
|
const pageActions = actions;
|
|
112
139
|
const isVisible = (isBulkMode ? bulkActions.length > 0 || !!bulk : pageActions.length > 0) ||
|
|
113
140
|
!!infoLabel ||
|
|
114
141
|
!!secondaryLabel;
|
|
142
|
+
// A panel left open after its content is withdrawn (bulk mode taking over, or
|
|
143
|
+
// the page clearing it) would sit there as an empty sheet.
|
|
144
|
+
useEffect(() => {
|
|
145
|
+
if (!hasInfoPanel)
|
|
146
|
+
setInfoOpen(false);
|
|
147
|
+
}, [hasInfoPanel]);
|
|
115
148
|
// Keep selectedActionId in sync with available bulk actions
|
|
116
149
|
useEffect(() => {
|
|
117
150
|
if (!isBulkMode) {
|
|
@@ -151,7 +184,7 @@ export default function BottomActions() {
|
|
|
151
184
|
isVisible ? "translate-y-0" : "translate-y-full pointer-events-none",
|
|
152
185
|
]
|
|
153
186
|
.filter(Boolean)
|
|
154
|
-
.join(" "), "data-section": "bottomactions-div-401", children: [isBulkMode && (_jsx(BulkPickerPanel, { bulkActions: bulkActions, selectedActionId: selectedActionId, pickerOpen: pickerOpen, onSelect: (id) => { setSelectedActionId(id); setPickerOpen(false); } })), isBulkMode && (_jsx(Div, { className: "h-[3px] w-full bg-[image:var(--appkit-gradient-brand-tri)]" })), secondaryLabel && !isBulkMode && (_jsx(Div, { border: "subtle", className: "pt-[var(--appkit-space-2)] pb-[var(--appkit-space-0)] border-b /80", padding: "x-md", children: _jsx(Text, { className: "leading-5 truncate", color: "muted", size: "xs", weight: "semibold", children: secondaryLabel }) })), infoLabel && !isBulkMode && (_jsx(Div, { border: "subtle", className: "pt-[var(--appkit-space-2)] pb-[var(--appkit-space-0)] border-b /80", padding: "x-md", children: _jsx(Text, { className: "leading-5 truncate", color: "muted", size: "xs", weight: "semibold", children: infoLabel }) })), _jsx(Row, { className: `${BOTTOM_NAV_HEIGHT}`, gap: "sm", padding: "x-sm", children: isBulkMode && bulk ? (_jsxs(_Fragment, { children: [_jsxs(Button, { rounded: "full", gap: "xs", type: "button", variant: "ghost", onClick: dispatchBulkClear, className: "inline- flex-shrink-0 bg-primary-50 hover:bg-primary-100 active:bg-primary-200 dark:bg-primary-950/30 dark:hover:bg-primary-900/50 text-primary-700 dark:text-primary-300 pl-2 pr-3 h-8 border border-primary-200/70 dark:border-primary-800/50 transition-colors min-h-0", "aria-label": "Clear selection", children: [_jsx(X, { className: "w-3.5 h-3.5 flex-shrink-0", "aria-hidden": "true" }), _jsx(Span, { size: "xs", weight: "semibold", className: "tabular-nums whitespace-nowrap leading-none", children: bulk.noun
|
|
187
|
+
.join(" "), "data-section": "bottomactions-div-401", children: [isBulkMode && (_jsx(BulkPickerPanel, { bulkActions: bulkActions, selectedActionId: selectedActionId, pickerOpen: pickerOpen, onSelect: (id) => { setSelectedActionId(id); setPickerOpen(false); } })), hasInfoPanel && !isBulkMode && (_jsx(InfoPanel, { id: infoPanelId, open: infoOpen, children: infoPanel })), isBulkMode && (_jsx(Div, { className: "h-[3px] w-full bg-[image:var(--appkit-gradient-brand-tri)]" })), secondaryLabel && !isBulkMode && (_jsx(Div, { border: "subtle", className: "pt-[var(--appkit-space-2)] pb-[var(--appkit-space-0)] border-b /80", padding: "x-md", children: _jsx(Text, { className: "leading-5 truncate", color: "muted", size: "xs", weight: "semibold", children: secondaryLabel }) })), infoLabel && !isBulkMode && !hasInfoPanel && (_jsx(Div, { border: "subtle", className: "pt-[var(--appkit-space-2)] pb-[var(--appkit-space-0)] border-b /80", padding: "x-md", children: _jsx(Text, { className: "leading-5 truncate", color: "muted", size: "xs", weight: "semibold", children: infoLabel }) })), infoLabel && !isBulkMode && hasInfoPanel && (_jsxs(Button, { type: "button", variant: "ghost", onClick: () => setInfoOpen((o) => !o), "aria-expanded": infoOpen, "aria-controls": infoPanelId, className: "w-full flex items-center justify-between gap-[var(--appkit-space-2)] rounded-none border-b border-[var(--appkit-color-border)]/80 px-[var(--appkit-space-4)] pt-[var(--appkit-space-2)] pb-[var(--appkit-space-1)] min-h-0", children: [_jsx(Text, { className: "leading-5 truncate", color: "muted", size: "xs", weight: "semibold", children: infoLabel }), _jsxs(Span, { className: "flex-shrink-0 flex items-center gap-[var(--appkit-space-1)]", children: [_jsx(Text, { as: "span", size: "xs", color: "muted", children: infoOpen ? "Hide" : "Details" }), infoOpen ? (_jsx(ChevronDown, { className: "w-4 h-4 text-zinc-400", "aria-hidden": "true" })) : (_jsx(ChevronUp, { className: "w-4 h-4 text-zinc-400", "aria-hidden": "true" }))] })] })), _jsx(Row, { className: `${BOTTOM_NAV_HEIGHT}`, gap: "sm", padding: "x-sm", children: isBulkMode && bulk ? (_jsxs(_Fragment, { children: [_jsxs(Button, { rounded: "full", gap: "xs", type: "button", variant: "ghost", onClick: dispatchBulkClear, className: "inline- flex-shrink-0 bg-primary-50 hover:bg-primary-100 active:bg-primary-200 dark:bg-primary-950/30 dark:hover:bg-primary-900/50 text-primary-700 dark:text-primary-300 pl-2 pr-3 h-8 border border-primary-200/70 dark:border-primary-800/50 transition-colors min-h-0", "aria-label": "Clear selection", children: [_jsx(X, { className: "w-3.5 h-3.5 flex-shrink-0", "aria-hidden": "true" }), _jsx(Span, { size: "xs", weight: "semibold", className: "tabular-nums whitespace-nowrap leading-none", children: bulk.noun
|
|
155
188
|
? `${bulk.selectedCount} ${bulk.noun}`
|
|
156
189
|
: `${bulk.selectedCount} selected` })] }), bulkActions.length > 0 && (_jsxs(Button, { type: "button", variant: "ghost", onClick: () => setPickerOpen((o) => !o), "aria-haspopup": "listbox", "aria-expanded": pickerOpen, className: [
|
|
157
190
|
"flex-1 min-w-0 h-10 flex items-center gap-[var(--appkit-space-2)] px-[var(--appkit-space-3)] rounded-lg border text-[length:var(--appkit-text-sm)] font-medium transition-colors",
|
|
@@ -99,6 +99,16 @@ export interface BottomActionsState {
|
|
|
99
99
|
* the auction detail page's live countdown ("Ends in 2d 5h").
|
|
100
100
|
*/
|
|
101
101
|
secondaryLabel?: string;
|
|
102
|
+
/**
|
|
103
|
+
* Optional expandable content revealed ABOVE the bar when the buyer taps the
|
|
104
|
+
* `infoLabel` row, which becomes a disclosure toggle while this is set.
|
|
105
|
+
*
|
|
106
|
+
* Exists so a summarised figure in `infoLabel` ("Total: ₹777.00") can be
|
|
107
|
+
* opened up into the detail behind it without navigating away — the mobile
|
|
108
|
+
* counterpart of a "Show details" expander in a desktop sidebar. Ignored in
|
|
109
|
+
* bulk mode, which owns the same space for its action picker.
|
|
110
|
+
*/
|
|
111
|
+
infoPanel?: React.ReactNode;
|
|
102
112
|
}
|
|
103
113
|
interface BottomActionsContextValue {
|
|
104
114
|
state: BottomActionsState;
|
|
@@ -119,6 +129,8 @@ interface BottomActionsContextValue {
|
|
|
119
129
|
setInfoLabel: (label: string | undefined) => void;
|
|
120
130
|
/** Set or clear the secondary label (rendered above infoLabel). */
|
|
121
131
|
setSecondaryLabel: (label: string | undefined) => void;
|
|
132
|
+
/** Set or clear the expandable panel revealed above the bar. */
|
|
133
|
+
setInfoPanel: (panel: React.ReactNode | undefined) => void;
|
|
122
134
|
/** Set the desktop visibility policy. */
|
|
123
135
|
setDesktopMode: (mode: BottomActionsDesktopMode | undefined) => void;
|
|
124
136
|
/** Clear all state (called on feature unmount). */
|
|
@@ -87,6 +87,9 @@ export function BottomActionsProvider({ children, }) {
|
|
|
87
87
|
const setSecondaryLabel = useCallback((secondaryLabel) => {
|
|
88
88
|
setState((prev) => ({ ...prev, secondaryLabel }));
|
|
89
89
|
}, []);
|
|
90
|
+
const setInfoPanel = useCallback((infoPanel) => {
|
|
91
|
+
setState((prev) => ({ ...prev, infoPanel }));
|
|
92
|
+
}, []);
|
|
90
93
|
const setDesktopMode = useCallback((desktop) => {
|
|
91
94
|
setState((prev) => (prev.desktop === desktop ? prev : { ...prev, desktop }));
|
|
92
95
|
}, []);
|
|
@@ -105,6 +108,7 @@ export function BottomActionsProvider({ children, }) {
|
|
|
105
108
|
bulkClearRef,
|
|
106
109
|
setInfoLabel,
|
|
107
110
|
setSecondaryLabel,
|
|
111
|
+
setInfoPanel,
|
|
108
112
|
setDesktopMode,
|
|
109
113
|
clearAll,
|
|
110
114
|
}), [
|
|
@@ -113,6 +117,7 @@ export function BottomActionsProvider({ children, }) {
|
|
|
113
117
|
setBulkConfig,
|
|
114
118
|
setInfoLabel,
|
|
115
119
|
setSecondaryLabel,
|
|
120
|
+
setInfoPanel,
|
|
116
121
|
setDesktopMode,
|
|
117
122
|
clearAll,
|
|
118
123
|
]);
|
|
@@ -8,7 +8,7 @@ export function FooterLayout({ brandName, brandDescription, socialLinks, linkGro
|
|
|
8
8
|
const [openGroups, setOpenGroups] = useState({});
|
|
9
9
|
const toggleGroup = (idx) => setOpenGroups((prev) => ({ ...prev, [idx]: !prev[idx] }));
|
|
10
10
|
const visibleTrustItems = trustBarItems.filter((item) => item.visible !== false);
|
|
11
|
-
return (_jsxs(BlockFooter, { id: id, className: "bg-[var(--appkit-color-bg)] border-t border-[var(--appkit-color-border-subtle)]", children: [showTrustBar && visibleTrustItems.length > 0 && (_jsx(Div, { border: "subtle", className: "border-b", padding: "y-xl", children: _jsx(Div, { paddingX: "x-page", className: "container mx-auto max-w-[1920px]", children: _jsx(Row, { as: "ul", wrap: true, justify: "center", gap: "lg", className: "lg:gap-[var(--appkit-space-10)]", children: visibleTrustItems.map((item, i) => (_jsx(Li, { children: _jsxs(Row, { textSize: "sm", gap: "sm", className: "min-w-[160px]", children: [_jsx(Span, { className: "flex-shrink-0 text-primary-600 dark:text-secondary-400 w-5 h-5", children: item.icon }), _jsxs(Span, { children: [_jsx(Span, { size: "sm", weight: "bold", className: "block", color: "primary", children: item.label }), item.subtitle && (_jsx(Span, { size: "xs", className: "block", color: "muted", children: item.subtitle }))] })] }) }, item.id ?? String(i)))) }) }) })), _jsxs(Div, { paddingX: "x-page", className: "container mx-auto max-w-[1920px] lg:py-[var(--appkit-space-14)]", padding: "y-2xl", children: [_jsxs(Div, { className: "grid grid-cols-1 lg:grid-cols-5 gap-[var(--appkit-space-10)] lg:gap-[var(--appkit-space-16)]", children: [_jsxs(Stack, { className: "lg:col-span-2", gap: "md", children: [_jsx(Heading, { level: 5, size: "xl", weight: "bold", color: "primary", children: brandName }), _jsx(Text, { className: "leading-relaxed max-w-sm", color: "muted", size: "sm", children: brandDescription }), socialLinks.length > 0 && (_jsx(Row, { as: Ul, gap: "sm", children: socialLinks.map((link) => (_jsx(Li, { children: _jsx(Link, { href: link.href, "aria-label": link.ariaLabel, target: "_blank", rel: "noopener noreferrer", className: "flex items-center justify-center w-9 h-9 rounded-full border border-[var(--appkit-color-border)] text-[var(--appkit-color-text-muted)] hover:text-primary dark:hover:text-secondary hover:border-primary/40 dark:hover:border-secondary/40 transition-colors", children: link.icon }) }, link.platform))) })), newsletterEnabled && newsletterSlot && (_jsx(Div, { padding: "t-xs", children: newsletterSlot }))] }), _jsx(Div, { className: `lg:col-span-3 grid grid-cols-1 gap-[var(--appkit-space-0)] lg:gap-[var(--appkit-space-8)] ${linkGroups.length <= 3 ? "lg:grid-cols-3" :
|
|
11
|
+
return (_jsxs(BlockFooter, { id: id, className: "bg-[var(--appkit-color-bg)] border-t border-[var(--appkit-color-border-subtle)]", children: [showTrustBar && visibleTrustItems.length > 0 && (_jsx(Div, { border: "subtle", className: "border-b", padding: "y-xl", children: _jsx(Div, { paddingX: "x-page", className: "container mx-auto max-w-[1920px]", children: _jsx(Row, { as: "ul", wrap: true, justify: "center", gap: "lg", className: "lg:gap-[var(--appkit-space-10)]", children: visibleTrustItems.map((item, i) => (_jsx(Li, { children: _jsxs(Row, { textSize: "sm", gap: "sm", className: "min-w-[160px]", children: [_jsx(Span, { className: "flex-shrink-0 text-primary-600 dark:text-secondary-400 w-5 h-5", children: item.icon }), _jsxs(Span, { children: [_jsx(Span, { size: "sm", weight: "bold", className: "block", color: "primary", children: item.label }), item.subtitle && (_jsx(Span, { size: "xs", className: "block", color: "muted", weight: "medium", children: item.subtitle }))] })] }) }, item.id ?? String(i)))) }) }) })), _jsxs(Div, { paddingX: "x-page", className: "container mx-auto max-w-[1920px] lg:py-[var(--appkit-space-14)]", padding: "y-2xl", children: [_jsxs(Div, { className: "grid grid-cols-1 lg:grid-cols-5 gap-[var(--appkit-space-10)] lg:gap-[var(--appkit-space-16)]", children: [_jsxs(Stack, { className: "lg:col-span-2", gap: "md", children: [_jsx(Heading, { level: 5, size: "xl", weight: "bold", color: "primary", children: brandName }), _jsx(Text, { className: "leading-relaxed max-w-sm", color: "muted", size: "sm", weight: "medium", children: brandDescription }), socialLinks.length > 0 && (_jsx(Row, { as: Ul, gap: "sm", children: socialLinks.map((link) => (_jsx(Li, { children: _jsx(Link, { href: link.href, "aria-label": link.ariaLabel, target: "_blank", rel: "noopener noreferrer", className: "flex items-center justify-center w-9 h-9 rounded-full border border-[var(--appkit-color-border)] text-[var(--appkit-color-text-muted)] hover:text-primary dark:hover:text-secondary hover:border-primary/40 dark:hover:border-secondary/40 transition-colors", children: link.icon }) }, link.platform))) })), newsletterEnabled && newsletterSlot && (_jsx(Div, { padding: "t-xs", children: newsletterSlot }))] }), _jsx(Div, { className: `lg:col-span-3 grid grid-cols-1 gap-[var(--appkit-space-0)] lg:gap-[var(--appkit-space-8)] ${linkGroups.length <= 3 ? "lg:grid-cols-3" :
|
|
12
12
|
linkGroups.length === 4 ? "lg:grid-cols-4" :
|
|
13
|
-
"lg:grid-cols-5"}`, children: linkGroups.map((group, idx) => (_jsxs(Div, { children: [_jsxs(Button, { type: "button", variant: "ghost", onClick: () => toggleGroup(idx), className: "w-full justify-between border-b py-[var(--appkit-space-3)] text-left text-[length:var(--appkit-text-sm)] font-semibold transition-colors border-[var(--appkit-color-border-subtle)] text-[var(--appkit-color-text)] hover:text-primary dark:hover:text-secondary lg:hidden", "aria-expanded": !!openGroups[idx], children: [group.heading, _jsx(ChevronDown, { className: `w-4 h-4 transition-transform duration-200 ${openGroups[idx] ? "rotate-180" : ""}` })] }), _jsx(Ul, { className: `lg:hidden overflow-hidden transition-all duration-200 ${openGroups[idx] ? "max-h-96 py-[var(--appkit-space-3)]" : "max-h-0"} space-y-2.5`, children: group.links.map((link) => (_jsx(Li, { children: _jsx(Link, { href: link.href, className: "text-[length:var(--appkit-text-sm)] text-[var(--appkit-color-text-muted)] hover:text-primary dark:hover:text-secondary transition-colors", children: link.label }) }, link.href))) }), _jsxs(Div, { className: "hidden lg:block", children: [_jsx(Text, { className: "tracking-wider mb-3", color: "muted", size: "xs", weight: "
|
|
13
|
+
"lg:grid-cols-5"}`, children: linkGroups.map((group, idx) => (_jsxs(Div, { children: [_jsxs(Button, { type: "button", variant: "ghost", onClick: () => toggleGroup(idx), className: "w-full justify-between border-b py-[var(--appkit-space-3)] text-left text-[length:var(--appkit-text-sm)] font-semibold transition-colors border-[var(--appkit-color-border-subtle)] text-[var(--appkit-color-text)] hover:text-primary dark:hover:text-secondary lg:hidden", "aria-expanded": !!openGroups[idx], children: [group.heading, _jsx(ChevronDown, { className: `w-4 h-4 transition-transform duration-200 ${openGroups[idx] ? "rotate-180" : ""}` })] }), _jsx(Ul, { className: `lg:hidden overflow-hidden transition-all duration-200 ${openGroups[idx] ? "max-h-96 py-[var(--appkit-space-3)]" : "max-h-0"} space-y-2.5`, children: group.links.map((link) => (_jsx(Li, { children: _jsx(Link, { href: link.href, className: "text-[length:var(--appkit-text-sm)] font-medium text-[var(--appkit-color-text-muted)] hover:text-primary dark:hover:text-secondary transition-colors", children: link.label }) }, link.href))) }), _jsxs(Div, { className: "hidden lg:block", children: [_jsx(Text, { className: "tracking-wider mb-3", color: "muted", size: "xs", weight: "bold", transform: "uppercase", children: group.heading }), _jsx(Ul, { className: "space-y-2.5", children: group.links.map((link) => (_jsx(Li, { children: _jsx(Link, { href: link.href, className: "text-[length:var(--appkit-text-sm)] font-medium text-[var(--appkit-color-text-muted)] hover:text-primary dark:hover:text-secondary transition-colors", children: link.label }) }, link.href))) })] })] }, idx))) })] }), _jsxs(Stack, { direction: "sm-row", color: "muted", textSize: "xs", justify: "between", border: "default", className: "mt-10 border-t border-[var(--appkit-color-border-subtle)] text-[var(--appkit-color-text-muted)]", padding: "t-lg", align: "center", gap: "3", children: [_jsx(Text, { size: "xs", color: "muted", weight: "medium", children: copyrightText }), bottomLinks.length > 0 && (_jsx(Nav, { "aria-label": "Site utilities", children: _jsx(Ul, { className: "flex flex-wrap items-center gap-x-4 gap-y-1 justify-center", children: bottomLinks.map((link) => (_jsx(Li, { children: _jsx(Link, { href: link.href, className: "text-[length:var(--appkit-text-xs)] font-medium text-[var(--appkit-color-text-muted)] hover:text-primary dark:hover:text-secondary transition-colors underline-offset-2 hover:underline", children: link.label }) }, link.href))) }) })), madeInText && (_jsx(Text, { size: "xs", color: "muted", weight: "medium", children: madeInText }))] })] })] }));
|
|
14
14
|
}
|
|
@@ -4,7 +4,7 @@ import { Avatar, BlockHeader, Button, Div, Row, Section, SiteLogo, SiteMark, Spa
|
|
|
4
4
|
/** Shared icon-button class for all action buttons in the title bar. */
|
|
5
5
|
const iconBtn = "flex items-center justify-center w-9 h-9 rounded-lg text-[var(--appkit-color-text-muted)] hover:bg-primary-50 hover:text-primary-700 dark:hover:bg-[var(--appkit-color-surface-elevated)] dark:hover:text-secondary-400 transition-colors";
|
|
6
6
|
/** Badge counter class for wishlist/cart counts. */
|
|
7
|
-
const countBadge = "absolute -top-0.5 -right-0.5 flex items-center justify-center w-4 h-4 rounded-full bg-error-
|
|
7
|
+
const countBadge = "absolute -top-0.5 -right-0.5 flex items-center justify-center w-4 h-4 rounded-full bg-error-solid text-error-on-solid text-[10px] font-bold leading-none";
|
|
8
8
|
/**
|
|
9
9
|
* TitleBarLayout — generic top sticky title-bar shell.
|
|
10
10
|
*
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type React from "react";
|
|
1
2
|
import { type BottomAction, type BottomActionsDesktopMode, type BottomBulkConfig } from "../BottomActionsContext";
|
|
2
3
|
export interface UseBottomActionsOptions {
|
|
3
4
|
actions?: BottomAction[];
|
|
@@ -5,6 +6,11 @@ export interface UseBottomActionsOptions {
|
|
|
5
6
|
infoLabel?: string;
|
|
6
7
|
/** Rendered as its own row above infoLabel. See BottomActionsState.secondaryLabel. */
|
|
7
8
|
secondaryLabel?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Expandable detail revealed above the bar when the infoLabel row is tapped.
|
|
11
|
+
* See BottomActionsState.infoPanel.
|
|
12
|
+
*/
|
|
13
|
+
infoPanel?: React.ReactNode;
|
|
8
14
|
/**
|
|
9
15
|
* Whether the bar may also show on desktop. Defaults to "hidden" (mobile/tablet only),
|
|
10
16
|
* so pages that don't opt in behave exactly as before.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { useEffect, useRef } from "react";
|
|
3
3
|
import { useBottomActionsContext, } from "../BottomActionsContext";
|
|
4
4
|
export function useBottomActions(options = {}) {
|
|
5
|
-
const { setActions, setBulkConfig, setInfoLabel, setSecondaryLabel, setDesktopMode, clearAll, } = useBottomActionsContext();
|
|
5
|
+
const { setActions, setBulkConfig, setInfoLabel, setSecondaryLabel, setInfoPanel, setDesktopMode, clearAll, } = useBottomActionsContext();
|
|
6
6
|
const actionsRef = useRef(options.actions ?? []);
|
|
7
7
|
actionsRef.current = options.actions ?? [];
|
|
8
8
|
const bulkRef = useRef(options.bulk);
|
|
@@ -25,6 +25,16 @@ export function useBottomActions(options = {}) {
|
|
|
25
25
|
useEffect(() => {
|
|
26
26
|
setSecondaryLabel(options.secondaryLabel);
|
|
27
27
|
}, [setSecondaryLabel, options.secondaryLabel]);
|
|
28
|
+
// A ReactNode is a fresh object on every render, so it can't be a dependency
|
|
29
|
+
// without looping. Mirror it through a ref and re-publish whenever anything
|
|
30
|
+
// the panel actually derives from changes — callers pass `infoLabel`, which
|
|
31
|
+
// moves with the same data the panel shows.
|
|
32
|
+
const infoPanelRef = useRef(options.infoPanel);
|
|
33
|
+
infoPanelRef.current = options.infoPanel;
|
|
34
|
+
const hasInfoPanel = options.infoPanel != null;
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
setInfoPanel(infoPanelRef.current);
|
|
37
|
+
}, [setInfoPanel, hasInfoPanel, options.infoLabel, options.secondaryLabel]);
|
|
28
38
|
useEffect(() => {
|
|
29
39
|
setDesktopMode(options.desktop);
|
|
30
40
|
}, [setDesktopMode, options.desktop]);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Icon badges for an order's paid add-ons and applied coupon.
|
|
4
|
+
*
|
|
5
|
+
* These are fulfilment instructions, not decoration: WhatsApp tells the
|
|
6
|
+
* status-change notifier to message this buyer, gift wrap tells the packer to
|
|
7
|
+
* wrap the parcel and enclose the message, shipment protection changes how a
|
|
8
|
+
* loss claim is handled. They are recorded on the order (and filterable in the
|
|
9
|
+
* `orders` Sieve config) so nobody has to keep a separate list of who asked for
|
|
10
|
+
* what — the order IS the record.
|
|
11
|
+
*
|
|
12
|
+
* Rendered on every order surface — buyer detail, seller list + detail, admin
|
|
13
|
+
* list + detail — from one implementation, so a new add-on shows up everywhere
|
|
14
|
+
* at once instead of being wired per screen.
|
|
15
|
+
*/
|
|
16
|
+
export interface OrderAddonBadgesOrder {
|
|
17
|
+
whatsappNotifyAddon?: boolean;
|
|
18
|
+
whatsappNotifyFee?: number;
|
|
19
|
+
giftWrapAddon?: boolean;
|
|
20
|
+
giftWrapFee?: number;
|
|
21
|
+
giftWrapMessage?: string;
|
|
22
|
+
shipmentProtectionAddon?: boolean;
|
|
23
|
+
shipmentProtectionFee?: number;
|
|
24
|
+
couponCode?: string;
|
|
25
|
+
couponDiscount?: number;
|
|
26
|
+
}
|
|
27
|
+
export interface OrderAddonBadgesProps {
|
|
28
|
+
order: OrderAddonBadgesOrder;
|
|
29
|
+
/** `"compact"` — icon chips only, for list rows. `"detail"` — chips + the gift message and fees. */
|
|
30
|
+
variant?: "compact" | "detail";
|
|
31
|
+
/** Hide the coupon chip where the coupon is already shown in a price breakdown. */
|
|
32
|
+
showCoupon?: boolean;
|
|
33
|
+
currency?: string;
|
|
34
|
+
className?: string;
|
|
35
|
+
}
|
|
36
|
+
export declare function OrderAddonBadges({ order, variant, showCoupon, currency, className, }: OrderAddonBadgesProps): React.JSX.Element | null;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Gift, MessageCircle, ShieldCheck, Tag } from "lucide-react";
|
|
4
|
+
import { Row, Span, Stack, Text } from "../../../ui";
|
|
5
|
+
import { formatCurrency } from "../../../utils/number.formatter";
|
|
6
|
+
const CHIP_BASE = "inline-flex items-center gap-[var(--appkit-space-1)] rounded-full px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-[length:var(--appkit-text-xs)] font-medium";
|
|
7
|
+
function Chip({ icon, label, tone, title, }) {
|
|
8
|
+
const toneCls = tone === "success"
|
|
9
|
+
? "bg-success-surface text-success"
|
|
10
|
+
: tone === "info"
|
|
11
|
+
? "bg-info-surface text-info"
|
|
12
|
+
: "bg-primary-50 text-primary-700 dark:bg-primary-950/40 dark:text-primary-300";
|
|
13
|
+
return (_jsxs(Span, { className: `${CHIP_BASE} ${toneCls}`, title: title, children: [_jsx(Span, { className: "flex-shrink-0", "aria-hidden": "true", children: icon }), label] }));
|
|
14
|
+
}
|
|
15
|
+
export function OrderAddonBadges({ order, variant = "compact", showCoupon = true, currency = "INR", className = "", }) {
|
|
16
|
+
const money = (v) => (typeof v === "number" && v > 0 ? formatCurrency(v, currency) : undefined);
|
|
17
|
+
const hasCoupon = showCoupon && !!order.couponCode;
|
|
18
|
+
const hasAny = order.whatsappNotifyAddon ||
|
|
19
|
+
order.giftWrapAddon ||
|
|
20
|
+
order.shipmentProtectionAddon ||
|
|
21
|
+
hasCoupon;
|
|
22
|
+
if (!hasAny)
|
|
23
|
+
return null;
|
|
24
|
+
const isDetail = variant === "detail";
|
|
25
|
+
return (_jsxs(Stack, { gap: "xs", className: `min-w-0 ${className}`, children: [_jsxs(Row, { gap: "xs", align: "center", wrap: true, className: "min-w-0", children: [order.whatsappNotifyAddon && (_jsx(Chip, { icon: _jsx(MessageCircle, { size: 12 }), label: isDetail && money(order.whatsappNotifyFee) ? `WhatsApp updates · ${money(order.whatsappNotifyFee)}` : "WhatsApp updates", tone: "success", title: "Buyer opted into WhatsApp order-status updates \u2014 status changes on this order should notify them on WhatsApp." })), order.giftWrapAddon && (_jsx(Chip, { icon: _jsx(Gift, { size: 12 }), label: isDetail && money(order.giftWrapFee) ? `Gift wrap · ${money(order.giftWrapFee)}` : "Gift wrap", tone: "brand", title: "This parcel must be gift-wrapped before dispatch." })), order.shipmentProtectionAddon && (_jsx(Chip, { icon: _jsx(ShieldCheck, { size: 12 }), label: isDetail && money(order.shipmentProtectionFee) ? `Protected · ${money(order.shipmentProtectionFee)}` : "Protected", tone: "info", title: "Shipment protection purchased \u2014 loss or damage in transit is covered." })), hasCoupon && (_jsx(Chip, { icon: _jsx(Tag, { size: 12 }), label: isDetail && money(order.couponDiscount)
|
|
26
|
+
? `${order.couponCode} · −${money(order.couponDiscount)}`
|
|
27
|
+
: order.couponCode, tone: "success", title: `Coupon ${order.couponCode} applied to this order.` }))] }), isDetail && order.giftWrapAddon && order.giftWrapMessage && (_jsxs(Stack, { gap: "xs", className: "min-w-0", children: [_jsx(Text, { size: "xs", color: "muted", weight: "semibold", transform: "uppercase", className: "tracking-wide", children: "Gift message" }), _jsx(Text, { size: "sm", color: "primary", className: "min-w-0 whitespace-pre-wrap break-words", children: order.giftWrapMessage })] }))] }));
|
|
28
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { OrderCard, OrdersList } from "./OrdersList";
|
|
2
|
+
export { OrderAddonBadges, type OrderAddonBadgesOrder, type OrderAddonBadgesProps, } from "./OrderAddonBadges";
|
|
2
3
|
export { MarketplaceOrderCard, type MarketplaceOrderCardLabels, type MarketplaceOrderCardLinks, type MarketplaceOrderCardOrder, type MarketplaceOrderCardProps, } from "./MarketplaceOrderCard";
|
|
3
4
|
export { OrderFilters, ORDER_FILTER_KEYS, ORDER_ADMIN_SORT_OPTIONS, ORDER_SELLER_SORT_OPTIONS, ORDER_USER_SORT_OPTIONS, getOrderFilterKeys, getOrderSortOptions, } from "./OrderFilters";
|
|
4
5
|
export type { OrderFiltersProps, OrderFilterVariant } from "./OrderFilters";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { OrderCard, OrdersList } from "./OrdersList";
|
|
2
|
+
export { OrderAddonBadges, } from "./OrderAddonBadges";
|
|
2
3
|
export { MarketplaceOrderCard, } from "./MarketplaceOrderCard";
|
|
3
4
|
export { OrderFilters, ORDER_FILTER_KEYS, ORDER_ADMIN_SORT_OPTIONS, ORDER_SELLER_SORT_OPTIONS, ORDER_USER_SORT_OPTIONS, getOrderFilterKeys, getOrderSortOptions, } from "./OrderFilters";
|
|
4
5
|
export { OrderSiblingPayments } from "./OrderSiblingPayments";
|
|
@@ -101,6 +101,22 @@ declare class OrderRepository extends BaseRepository<OrderDocument> {
|
|
|
101
101
|
canFilter: boolean;
|
|
102
102
|
canSort: boolean;
|
|
103
103
|
};
|
|
104
|
+
whatsappNotifyAddon: {
|
|
105
|
+
canFilter: boolean;
|
|
106
|
+
canSort: boolean;
|
|
107
|
+
};
|
|
108
|
+
giftWrapAddon: {
|
|
109
|
+
canFilter: boolean;
|
|
110
|
+
canSort: boolean;
|
|
111
|
+
};
|
|
112
|
+
shipmentProtectionAddon: {
|
|
113
|
+
canFilter: boolean;
|
|
114
|
+
canSort: boolean;
|
|
115
|
+
};
|
|
116
|
+
couponCode: {
|
|
117
|
+
canFilter: boolean;
|
|
118
|
+
canSort: boolean;
|
|
119
|
+
};
|
|
104
120
|
};
|
|
105
121
|
listForSeller(productIds: string[], model: SieveModel): Promise<FirebaseSieveResult<OrderDocument>>;
|
|
106
122
|
listForUser(userId: string, model: SieveModel): Promise<FirebaseSieveResult<OrderDocument>>;
|
|
@@ -179,6 +195,22 @@ declare class OrderRepository extends BaseRepository<OrderDocument> {
|
|
|
179
195
|
canFilter: boolean;
|
|
180
196
|
canSort: boolean;
|
|
181
197
|
};
|
|
198
|
+
whatsappNotifyAddon: {
|
|
199
|
+
canFilter: boolean;
|
|
200
|
+
canSort: boolean;
|
|
201
|
+
};
|
|
202
|
+
giftWrapAddon: {
|
|
203
|
+
canFilter: boolean;
|
|
204
|
+
canSort: boolean;
|
|
205
|
+
};
|
|
206
|
+
shipmentProtectionAddon: {
|
|
207
|
+
canFilter: boolean;
|
|
208
|
+
canSort: boolean;
|
|
209
|
+
};
|
|
210
|
+
couponCode: {
|
|
211
|
+
canFilter: boolean;
|
|
212
|
+
canSort: boolean;
|
|
213
|
+
};
|
|
182
214
|
};
|
|
183
215
|
listAll(model: SieveModel): Promise<FirebaseSieveResult<OrderDocument>>;
|
|
184
216
|
/**
|
|
@@ -496,6 +496,13 @@ OrderRepository.SELLER_SIEVE_FIELDS = {
|
|
|
496
496
|
orderDate: { canFilter: true, canSort: true, parseValue: parseSieveDateValue },
|
|
497
497
|
createdAt: { canFilter: true, canSort: true, parseValue: parseSieveDateValue },
|
|
498
498
|
contestable: { canFilter: true, canSort: false },
|
|
499
|
+
// Fulfilment flags. Filterable so "which of my orders need gift wrap?" is a
|
|
500
|
+
// query against the orders themselves — the whole point of recording the
|
|
501
|
+
// add-on on the order rather than keeping a side list of who asked for what.
|
|
502
|
+
whatsappNotifyAddon: { canFilter: true, canSort: false },
|
|
503
|
+
giftWrapAddon: { canFilter: true, canSort: false },
|
|
504
|
+
shipmentProtectionAddon: { canFilter: true, canSort: false },
|
|
505
|
+
couponCode: { canFilter: true, canSort: false },
|
|
499
506
|
};
|
|
500
507
|
OrderRepository.ADMIN_SIEVE_FIELDS = {
|
|
501
508
|
id: { canFilter: true, canSort: false },
|
|
@@ -520,6 +527,13 @@ OrderRepository.ADMIN_SIEVE_FIELDS = {
|
|
|
520
527
|
// Never filtered as "standard" via Firestore == (see listForUser callers):
|
|
521
528
|
// orders written before this field existed have no value to match.
|
|
522
529
|
orderType: { canFilter: true, canSort: false },
|
|
530
|
+
// Same fulfilment flags as SELLER_SIEVE_FIELDS — admin needs to answer
|
|
531
|
+
// "who opted into WhatsApp updates?" when a status change goes out, without
|
|
532
|
+
// maintaining a parallel tracker.
|
|
533
|
+
whatsappNotifyAddon: { canFilter: true, canSort: false },
|
|
534
|
+
giftWrapAddon: { canFilter: true, canSort: false },
|
|
535
|
+
shipmentProtectionAddon: { canFilter: true, canSort: false },
|
|
536
|
+
couponCode: { canFilter: true, canSort: false },
|
|
523
537
|
};
|
|
524
538
|
const orderRepository = new OrderRepository();
|
|
525
539
|
export { OrderRepository, orderRepository };
|
|
@@ -79,8 +79,30 @@ export interface Order {
|
|
|
79
79
|
* store plus one platform-wide coupon and this scalar shows only one of them.
|
|
80
80
|
*/
|
|
81
81
|
couponCode?: string;
|
|
82
|
+
/** Total rupee value of every discount applied to this order. */
|
|
83
|
+
couponDiscount?: number;
|
|
82
84
|
/** Every discount applied to this order — the full stack, not just the first. */
|
|
83
85
|
appliedDiscounts?: AppliedOrderDiscount[];
|
|
86
|
+
/**
|
|
87
|
+
* Paid add-ons the buyer opted into for THIS order (= this store).
|
|
88
|
+
*
|
|
89
|
+
* These are operational flags, not just billing records: `whatsappNotifyAddon`
|
|
90
|
+
* is what the status-change notifier checks to decide whether to send a
|
|
91
|
+
* WhatsApp message, and `giftWrapAddon`/`shipmentProtectionAddon` are what
|
|
92
|
+
* the packer needs to see before dispatch. They live on the order itself and
|
|
93
|
+
* are filterable (`orders` SIEVE_FIELDS) precisely so nobody has to keep a
|
|
94
|
+
* separate list of who asked for what.
|
|
95
|
+
*/
|
|
96
|
+
whatsappNotifyAddon?: boolean;
|
|
97
|
+
whatsappNotifyFee?: number;
|
|
98
|
+
giftWrapAddon?: boolean;
|
|
99
|
+
giftWrapFee?: number;
|
|
100
|
+
/** The buyer's gift message — the packer has to physically include this. */
|
|
101
|
+
giftWrapMessage?: string;
|
|
102
|
+
shipmentProtectionAddon?: boolean;
|
|
103
|
+
shipmentProtectionFee?: number;
|
|
104
|
+
/** Buyer-facing platform commission apportioned to this order. */
|
|
105
|
+
platformFee?: number;
|
|
84
106
|
trackingNumber?: string;
|
|
85
107
|
shippingCarrier?: string;
|
|
86
108
|
/** Carrier tracking page URL — rendered as an external link that opens in a new tab. No live-tracking API integration; this is just the passthrough URL a seller/admin entered. */
|
|
@@ -10,10 +10,10 @@ import { ROUTES } from "../../../next";
|
|
|
10
10
|
import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
|
|
11
11
|
import { formatCurrency } from "../../../utils";
|
|
12
12
|
import { getDefaultCurrency } from "../../../core/baseline-resolver";
|
|
13
|
-
const CLS_PREORDER_BADGE = "inline-flex items-center rounded-full bg-warning-
|
|
14
|
-
const CLS_SALE_BADGE = "rounded-full bg-success-
|
|
13
|
+
const CLS_PREORDER_BADGE = "inline-flex items-center rounded-full bg-warning-solid px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-warning-on-solid";
|
|
14
|
+
const CLS_SALE_BADGE = "rounded-full bg-success-solid px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-[length:var(--appkit-text-xs)] font-bold text-success-on-solid";
|
|
15
15
|
const CLS_TRENDING_BADGE = "rounded-full bg-indigo-600 px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-[length:var(--appkit-text-xs)] font-bold text-white";
|
|
16
|
-
const CLS_LIMITED_BADGE = "rounded-full bg-info-
|
|
16
|
+
const CLS_LIMITED_BADGE = "rounded-full bg-info-solid px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-[length:var(--appkit-text-xs)] font-bold text-info-on-solid";
|
|
17
17
|
import { BaseListingCard, Button, RichText, Row, Span, Stack, Text, TextLink } from "../../../ui";
|
|
18
18
|
import { normalizeRichTextHtml } from "../../../utils/string.formatter";
|
|
19
19
|
import { PreorderBadge } from "./PreorderCard";
|
|
@@ -4,9 +4,8 @@ import { AdSlot } from "../../homepage/components/AdSlot";
|
|
|
4
4
|
import { ProductsIndexListing } from "./ProductsIndexListing";
|
|
5
5
|
import { PRODUCT_FIELDS } from "../../../constants/field-names";
|
|
6
6
|
import { sortBy } from "../../../constants/sort";
|
|
7
|
-
import { ART_STICKERS_TYPE_FILTER_TABS } from "../constants/listing-tabs";
|
|
8
|
-
import { listPublicProducts, parsePublicProductParams, } from "../../../_internal/server/features/products/list-public";
|
|
9
|
-
const ART_STICKERS_LISTING_TYPES = ["art", "stickers"];
|
|
7
|
+
import { ART_STICKERS_TYPE_FILTER_TABS, ART_STICKERS_LISTING_TYPES, } from "../constants/listing-tabs";
|
|
8
|
+
import { listPublicProducts, parsePublicProductParams, defaultTogglesForListingTypes, } from "../../../_internal/server/features/products/list-public";
|
|
10
9
|
const DEFAULT_PAGE_SIZE = 24;
|
|
11
10
|
const DEFAULT_SORT = sortBy(PRODUCT_FIELDS.CREATED_AT);
|
|
12
11
|
export async function ArtStickersListView({ searchParams = {} }) {
|
|
@@ -17,7 +16,9 @@ export async function ArtStickersListView({ searchParams = {} }) {
|
|
|
17
16
|
listingTypes: ART_STICKERS_LISTING_TYPES,
|
|
18
17
|
pageSize: DEFAULT_PAGE_SIZE,
|
|
19
18
|
sorts: DEFAULT_SORT,
|
|
20
|
-
|
|
19
|
+
// Derived, so this page and ProductsIndexListing can never disagree
|
|
20
|
+
// about the default view (Root Cause #30).
|
|
21
|
+
...defaultTogglesForListingTypes(ART_STICKERS_LISTING_TYPES),
|
|
21
22
|
}));
|
|
22
23
|
return (_jsxs(Main, { children: [_jsx(Section, { border: "bottom-subtle", paddingY: "b-md", padding: "t-xl", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 1, size: "3xl", weight: "bold", color: "primary", children: "Art & Stickers" }), _jsx(Text, { className: "mt-1", color: "muted", size: "sm", children: "Original art prints and sticker packs from independent sellers" })] }) }), _jsxs(Container, { size: "xl", padding: "x-md", children: [_jsx(AdSlot, { id: "listing-sidebar-top", className: "mb-4 mt-4" }), _jsx(ProductsIndexListing, { initialData: result ?? null, listingTypes: ART_STICKERS_LISTING_TYPES, typeTabs: ART_STICKERS_TYPE_FILTER_TABS, searchPlaceholder: "Search art & stickers..." }), _jsx(AdSlot, { id: "listing-sidebar-bottom", className: "mt-8" })] })] }));
|
|
23
24
|
}
|
|
@@ -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) {
|