@mohasinac/appkit 2.7.49 → 2.7.52
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/client/features/layout/SidebarCollapseToggle.d.ts +19 -0
- package/dist/_internal/client/features/layout/SidebarCollapseToggle.js +15 -0
- package/dist/_internal/client/features/layout/index.d.ts +2 -0
- package/dist/_internal/client/features/layout/index.js +1 -0
- package/dist/_internal/server/features/checkout/actions.js +25 -2
- package/dist/_internal/server/features/promotions/actions.js +3 -1
- package/dist/_internal/server/jobs/core/__tests__/couponExpiry.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/couponExpiry.test.js +65 -0
- package/dist/_internal/server/jobs/core/__tests__/listingProcessor.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/listingProcessor.test.js +163 -0
- package/dist/_internal/server/jobs/core/__tests__/mediaTmpCleanup.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/mediaTmpCleanup.test.js +115 -0
- package/dist/_internal/server/jobs/core/__tests__/pendingOrderTimeout.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/pendingOrderTimeout.test.js +96 -0
- package/dist/_internal/shared/actions/action-registry.js +149 -0
- package/dist/client.d.ts +45 -6
- package/dist/client.js +27 -4
- package/dist/constants/api-endpoints.d.ts +42 -0
- package/dist/constants/api-endpoints.js +14 -0
- package/dist/features/_guide-cls.d.ts +13 -0
- package/dist/features/_guide-cls.js +14 -0
- package/dist/features/about/components/HelpPageView.js +29 -26
- package/dist/features/account/components/BuyerAccountGuideView.d.ts +1 -0
- package/dist/features/account/components/BuyerAccountGuideView.js +39 -0
- package/dist/features/account/components/BuyerAuctionsGuideView.d.ts +1 -0
- package/dist/features/account/components/BuyerAuctionsGuideView.js +38 -0
- package/dist/features/account/components/BuyerOrdersGuideView.d.ts +1 -0
- package/dist/features/account/components/BuyerOrdersGuideView.js +48 -0
- package/dist/features/account/components/BuyerShoppingGuideView.d.ts +1 -0
- package/dist/features/account/components/BuyerShoppingGuideView.js +38 -0
- package/dist/features/account/components/UserSidebar.js +2 -1
- package/dist/features/admin/components/AdminAnalyticsGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminAnalyticsGuideView.js +24 -0
- package/dist/features/admin/components/AdminBlogEditorView.js +102 -75
- package/dist/features/admin/components/AdminBundleEditorView.js +20 -14
- package/dist/features/admin/components/AdminBundlesView.d.ts +1 -3
- package/dist/features/admin/components/AdminBundlesView.js +173 -52
- package/dist/features/admin/components/AdminCatalogGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminCatalogGuideView.js +28 -0
- package/dist/features/admin/components/AdminContentGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminContentGuideView.js +36 -0
- package/dist/features/admin/components/AdminCouponEditorView.js +1 -1
- package/dist/features/admin/components/AdminGuideHubView.d.ts +5 -0
- package/dist/features/admin/components/AdminGuideHubView.js +79 -0
- package/dist/features/admin/components/AdminOrdersGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminOrdersGuideView.js +44 -0
- package/dist/features/admin/components/AdminProductsView.js +8 -3
- package/dist/features/admin/components/AdminSidebar.js +2 -1
- package/dist/features/admin/components/AdminSiteConfigGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminSiteConfigGuideView.js +21 -0
- package/dist/features/admin/components/AdminStoresGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminStoresGuideView.js +32 -0
- package/dist/features/admin/components/AdminTeamGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminTeamGuideView.js +33 -0
- package/dist/features/admin/components/AdminTrustGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminTrustGuideView.js +40 -0
- package/dist/features/admin/components/AdminUsersGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminUsersGuideView.js +38 -0
- package/dist/features/auth/repository/session.repository.js +16 -1
- package/dist/features/cart/hooks/useAddToCart.d.ts +2 -0
- package/dist/features/cart/hooks/useAddToCart.js +2 -0
- package/dist/features/cart/hooks/useGuestCart.d.ts +2 -0
- package/dist/features/cart/utils/guest-cart.d.ts +7 -0
- package/dist/features/cart/utils/pending-ops.d.ts +6 -0
- package/dist/features/categories/components/CategoryBundlesListing.js +3 -25
- package/dist/features/categories/components/CategoryProductsListing.js +6 -10
- package/dist/features/categories/components/index.d.ts +0 -2
- package/dist/features/categories/components/index.js +0 -1
- package/dist/features/categories/schemas/firestore.d.ts +9 -0
- package/dist/features/events/components/AdminEventEditorView.js +223 -269
- package/dist/features/events/components/EventRaffleWinnerView.d.ts +9 -1
- package/dist/features/events/components/EventRaffleWinnerView.js +3 -3
- package/dist/features/events/components/SpinWheelView.js +3 -3
- package/dist/features/grouped/repository/grouped-listings.repository.d.ts +9 -0
- package/dist/features/grouped/repository/grouped-listings.repository.js +12 -0
- package/dist/features/layout/BottomNavLayout.d.ts +5 -0
- package/dist/features/layout/BottomNavLayout.js +21 -0
- package/dist/features/layout/TitleBar.js +7 -1
- package/dist/features/layout/TitleBarLayout.d.ts +8 -2
- package/dist/features/layout/TitleBarLayout.js +11 -7
- package/dist/features/media/upload/MediaUploadField.js +1 -1
- package/dist/features/orders/schemas/index.d.ts +2 -2
- package/dist/features/pre-orders/components/PreOrdersIndexListing.js +12 -6
- package/dist/features/products/components/AuctionsIndexListing.js +3 -1
- package/dist/features/products/components/MarketplaceBundleCard.d.ts +21 -0
- package/dist/features/products/components/MarketplaceBundleCard.js +56 -0
- package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
- package/dist/features/products/components/PrizeDrawEntryActions.d.ts +3 -1
- package/dist/features/products/components/PrizeDrawEntryActions.js +8 -12
- package/dist/features/products/components/ProductDetailActions.d.ts +5 -1
- package/dist/features/products/components/ProductDetailActions.js +3 -1
- package/dist/features/products/components/ProductDetailPageView.d.ts +2 -0
- package/dist/features/products/components/ProductDetailPageView.js +4 -0
- package/dist/features/products/components/ProductGrid.js +3 -3
- package/dist/features/products/components/ProductsIndexListing.js +18 -9
- package/dist/features/products/components/index.d.ts +2 -4
- package/dist/features/products/components/index.js +1 -2
- package/dist/features/promotions/components/CouponCard.d.ts +11 -1
- package/dist/features/promotions/components/CouponCard.js +36 -2
- package/dist/features/promotions/repository/claimed-coupons.repository.d.ts +27 -0
- package/dist/features/promotions/repository/claimed-coupons.repository.js +115 -0
- package/dist/features/promotions/schemas/firestore.d.ts +34 -0
- package/dist/features/promotions/schemas/firestore.js +14 -1
- package/dist/features/reviews/components/index.d.ts +0 -2
- package/dist/features/reviews/components/index.js +0 -1
- package/dist/features/seller/components/BrandInlineSelect.js +23 -3
- package/dist/features/seller/components/CategoryInlineSelect.js +20 -3
- package/dist/features/seller/components/SellerAddressesView.js +3 -2
- package/dist/features/seller/components/SellerAnalyticsAlertsView.d.ts +6 -0
- package/dist/features/seller/components/SellerAnalyticsAlertsView.js +124 -0
- package/dist/features/seller/components/SellerAnalyticsView.d.ts +2 -0
- package/dist/features/seller/components/SellerAnalyticsView.js +17 -9
- package/dist/features/seller/components/SellerBidsView.js +33 -3
- package/dist/features/seller/components/SellerBundlesView.d.ts +5 -0
- package/dist/features/seller/components/SellerBundlesView.js +104 -0
- package/dist/features/seller/components/SellerClassifiedView.d.ts +5 -0
- package/dist/features/seller/components/SellerClassifiedView.js +113 -0
- package/dist/features/seller/components/SellerDashboardView.js +19 -7
- package/dist/features/seller/components/SellerDigitalCodesView.d.ts +5 -0
- package/dist/features/seller/components/SellerDigitalCodesView.js +115 -0
- package/dist/features/seller/components/SellerGoogleReviewsView.d.ts +14 -0
- package/dist/features/seller/components/SellerGoogleReviewsView.js +95 -0
- package/dist/features/seller/components/SellerGroupedListingsView.d.ts +6 -0
- package/dist/features/seller/components/SellerGroupedListingsView.js +112 -0
- package/dist/features/seller/components/SellerLiveView.d.ts +5 -0
- package/dist/features/seller/components/SellerLiveView.js +113 -0
- package/dist/features/seller/components/SellerOrdersView.js +53 -2
- package/dist/features/seller/components/SellerPayoutMethodsView.d.ts +7 -0
- package/dist/features/seller/components/SellerPayoutMethodsView.js +120 -0
- package/dist/features/seller/components/SellerPayoutSettingsView.js +50 -12
- package/dist/features/seller/components/SellerProductShell.d.ts +3 -1
- package/dist/features/seller/components/SellerProductShell.js +35 -11
- package/dist/features/seller/components/SellerProductsCards.d.ts +19 -0
- package/dist/features/seller/components/SellerProductsCards.js +19 -0
- package/dist/features/seller/components/SellerProductsFilterDrawer.d.ts +14 -0
- package/dist/features/seller/components/SellerProductsFilterDrawer.js +22 -0
- package/dist/features/seller/components/SellerProductsView.d.ts +3 -1
- package/dist/features/seller/components/SellerProductsView.js +92 -49
- package/dist/features/seller/components/SellerReviewsView.js +67 -4
- package/dist/features/seller/components/SellerShippingConfigsView.d.ts +7 -0
- package/dist/features/seller/components/SellerShippingConfigsView.js +149 -0
- package/dist/features/seller/components/SellerShippingView.js +37 -10
- package/dist/features/seller/components/SellerSidebar.js +2 -1
- package/dist/features/seller/components/SellerStoreCategoriesView.d.ts +9 -0
- package/dist/features/seller/components/SellerStoreCategoriesView.js +122 -0
- package/dist/features/seller/components/SellerStorefrontView.d.ts +0 -7
- package/dist/features/seller/components/SellerStorefrontView.js +26 -29
- package/dist/features/seller/components/SellerTemplatesView.d.ts +10 -0
- package/dist/features/seller/components/SellerTemplatesView.js +265 -0
- package/dist/features/seller/components/index.d.ts +26 -0
- package/dist/features/seller/components/index.js +13 -0
- package/dist/features/seller/components/seller-products-styles.d.ts +7 -0
- package/dist/features/seller/components/seller-products-styles.js +14 -0
- package/dist/features/shell/FormShell.d.ts +7 -1
- package/dist/features/shell/FormShell.js +5 -2
- package/dist/features/store-extensions/index.d.ts +4 -0
- package/dist/features/store-extensions/index.js +4 -0
- package/dist/features/store-extensions/repository/rbac.repositories.d.ts +23 -0
- package/dist/features/store-extensions/repository/rbac.repositories.js +32 -0
- package/dist/features/store-extensions/repository/store-extensions.repositories.d.ts +91 -0
- package/dist/features/store-extensions/repository/store-extensions.repositories.js +127 -0
- package/dist/features/store-extensions/schemas/firestore.d.ts +244 -0
- package/dist/features/store-extensions/schemas/firestore.js +158 -0
- package/dist/features/store-extensions/schemas/rbac.d.ts +65 -0
- package/dist/features/store-extensions/schemas/rbac.js +43 -0
- package/dist/features/stores/api/[storeSlug]/reviews/route.js +49 -7
- package/dist/features/stores/components/InteractiveStoreCard.js +7 -3
- package/dist/features/stores/components/StoreCapabilitiesGuideView.d.ts +3 -0
- package/dist/features/stores/components/StoreCapabilitiesGuideView.js +101 -0
- package/dist/features/stores/components/StoreFinanceGuideView.d.ts +3 -0
- package/dist/features/stores/components/StoreFinanceGuideView.js +79 -0
- package/dist/features/stores/components/StoreGuideHubView.d.ts +5 -0
- package/dist/features/stores/components/StoreGuideHubView.js +89 -0
- package/dist/features/stores/components/StoreListingsGuideView.d.ts +3 -0
- package/dist/features/stores/components/StoreListingsGuideView.js +151 -0
- package/dist/features/stores/components/StoreOrdersGuideView.d.ts +3 -0
- package/dist/features/stores/components/StoreOrdersGuideView.js +122 -0
- package/dist/features/stores/components/StoreProductsListing.js +6 -10
- package/dist/features/stores/components/StoreReviewsListing.js +100 -23
- package/dist/features/stores/components/StoreSettingsGuideView.d.ts +3 -0
- package/dist/features/stores/components/StoreSettingsGuideView.js +56 -0
- package/dist/features/stores/components/index.d.ts +12 -1
- package/dist/features/stores/components/index.js +6 -1
- package/dist/features/stores/hooks/useStores.d.ts +5 -0
- package/dist/features/stores/hooks/useStores.js +10 -0
- package/dist/features/stores/manifest.js +1 -1
- package/dist/features/stores/schemas/firestore.d.ts +2 -0
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +13 -2
- package/dist/index.d.ts +60 -9
- package/dist/index.js +66 -15
- package/dist/next/routing/route-map.d.ts +134 -0
- package/dist/next/routing/route-map.js +59 -0
- package/dist/providers/db-firebase/__tests__/filter-aliases.test.d.ts +1 -0
- package/dist/providers/db-firebase/__tests__/filter-aliases.test.js +93 -0
- package/dist/providers/db-firebase/sieve.d.ts +49 -0
- package/dist/providers/db-firebase/sieve.js +61 -8
- package/dist/react/hooks/useFormStatePreservation.d.ts +17 -0
- package/dist/react/hooks/useFormStatePreservation.js +62 -0
- package/dist/react/hooks/useInlineRowEdit.d.ts +24 -0
- package/dist/react/hooks/useInlineRowEdit.js +68 -0
- package/dist/react/index.d.ts +4 -0
- package/dist/react/index.js +4 -0
- package/dist/repositories/index.d.ts +5 -0
- package/dist/repositories/index.js +7 -0
- package/dist/seed/actions/demo-seed-actions.d.ts +1 -1
- package/dist/seed/claimed-coupons-seed-data.d.ts +13 -0
- package/dist/seed/claimed-coupons-seed-data.js +79 -0
- package/dist/seed/index.d.ts +2 -0
- package/dist/seed/index.js +3 -0
- package/dist/seed/manifest.js +17 -0
- package/dist/seed/store-extensions-seed-data.d.ts +19 -0
- package/dist/seed/store-extensions-seed-data.js +421 -0
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/Button.style.css +20 -12
- package/dist/ui/components/ClaimCouponButton.d.ts +28 -0
- package/dist/ui/components/ClaimCouponButton.js +89 -0
- package/dist/ui/components/ListingToolbar.d.ts +8 -1
- package/dist/ui/components/ListingToolbar.js +4 -2
- package/dist/ui/components/QuickCreateModal.d.ts +15 -0
- package/dist/ui/components/QuickCreateModal.js +48 -0
- package/dist/ui/components/QuickCreateModal.style.css +84 -0
- package/dist/ui/components/StickyBottomBar.d.ts +22 -0
- package/dist/ui/components/StickyBottomBar.js +20 -0
- package/dist/ui/components/VacationBanner.d.ts +11 -0
- package/dist/ui/components/VacationBanner.js +16 -0
- package/dist/ui/components/__tests__/BulkActionBar.test.d.ts +1 -0
- package/dist/ui/components/__tests__/BulkActionBar.test.js +96 -0
- package/dist/ui/components/__tests__/ListingToolbar.test.d.ts +1 -0
- package/dist/ui/components/__tests__/ListingToolbar.test.js +125 -0
- package/dist/ui/index.d.ts +9 -1
- package/dist/ui/index.js +4 -0
- package/package.json +8 -2
|
@@ -81,41 +81,49 @@
|
|
|
81
81
|
gap: 0.625rem;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
/* Filled variants — colour rules use a chained-class selector (specificity
|
|
85
|
+
(0,2,0)) so they beat single-class Tailwind text utilities (0,1,0). This
|
|
86
|
+
guarantees contrast-safe foregrounds even when call-sites add a stray
|
|
87
|
+
`text-*` className. Tailwind utilities still apply for non-color props
|
|
88
|
+
like `text-xs` (font-size). See plan §4 "Defensive layer" + the global
|
|
89
|
+
sweep request 2026-05-18. */
|
|
90
|
+
|
|
91
|
+
.appkit-button.appkit-button--primary {
|
|
85
92
|
background: var(--appkit-color-primary-700);
|
|
86
93
|
color: var(--appkit-color-text-on-primary);
|
|
87
94
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
|
|
88
95
|
}
|
|
89
96
|
|
|
90
|
-
.appkit-button--primary:hover:not(:disabled) {
|
|
97
|
+
.appkit-button.appkit-button--primary:hover:not(:disabled) {
|
|
91
98
|
background: var(--appkit-color-primary-800);
|
|
92
99
|
}
|
|
93
100
|
|
|
94
|
-
.dark .appkit-button--primary {
|
|
101
|
+
.dark .appkit-button.appkit-button--primary {
|
|
95
102
|
background: var(--appkit-color-secondary);
|
|
103
|
+
color: var(--appkit-color-text-on-primary);
|
|
96
104
|
}
|
|
97
105
|
|
|
98
|
-
.dark .appkit-button--primary:hover:not(:disabled) {
|
|
106
|
+
.dark .appkit-button.appkit-button--primary:hover:not(:disabled) {
|
|
99
107
|
background: var(--appkit-color-secondary-400);
|
|
100
108
|
}
|
|
101
109
|
|
|
102
|
-
.appkit-button--secondary {
|
|
110
|
+
.appkit-button.appkit-button--secondary {
|
|
103
111
|
background: var(--appkit-color-surface);
|
|
104
112
|
border-color: var(--appkit-color-primary-700);
|
|
105
113
|
color: var(--appkit-color-primary-700);
|
|
106
114
|
}
|
|
107
115
|
|
|
108
|
-
.appkit-button--secondary:hover:not(:disabled) {
|
|
116
|
+
.appkit-button.appkit-button--secondary:hover:not(:disabled) {
|
|
109
117
|
background: color-mix(in srgb, var(--appkit-color-primary-700) 8%, transparent);
|
|
110
118
|
}
|
|
111
119
|
|
|
112
|
-
.dark .appkit-button--secondary {
|
|
120
|
+
.dark .appkit-button.appkit-button--secondary {
|
|
113
121
|
background: transparent;
|
|
114
122
|
border-color: var(--appkit-color-secondary);
|
|
115
123
|
color: var(--appkit-color-secondary);
|
|
116
124
|
}
|
|
117
125
|
|
|
118
|
-
.dark .appkit-button--secondary:hover:not(:disabled) {
|
|
126
|
+
.dark .appkit-button.appkit-button--secondary:hover:not(:disabled) {
|
|
119
127
|
background: color-mix(in srgb, var(--appkit-color-secondary) 12%, transparent);
|
|
120
128
|
}
|
|
121
129
|
|
|
@@ -156,21 +164,21 @@
|
|
|
156
164
|
background: rgba(30, 41, 59, 0.5);
|
|
157
165
|
}
|
|
158
166
|
|
|
159
|
-
.appkit-button--danger {
|
|
167
|
+
.appkit-button.appkit-button--danger {
|
|
160
168
|
background: var(--appkit-color-error);
|
|
161
169
|
color: var(--appkit-color-text-on-primary);
|
|
162
170
|
}
|
|
163
171
|
|
|
164
|
-
.appkit-button--danger:hover:not(:disabled) {
|
|
172
|
+
.appkit-button.appkit-button--danger:hover:not(:disabled) {
|
|
165
173
|
background: var(--appkit-color-error-hover);
|
|
166
174
|
}
|
|
167
175
|
|
|
168
|
-
.appkit-button--warning {
|
|
176
|
+
.appkit-button.appkit-button--warning {
|
|
169
177
|
background: var(--appkit-color-warning);
|
|
170
178
|
color: var(--appkit-color-text-on-primary);
|
|
171
179
|
}
|
|
172
180
|
|
|
173
|
-
.appkit-button--warning:hover:not(:disabled) {
|
|
181
|
+
.appkit-button.appkit-button--warning:hover:not(:disabled) {
|
|
174
182
|
background: var(--appkit-color-amber-400);
|
|
175
183
|
}
|
|
176
184
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClaimCouponButton — surfaced anywhere a user wins or earns a coupon
|
|
3
|
+
* (SpinWheel "YOU WON" panel, raffle winner page, prize-draw reveal modal,
|
|
4
|
+
* promotional inserts).
|
|
5
|
+
*
|
|
6
|
+
* Behaviour:
|
|
7
|
+
* 1. If the visitor is not signed in, opens the standard LoginRequiredModal —
|
|
8
|
+
* no coupon is copied or saved. The wallet is per-user, so anonymous
|
|
9
|
+
* claims would be lost.
|
|
10
|
+
* 2. POST `/api/user/coupons/claim` to persist the claim into the user's
|
|
11
|
+
* wallet (idempotent — second claim is a no-op).
|
|
12
|
+
* 3. Copy the code to the clipboard so the user can paste it at checkout.
|
|
13
|
+
*
|
|
14
|
+
* No more cart deep-link — the wallet ("My Coupons" page) is the single home
|
|
15
|
+
* for claimed coupons, and the user applies them from there at checkout.
|
|
16
|
+
*/
|
|
17
|
+
import React from "react";
|
|
18
|
+
export interface ClaimCouponButtonProps {
|
|
19
|
+
couponCode: string;
|
|
20
|
+
/** Optional server-side claim handler — overrides the default wallet POST. */
|
|
21
|
+
onClaim?: (code: string) => Promise<void> | void;
|
|
22
|
+
/** Override the button label. */
|
|
23
|
+
label?: string;
|
|
24
|
+
className?: string;
|
|
25
|
+
size?: "sm" | "md" | "lg";
|
|
26
|
+
source?: "manual" | "promo" | "spin" | "raffle" | "prize-draw";
|
|
27
|
+
}
|
|
28
|
+
export declare function ClaimCouponButton({ couponCode, onClaim, label, className, size, source, }: ClaimCouponButtonProps): React.JSX.Element;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* ClaimCouponButton — surfaced anywhere a user wins or earns a coupon
|
|
5
|
+
* (SpinWheel "YOU WON" panel, raffle winner page, prize-draw reveal modal,
|
|
6
|
+
* promotional inserts).
|
|
7
|
+
*
|
|
8
|
+
* Behaviour:
|
|
9
|
+
* 1. If the visitor is not signed in, opens the standard LoginRequiredModal —
|
|
10
|
+
* no coupon is copied or saved. The wallet is per-user, so anonymous
|
|
11
|
+
* claims would be lost.
|
|
12
|
+
* 2. POST `/api/user/coupons/claim` to persist the claim into the user's
|
|
13
|
+
* wallet (idempotent — second claim is a no-op).
|
|
14
|
+
* 3. Copy the code to the clipboard so the user can paste it at checkout.
|
|
15
|
+
*
|
|
16
|
+
* No more cart deep-link — the wallet ("My Coupons" page) is the single home
|
|
17
|
+
* for claimed coupons, and the user applies them from there at checkout.
|
|
18
|
+
*/
|
|
19
|
+
import { useCallback, useState } from "react";
|
|
20
|
+
import { Button } from "./Button";
|
|
21
|
+
import { LoginRequiredModal } from "./LoginRequiredModal";
|
|
22
|
+
import { useSession } from "../../react/contexts/SessionContext";
|
|
23
|
+
import { useToast } from "./Toast";
|
|
24
|
+
async function postClaim(couponCode, source) {
|
|
25
|
+
if (typeof fetch === "undefined")
|
|
26
|
+
return false;
|
|
27
|
+
try {
|
|
28
|
+
const res = await fetch("/api/user/coupons/claim", {
|
|
29
|
+
method: "POST",
|
|
30
|
+
headers: { "Content-Type": "application/json" },
|
|
31
|
+
credentials: "include",
|
|
32
|
+
body: JSON.stringify({ couponCode, source }),
|
|
33
|
+
});
|
|
34
|
+
return res.ok;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async function copyToClipboard(text) {
|
|
41
|
+
if (typeof navigator === "undefined" || !navigator.clipboard?.writeText) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
await navigator.clipboard.writeText(text);
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function ClaimCouponButton({ couponCode, onClaim, label, className, size = "md", source = "manual", }) {
|
|
53
|
+
const { user, loading } = useSession();
|
|
54
|
+
const { showToast } = useToast();
|
|
55
|
+
const [busy, setBusy] = useState(false);
|
|
56
|
+
const [claimed, setClaimed] = useState(false);
|
|
57
|
+
const [loginOpen, setLoginOpen] = useState(false);
|
|
58
|
+
const handleClick = useCallback(async () => {
|
|
59
|
+
if (busy || !couponCode)
|
|
60
|
+
return;
|
|
61
|
+
if (loading)
|
|
62
|
+
return;
|
|
63
|
+
if (!user) {
|
|
64
|
+
setLoginOpen(true);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
setBusy(true);
|
|
68
|
+
try {
|
|
69
|
+
const ok = onClaim != null
|
|
70
|
+
? (await Promise.resolve(onClaim(couponCode)), true)
|
|
71
|
+
: await postClaim(couponCode, source);
|
|
72
|
+
if (!ok) {
|
|
73
|
+
showToast("Could not save coupon. Please try again.", "error");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
await copyToClipboard(couponCode);
|
|
77
|
+
setClaimed(true);
|
|
78
|
+
showToast(`${couponCode} saved to your coupons.`, "success");
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
setBusy(false);
|
|
82
|
+
}
|
|
83
|
+
}, [busy, couponCode, loading, onClaim, showToast, source, user]);
|
|
84
|
+
return (_jsxs(_Fragment, { children: [_jsx(Button, { type: "button", variant: "primary", size: size, onClick: handleClick, disabled: busy || !couponCode || loading, className: className, children: busy
|
|
85
|
+
? "Saving…"
|
|
86
|
+
: claimed
|
|
87
|
+
? "Saved to My Coupons"
|
|
88
|
+
: (label ?? "Claim coupon") }), _jsx(LoginRequiredModal, { isOpen: loginOpen, onClose: () => setLoginOpen(false), message: "Sign in to save coupons to your wallet." })] }));
|
|
89
|
+
}
|
|
@@ -3,6 +3,11 @@ export interface ListingToolbarSortOption {
|
|
|
3
3
|
value: string;
|
|
4
4
|
label: string;
|
|
5
5
|
}
|
|
6
|
+
export interface ListingToolbarToggle {
|
|
7
|
+
label: string;
|
|
8
|
+
active: boolean;
|
|
9
|
+
onChange: (next: boolean) => void;
|
|
10
|
+
}
|
|
6
11
|
export interface ListingToolbarLabels {
|
|
7
12
|
search?: string;
|
|
8
13
|
filters?: string;
|
|
@@ -48,10 +53,12 @@ export interface ListingToolbarProps {
|
|
|
48
53
|
bulkTotalCount?: number;
|
|
49
54
|
onBulkSelectAll?: () => void;
|
|
50
55
|
onBulkClear?: () => void;
|
|
56
|
+
/** Pill-style filter toggles rendered between view controls and extra */
|
|
57
|
+
toggles?: ListingToolbarToggle[];
|
|
51
58
|
/** Any extra action buttons placed after the view toggle */
|
|
52
59
|
extra?: React.ReactNode;
|
|
53
60
|
/** Overridable text labels for i18n */
|
|
54
61
|
labels?: ListingToolbarLabels;
|
|
55
62
|
className?: string;
|
|
56
63
|
}
|
|
57
|
-
export declare function ListingToolbar({ filterCount, onFiltersClick, searchValue, searchPlaceholder, onSearchChange, onSearchCommit, onSearchKeyDown, sortValue, sortOptions, onSortChange, view, onViewChange, hideViewToggle, showTableView, onResetAll, hasActiveState, bulkMode, bulkSelectedCount, bulkTotalCount, onBulkSelectAll, onBulkClear, extra, labels, className, }: ListingToolbarProps): import("react/jsx-runtime").JSX.Element;
|
|
64
|
+
export declare function ListingToolbar({ filterCount, onFiltersClick, searchValue, searchPlaceholder, onSearchChange, onSearchCommit, onSearchKeyDown, sortValue, sortOptions, onSortChange, view, onViewChange, hideViewToggle, showTableView, onResetAll, hasActiveState, bulkMode, bulkSelectedCount, bulkTotalCount, onBulkSelectAll, onBulkClear, toggles, extra, labels, className, }: ListingToolbarProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -18,7 +18,7 @@ const DEFAULT_LABELS = {
|
|
|
18
18
|
const VIEW_BTN_BASE = "p-1.5 sm:p-2 transition-colors";
|
|
19
19
|
const VIEW_BTN_ACTIVE = "bg-[var(--appkit-color-primary,theme(colors.violet.600))] text-white";
|
|
20
20
|
const VIEW_BTN_INACTIVE = "text-zinc-500 hover:bg-zinc-50 dark:hover:bg-slate-800 dark:text-zinc-400";
|
|
21
|
-
export function ListingToolbar({ filterCount = 0, onFiltersClick, searchValue = "", searchPlaceholder = "Search…", onSearchChange, onSearchCommit, onSearchKeyDown, sortValue, sortOptions, onSortChange, view = "grid", onViewChange, hideViewToggle = false, showTableView = false, onResetAll, hasActiveState = false, bulkMode = false, bulkSelectedCount = 0, bulkTotalCount = 0, onBulkSelectAll, onBulkClear, extra, labels, className = "", }) {
|
|
21
|
+
export function ListingToolbar({ filterCount = 0, onFiltersClick, searchValue = "", searchPlaceholder = "Search…", onSearchChange, onSearchCommit, onSearchKeyDown, sortValue, sortOptions, onSortChange, view = "grid", onViewChange, hideViewToggle = false, showTableView = false, onResetAll, hasActiveState = false, bulkMode = false, bulkSelectedCount = 0, bulkTotalCount = 0, onBulkSelectAll, onBulkClear, toggles, extra, labels, className = "", }) {
|
|
22
22
|
const l = { ...DEFAULT_LABELS, ...labels };
|
|
23
23
|
const handleKeyDown = (e) => {
|
|
24
24
|
if (onSearchKeyDown) {
|
|
@@ -31,5 +31,7 @@ export function ListingToolbar({ filterCount = 0, onFiltersClick, searchValue =
|
|
|
31
31
|
const allSelected = bulkTotalCount > 0 && bulkSelectedCount === bulkTotalCount;
|
|
32
32
|
return (_jsx("div", { "data-testid": "listing-toolbar", className: `sticky top-[var(--header-height,0px)] z-20 border-b border-zinc-200 dark:border-slate-700 bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm py-2 px-3 sm:py-2.5 sm:px-4 ${className}`, children: _jsxs("div", { className: "flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-2.5", children: [bulkMode ? (_jsxs("div", { className: "flex flex-1 items-center gap-2", children: [_jsxs("button", { type: "button", onClick: onBulkSelectAll, className: "flex items-center gap-1.5 rounded-lg border border-zinc-300 dark:border-slate-600 px-3 py-1.5 text-sm font-medium text-zinc-700 dark:text-zinc-200 hover:bg-zinc-50 dark:hover:bg-slate-800 transition-colors", children: [allSelected
|
|
33
33
|
? _jsx(CheckSquare, { className: "h-4 w-4 text-[var(--appkit-color-primary,theme(colors.violet.600))]" })
|
|
34
|
-
: _jsx(Square, { className: "h-4 w-4" }), allSelected ? l.deselectAll : l.selectAll(bulkTotalCount)] }), _jsx("span", { className: "text-sm text-zinc-500 dark:text-zinc-400", children: l.selected(bulkSelectedCount) }), _jsx("button", { type: "button", onClick: onBulkClear, className: "text-xs text-zinc-400 hover:text-rose-500 dark:text-zinc-500 transition-colors", children: l.clearSelection })] })) : onSearchChange ? (_jsxs("div", { className: "flex flex-1 items-center overflow-hidden rounded-lg border border-zinc-300 dark:border-slate-600 bg-white dark:bg-slate-900 min-w-0", children: [_jsx("input", { type: "text", value: searchValue, onChange: (e) => onSearchChange(e.target.value), onKeyDown: handleKeyDown, placeholder: searchPlaceholder, className: "min-w-0 flex-1 bg-transparent px-3 py-2 text-sm text-zinc-900 dark:text-zinc-100 placeholder-zinc-400 outline-none" }), _jsx("button", { type: "button", onClick: onSearchCommit, className: "flex shrink-0 items-center justify-center px-2.5 py-2 text-zinc-400 hover:text-[var(--appkit-color-primary,theme(colors.violet.600))] transition-colors", "aria-label": l.search, children: _jsx(Search, { className: "h-4 w-4" }) })] })) : null, _jsxs("div", { className: "flex items-center gap-1.5 sm:gap-2 shrink-0", children: [onFiltersClick && (_jsxs("button", { type: "button", onClick: onFiltersClick, className: "relative flex shrink-0 items-center gap-1.5 rounded-lg border border-zinc-300 dark:border-slate-600 px-2.5 py-1.5 sm:px-3.5 sm:py-2 text-sm font-medium text-zinc-700 dark:text-zinc-200 hover:bg-zinc-50 dark:hover:bg-slate-800 transition-colors", children: [_jsx(SlidersHorizontal, { className: "h-4 w-4" }), _jsx("span", { className: "hidden sm:inline", children: l.filters }), filterCount > 0 && (_jsx("span", { className: "absolute -top-1.5 -right-1.5 flex h-4 w-4 items-center justify-center rounded-full bg-[var(--appkit-color-primary,theme(colors.violet.600))] text-[10px] font-bold text-white", children: filterCount }))] })), sortOptions && sortValue !== undefined && onSortChange && (_jsxs("div", { className: "flex items-center gap-1.5 text-sm text-zinc-500 dark:text-zinc-400", children: [_jsx("span", { className: "hidden md:inline whitespace-nowrap text-xs", children: l.sort }), _jsx(SortDropdown, { value: sortValue, onChange: onSortChange, options: sortOptions })] })), !hideViewToggle && onViewChange && (_jsxs("div", { className: "flex items-center rounded-lg border border-zinc-300 dark:border-slate-600 overflow-hidden", children: [_jsx("button", { type: "button", onClick: () => onViewChange("grid"), "aria-label": l.gridView, className: `${VIEW_BTN_BASE} ${view === "grid" ? VIEW_BTN_ACTIVE : VIEW_BTN_INACTIVE}`, children: _jsx(LayoutGrid, { className: "h-4 w-4" }) }), _jsx("button", { type: "button", onClick: () => onViewChange("list"), "aria-label": l.listView, className: `${VIEW_BTN_BASE} ${view === "list" ? VIEW_BTN_ACTIVE : VIEW_BTN_INACTIVE}`, children: _jsx(List, { className: "h-4 w-4" }) }), showTableView && (_jsx("button", { type: "button", onClick: () => onViewChange("table"), "aria-label": l.tableView, className: `${VIEW_BTN_BASE} ${view === "table" ? VIEW_BTN_ACTIVE : VIEW_BTN_INACTIVE}`, children: _jsx(Table2, { className: "h-4 w-4" }) }))] })), onResetAll && hasActiveState && (_jsx("button", { type: "button", onClick: onResetAll, "aria-label": l.resetAll, title: l.resetAll, className: "flex shrink-0 items-center justify-center rounded-lg border border-zinc-300 dark:border-slate-600 p-1.5 sm:p-2 text-zinc-500 dark:text-zinc-400 hover:bg-zinc-50 dark:hover:bg-slate-800 hover:text-rose-500 dark:hover:text-rose-400 transition-colors", children: _jsx(RotateCcw, { className: "h-4 w-4" }) })),
|
|
34
|
+
: _jsx(Square, { className: "h-4 w-4" }), allSelected ? l.deselectAll : l.selectAll(bulkTotalCount)] }), _jsx("span", { className: "text-sm text-zinc-500 dark:text-zinc-400", children: l.selected(bulkSelectedCount) }), _jsx("button", { type: "button", onClick: onBulkClear, className: "text-xs text-zinc-400 hover:text-rose-500 dark:text-zinc-500 transition-colors", children: l.clearSelection })] })) : onSearchChange ? (_jsxs("div", { className: "flex flex-1 items-center overflow-hidden rounded-lg border border-zinc-300 dark:border-slate-600 bg-white dark:bg-slate-900 min-w-0", children: [_jsx("input", { type: "text", value: searchValue, onChange: (e) => onSearchChange(e.target.value), onKeyDown: handleKeyDown, placeholder: searchPlaceholder, className: "min-w-0 flex-1 bg-transparent px-3 py-2 text-sm text-zinc-900 dark:text-zinc-100 placeholder-zinc-400 outline-none" }), _jsx("button", { type: "button", onClick: onSearchCommit, className: "flex shrink-0 items-center justify-center px-2.5 py-2 text-zinc-400 hover:text-[var(--appkit-color-primary,theme(colors.violet.600))] transition-colors", "aria-label": l.search, children: _jsx(Search, { className: "h-4 w-4" }) })] })) : null, _jsxs("div", { className: "flex items-center gap-1.5 sm:gap-2 shrink-0", children: [onFiltersClick && (_jsxs("button", { type: "button", onClick: onFiltersClick, className: "relative flex shrink-0 items-center gap-1.5 rounded-lg border border-zinc-300 dark:border-slate-600 px-2.5 py-1.5 sm:px-3.5 sm:py-2 text-sm font-medium text-zinc-700 dark:text-zinc-200 hover:bg-zinc-50 dark:hover:bg-slate-800 transition-colors", children: [_jsx(SlidersHorizontal, { className: "h-4 w-4" }), _jsx("span", { className: "hidden sm:inline", children: l.filters }), filterCount > 0 && (_jsx("span", { className: "absolute -top-1.5 -right-1.5 flex h-4 w-4 items-center justify-center rounded-full bg-[var(--appkit-color-primary,theme(colors.violet.600))] text-[10px] font-bold text-white", children: filterCount }))] })), sortOptions && sortValue !== undefined && onSortChange && (_jsxs("div", { className: "flex items-center gap-1.5 text-sm text-zinc-500 dark:text-zinc-400", children: [_jsx("span", { className: "hidden md:inline whitespace-nowrap text-xs", children: l.sort }), _jsx(SortDropdown, { value: sortValue, onChange: onSortChange, options: sortOptions })] })), !hideViewToggle && onViewChange && (_jsxs("div", { className: "flex items-center rounded-lg border border-zinc-300 dark:border-slate-600 overflow-hidden", children: [_jsx("button", { type: "button", onClick: () => onViewChange("grid"), "aria-label": l.gridView, className: `${VIEW_BTN_BASE} ${view === "grid" ? VIEW_BTN_ACTIVE : VIEW_BTN_INACTIVE}`, children: _jsx(LayoutGrid, { className: "h-4 w-4" }) }), _jsx("button", { type: "button", onClick: () => onViewChange("list"), "aria-label": l.listView, className: `${VIEW_BTN_BASE} ${view === "list" ? VIEW_BTN_ACTIVE : VIEW_BTN_INACTIVE}`, children: _jsx(List, { className: "h-4 w-4" }) }), showTableView && (_jsx("button", { type: "button", onClick: () => onViewChange("table"), "aria-label": l.tableView, className: `${VIEW_BTN_BASE} ${view === "table" ? VIEW_BTN_ACTIVE : VIEW_BTN_INACTIVE}`, children: _jsx(Table2, { className: "h-4 w-4" }) }))] })), onResetAll && hasActiveState && (_jsx("button", { type: "button", onClick: onResetAll, "aria-label": l.resetAll, title: l.resetAll, className: "flex shrink-0 items-center justify-center rounded-lg border border-zinc-300 dark:border-slate-600 p-1.5 sm:p-2 text-zinc-500 dark:text-zinc-400 hover:bg-zinc-50 dark:hover:bg-slate-800 hover:text-rose-500 dark:hover:text-rose-400 transition-colors", children: _jsx(RotateCcw, { className: "h-4 w-4" }) })), toggles && toggles.length > 0 && (_jsx("div", { className: "flex items-center gap-1", children: toggles.map((t) => (_jsx("button", { type: "button", role: "switch", "aria-checked": t.active, onClick: () => t.onChange(!t.active), className: `rounded-full border px-2.5 py-1 text-xs font-medium transition-colors whitespace-nowrap ${t.active
|
|
35
|
+
? "border-transparent bg-[var(--appkit-color-primary,theme(colors.violet.600))] text-white"
|
|
36
|
+
: "border-zinc-300 dark:border-slate-600 text-zinc-600 dark:text-zinc-300 hover:bg-zinc-50 dark:hover:bg-slate-800"}`, children: t.label }, t.label))) })), extra] })] }) }));
|
|
35
37
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export interface QuickCreateModalProps<T = unknown> {
|
|
3
|
+
isOpen: boolean;
|
|
4
|
+
title: string;
|
|
5
|
+
onSave: () => Promise<T> | T;
|
|
6
|
+
onSaved?: (doc: T) => void;
|
|
7
|
+
onCancel: () => void;
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
fullPageHref?: string;
|
|
10
|
+
saveLabel?: string;
|
|
11
|
+
cancelLabel?: string;
|
|
12
|
+
isSaving?: boolean;
|
|
13
|
+
saveDisabled?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function QuickCreateModal<T = unknown>({ isOpen, title, onSave, onSaved, onCancel, children, fullPageHref, saveLabel, cancelLabel, isSaving, saveDisabled, }: QuickCreateModalProps<T>): React.ReactPortal | null;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { createPortal } from "react-dom";
|
|
5
|
+
import { Button } from "./Button";
|
|
6
|
+
import { Div } from "./Div";
|
|
7
|
+
import { Heading } from "./Typography";
|
|
8
|
+
import { Row } from "./Layout";
|
|
9
|
+
export function QuickCreateModal({ isOpen, title, onSave, onSaved, onCancel, children, fullPageHref, saveLabel = "Save", cancelLabel = "Cancel", isSaving = false, saveDisabled = false, }) {
|
|
10
|
+
const [busy, setBusy] = React.useState(false);
|
|
11
|
+
const titleId = React.useId();
|
|
12
|
+
const panelRef = React.useRef(null);
|
|
13
|
+
React.useEffect(() => {
|
|
14
|
+
if (!isOpen)
|
|
15
|
+
return;
|
|
16
|
+
const prev = document.body.style.overflow;
|
|
17
|
+
document.body.style.overflow = "hidden";
|
|
18
|
+
return () => {
|
|
19
|
+
document.body.style.overflow = prev;
|
|
20
|
+
};
|
|
21
|
+
}, [isOpen]);
|
|
22
|
+
React.useEffect(() => {
|
|
23
|
+
if (!isOpen)
|
|
24
|
+
return;
|
|
25
|
+
const handler = (e) => {
|
|
26
|
+
if (e.key === "Escape" && !busy)
|
|
27
|
+
onCancel();
|
|
28
|
+
};
|
|
29
|
+
document.addEventListener("keydown", handler);
|
|
30
|
+
return () => document.removeEventListener("keydown", handler);
|
|
31
|
+
}, [isOpen, onCancel, busy]);
|
|
32
|
+
const handleSave = async () => {
|
|
33
|
+
if (saveDisabled || busy)
|
|
34
|
+
return;
|
|
35
|
+
setBusy(true);
|
|
36
|
+
try {
|
|
37
|
+
const doc = await onSave();
|
|
38
|
+
onSaved?.(doc);
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
setBusy(false);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
if (!isOpen || typeof document === "undefined")
|
|
45
|
+
return null;
|
|
46
|
+
const saving = busy || isSaving;
|
|
47
|
+
return createPortal(_jsxs(Div, { className: "appkit-quick-create-modal", role: "dialog", "aria-modal": "true", "aria-labelledby": titleId, children: [_jsx(Div, { className: "appkit-quick-create-modal__backdrop", "aria-hidden": "true", onClick: () => !saving && onCancel() }), _jsxs(Div, { ref: panelRef, tabIndex: -1, className: "appkit-quick-create-modal__panel", children: [_jsxs(Div, { className: "appkit-quick-create-modal__header", children: [_jsx(Heading, { level: 2, id: titleId, className: "appkit-quick-create-modal__title", children: title }), _jsx(Button, { variant: "ghost", size: "sm", type: "button", onClick: onCancel, "aria-label": "Close", disabled: saving, children: "\u00D7" })] }), _jsx(Div, { className: "appkit-quick-create-modal__body", children: children }), _jsxs(Div, { className: "appkit-quick-create-modal__footer", children: [fullPageHref ? (_jsx("a", { href: fullPageHref, target: "_blank", rel: "noreferrer", className: "appkit-quick-create-modal__full-link", children: "Add more details \u2192" })) : (_jsx("span", {})), _jsxs(Row, { className: "appkit-quick-create-modal__actions", children: [_jsx(Button, { variant: "ghost", type: "button", onClick: onCancel, disabled: saving, children: cancelLabel }), _jsx(Button, { variant: "primary", type: "button", onClick: handleSave, disabled: saving || saveDisabled, isLoading: saving, children: saveLabel })] })] })] })] }), document.body);
|
|
48
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
.appkit-quick-create-modal {
|
|
2
|
+
position: fixed;
|
|
3
|
+
inset: 0;
|
|
4
|
+
z-index: var(--appkit-z-modal, 1000);
|
|
5
|
+
display: flex;
|
|
6
|
+
align-items: stretch;
|
|
7
|
+
justify-content: center;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.appkit-quick-create-modal__backdrop {
|
|
11
|
+
position: absolute;
|
|
12
|
+
inset: 0;
|
|
13
|
+
background: rgb(0 0 0 / 0.45);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.appkit-quick-create-modal__panel {
|
|
17
|
+
position: relative;
|
|
18
|
+
display: flex;
|
|
19
|
+
flex-direction: column;
|
|
20
|
+
background: var(--appkit-color-surface, #ffffff);
|
|
21
|
+
color: var(--appkit-color-foreground, inherit);
|
|
22
|
+
width: 100%;
|
|
23
|
+
max-width: 560px;
|
|
24
|
+
margin: auto;
|
|
25
|
+
max-height: 92vh;
|
|
26
|
+
border-radius: 0.75rem;
|
|
27
|
+
box-shadow: var(--appkit-shadow-lg, 0 20px 40px rgb(0 0 0 / 0.25));
|
|
28
|
+
outline: none;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.appkit-quick-create-modal__header {
|
|
32
|
+
display: flex;
|
|
33
|
+
align-items: center;
|
|
34
|
+
justify-content: space-between;
|
|
35
|
+
padding: 1rem 1.25rem;
|
|
36
|
+
border-bottom: 1px solid var(--appkit-color-border, rgb(0 0 0 / 0.1));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.appkit-quick-create-modal__title {
|
|
40
|
+
margin: 0;
|
|
41
|
+
font-size: 1.125rem;
|
|
42
|
+
font-weight: 600;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.appkit-quick-create-modal__body {
|
|
46
|
+
flex: 1 1 auto;
|
|
47
|
+
overflow-y: auto;
|
|
48
|
+
padding: 1.25rem;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.appkit-quick-create-modal__footer {
|
|
52
|
+
display: flex;
|
|
53
|
+
align-items: center;
|
|
54
|
+
justify-content: space-between;
|
|
55
|
+
gap: 0.75rem;
|
|
56
|
+
padding: 1rem 1.25rem;
|
|
57
|
+
border-top: 1px solid var(--appkit-color-border, rgb(0 0 0 / 0.1));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.appkit-quick-create-modal__full-link {
|
|
61
|
+
font-size: 0.875rem;
|
|
62
|
+
color: var(--appkit-color-primary, #6366f1);
|
|
63
|
+
text-decoration: underline;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.appkit-quick-create-modal__actions {
|
|
67
|
+
gap: 0.5rem;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@media (max-width: 640px) {
|
|
71
|
+
.appkit-quick-create-modal {
|
|
72
|
+
align-items: flex-end;
|
|
73
|
+
}
|
|
74
|
+
.appkit-quick-create-modal__panel {
|
|
75
|
+
max-width: 100%;
|
|
76
|
+
margin: 0;
|
|
77
|
+
border-radius: 0.75rem 0.75rem 0 0;
|
|
78
|
+
max-height: 92vh;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
[data-theme="dark"] .appkit-quick-create-modal__panel {
|
|
83
|
+
background: var(--appkit-color-surface-dark, #1e293b);
|
|
84
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StickyBottomBar — fixed-bottom action bar that sits above the mobile
|
|
3
|
+
* BottomNavLayout (when present) and below any modals.
|
|
4
|
+
*
|
|
5
|
+
* Reads `--bottom-nav-height` written by `BottomNavLayout` (4rem when the
|
|
6
|
+
* navbar is rendered below `lg`, 0px otherwise). Falls back to 0px when the
|
|
7
|
+
* variable is unset so the bar still pins to the viewport bottom on routes
|
|
8
|
+
* that don't render a bottom nav.
|
|
9
|
+
*
|
|
10
|
+
* z-index sits above the navbar (`z-40`) but below modal overlays.
|
|
11
|
+
*/
|
|
12
|
+
import React from "react";
|
|
13
|
+
export interface StickyBottomBarProps {
|
|
14
|
+
children: React.ReactNode;
|
|
15
|
+
/** Optional extra className appended to the bar wrapper. */
|
|
16
|
+
className?: string;
|
|
17
|
+
/** Hide on `lg` and up. Defaults to `true` — matches BottomNavLayout's responsive scope. */
|
|
18
|
+
mobileOnly?: boolean;
|
|
19
|
+
/** Test id passthrough. */
|
|
20
|
+
"data-testid"?: string;
|
|
21
|
+
}
|
|
22
|
+
export declare function StickyBottomBar({ children, className, mobileOnly, "data-testid": testId, }: StickyBottomBarProps): React.JSX.Element;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
const BAR_STYLE = {
|
|
4
|
+
bottom: "var(--bottom-nav-height, 0px)",
|
|
5
|
+
zIndex: 45,
|
|
6
|
+
};
|
|
7
|
+
export function StickyBottomBar({ children, className, mobileOnly = true, "data-testid": testId, }) {
|
|
8
|
+
return (_jsx("div", {
|
|
9
|
+
// eslint-disable-next-line lir/no-inline-static-style
|
|
10
|
+
style: BAR_STYLE, "data-testid": testId, className: [
|
|
11
|
+
"fixed left-0 right-0",
|
|
12
|
+
mobileOnly ? "lg:hidden" : "",
|
|
13
|
+
"bg-white/95 dark:bg-slate-950/95 backdrop-blur-md",
|
|
14
|
+
"border-t border-zinc-200 dark:border-slate-800",
|
|
15
|
+
"shadow-2xl pb-safe",
|
|
16
|
+
className ?? "",
|
|
17
|
+
]
|
|
18
|
+
.filter(Boolean)
|
|
19
|
+
.join(" "), children: children }));
|
|
20
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface VacationBannerProps {
|
|
2
|
+
storeName?: string;
|
|
3
|
+
message?: string;
|
|
4
|
+
returnDate?: Date | string;
|
|
5
|
+
className?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* S-STORE-6-A — Vacation-mode banner shown on product detail + checkout
|
|
9
|
+
* pages when the seller has paused new orders.
|
|
10
|
+
*/
|
|
11
|
+
export declare function VacationBanner({ storeName, message, returnDate, className, }: VacationBannerProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Alert } from "./Alert";
|
|
4
|
+
/**
|
|
5
|
+
* S-STORE-6-A — Vacation-mode banner shown on product detail + checkout
|
|
6
|
+
* pages when the seller has paused new orders.
|
|
7
|
+
*/
|
|
8
|
+
export function VacationBanner({ storeName, message, returnDate, className, }) {
|
|
9
|
+
const formattedDate = returnDate &&
|
|
10
|
+
new Date(returnDate).toLocaleDateString(undefined, {
|
|
11
|
+
year: "numeric",
|
|
12
|
+
month: "short",
|
|
13
|
+
day: "numeric",
|
|
14
|
+
});
|
|
15
|
+
return (_jsx(Alert, { variant: "warning", className: className, children: _jsxs("div", { className: "text-sm", children: [_jsx("strong", { children: storeName ? `${storeName} is on vacation` : "Store is on vacation" }), message ? _jsxs("span", { children: [" \u2014 ", message] }) : null, formattedDate ? _jsxs("span", { children: [" Returns ", formattedDate, "."] }) : null, _jsx("div", { className: "text-xs opacity-80 mt-1", children: "New orders are paused; you can still wishlist items." })] }) }));
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { render, screen, fireEvent } from "@testing-library/react";
|
|
3
|
+
import { describe, it, expect, vi } from "vitest";
|
|
4
|
+
import { BulkActionBar } from "../BulkActionBar";
|
|
5
|
+
const actions = [
|
|
6
|
+
{ id: "publish", label: "Publish", onClick: vi.fn() },
|
|
7
|
+
{ id: "delete", label: "Delete", variant: "danger", onClick: vi.fn() },
|
|
8
|
+
];
|
|
9
|
+
describe("BulkActionBar", () => {
|
|
10
|
+
it("renders nothing when selectedCount is 0", () => {
|
|
11
|
+
render(_jsx(BulkActionBar, { selectedCount: 0, actions: actions }));
|
|
12
|
+
expect(screen.queryByTestId("bulk-action-bar")).toBeNull();
|
|
13
|
+
});
|
|
14
|
+
it("renders the bar when selectedCount > 0", () => {
|
|
15
|
+
render(_jsx(BulkActionBar, { selectedCount: 3, actions: actions }));
|
|
16
|
+
expect(screen.getByTestId("bulk-action-bar")).toBeTruthy();
|
|
17
|
+
});
|
|
18
|
+
it("shows the selected count in the count pill", () => {
|
|
19
|
+
render(_jsx(BulkActionBar, { selectedCount: 5, actions: actions }));
|
|
20
|
+
expect(screen.getByTestId("bulk-selected-count").textContent).toContain("5");
|
|
21
|
+
});
|
|
22
|
+
it("calls onClearSelection when the count pill button is clicked", () => {
|
|
23
|
+
const onClear = vi.fn();
|
|
24
|
+
render(_jsx(BulkActionBar, { selectedCount: 2, actions: actions, onClearSelection: onClear }));
|
|
25
|
+
fireEvent.click(screen.getByRole("button", { name: /clear selection/i }));
|
|
26
|
+
expect(onClear).toHaveBeenCalledOnce();
|
|
27
|
+
});
|
|
28
|
+
it("renders no action buttons when actions array is empty", () => {
|
|
29
|
+
render(_jsx(BulkActionBar, { selectedCount: 1, actions: [] }));
|
|
30
|
+
expect(screen.queryByRole("button", { name: /apply/i })).toBeNull();
|
|
31
|
+
});
|
|
32
|
+
it("renders the picker trigger and apply button when actions are provided", () => {
|
|
33
|
+
render(_jsx(BulkActionBar, { selectedCount: 1, actions: actions }));
|
|
34
|
+
// Apply button must exist
|
|
35
|
+
expect(screen.getByRole("button", { name: /apply/i })).toBeTruthy();
|
|
36
|
+
// Picker trigger has aria-haspopup="listbox"
|
|
37
|
+
expect(screen.getByRole("button", { expanded: false })).toBeTruthy();
|
|
38
|
+
});
|
|
39
|
+
it("opens the action picker on trigger click", () => {
|
|
40
|
+
render(_jsx(BulkActionBar, { selectedCount: 1, actions: actions }));
|
|
41
|
+
const trigger = screen.getByRole("button", { expanded: false });
|
|
42
|
+
fireEvent.click(trigger);
|
|
43
|
+
// Trigger now has aria-expanded="true"
|
|
44
|
+
expect(screen.getByRole("button", { expanded: true })).toBeTruthy();
|
|
45
|
+
});
|
|
46
|
+
it("selects an action when an option is clicked and closes the picker", () => {
|
|
47
|
+
render(_jsx(BulkActionBar, { selectedCount: 1, actions: actions }));
|
|
48
|
+
// Open picker
|
|
49
|
+
const trigger = screen.getByRole("button", { expanded: false });
|
|
50
|
+
fireEvent.click(trigger);
|
|
51
|
+
// Trigger is expanded
|
|
52
|
+
expect(trigger.getAttribute("aria-expanded")).toBe("true");
|
|
53
|
+
// Click Delete option
|
|
54
|
+
fireEvent.click(screen.getByRole("option", { name: /delete/i }));
|
|
55
|
+
// Picker should close — aria-expanded returns to false
|
|
56
|
+
expect(trigger.getAttribute("aria-expanded")).toBe("false");
|
|
57
|
+
});
|
|
58
|
+
it("calls the selected action's onClick when Apply is clicked", () => {
|
|
59
|
+
const publishFn = vi.fn();
|
|
60
|
+
const actionsWithFns = [
|
|
61
|
+
{ id: "publish", label: "Publish", onClick: publishFn },
|
|
62
|
+
];
|
|
63
|
+
render(_jsx(BulkActionBar, { selectedCount: 1, actions: actionsWithFns }));
|
|
64
|
+
fireEvent.click(screen.getByRole("button", { name: /apply/i }));
|
|
65
|
+
expect(publishFn).toHaveBeenCalledOnce();
|
|
66
|
+
});
|
|
67
|
+
it("executes the newly selected action when a different option is chosen then Apply clicked", () => {
|
|
68
|
+
const publishFn = vi.fn();
|
|
69
|
+
const deleteFn = vi.fn();
|
|
70
|
+
const actionsWithFns = [
|
|
71
|
+
{ id: "publish", label: "Publish", onClick: publishFn },
|
|
72
|
+
{ id: "delete", label: "Delete", variant: "danger", onClick: deleteFn },
|
|
73
|
+
];
|
|
74
|
+
render(_jsx(BulkActionBar, { selectedCount: 1, actions: actionsWithFns }));
|
|
75
|
+
// Select Delete via picker
|
|
76
|
+
fireEvent.click(screen.getByRole("button", { expanded: false }));
|
|
77
|
+
fireEvent.click(screen.getByRole("option", { name: /delete/i }));
|
|
78
|
+
// Apply
|
|
79
|
+
fireEvent.click(screen.getByRole("button", { name: /apply/i }));
|
|
80
|
+
expect(deleteFn).toHaveBeenCalledOnce();
|
|
81
|
+
expect(publishFn).not.toHaveBeenCalled();
|
|
82
|
+
});
|
|
83
|
+
it("disables the apply button when the selected action is disabled", () => {
|
|
84
|
+
const disabledActions = [
|
|
85
|
+
{ id: "archive", label: "Archive", onClick: vi.fn(), disabled: true },
|
|
86
|
+
];
|
|
87
|
+
render(_jsx(BulkActionBar, { selectedCount: 1, actions: disabledActions }));
|
|
88
|
+
const applyBtn = screen.getByRole("button", { name: /apply/i });
|
|
89
|
+
expect(applyBtn.disabled).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
it("uses custom labels", () => {
|
|
92
|
+
render(_jsx(BulkActionBar, { selectedCount: 2, actions: actions, labels: { apply: "Run", selected: "chosen" } }));
|
|
93
|
+
expect(screen.getByRole("button", { name: /run/i })).toBeTruthy();
|
|
94
|
+
expect(screen.getByTestId("bulk-selected-count").textContent).toContain("chosen");
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|