@mohasinac/appkit 2.7.16 → 2.7.18
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/actions.js +1 -1
- package/dist/_internal/server/features/blog/actions.js +2 -2
- package/dist/_internal/server/features/brands/actions.js +2 -2
- package/dist/_internal/server/features/bundles/actions.d.ts +15 -0
- package/dist/_internal/server/features/bundles/actions.js +48 -0
- package/dist/_internal/server/features/bundles/index.d.ts +1 -0
- package/dist/_internal/server/features/bundles/index.js +1 -0
- package/dist/_internal/server/features/cart/actions.js +3 -3
- package/dist/_internal/server/features/checkout/actions.d.ts +4 -0
- package/dist/_internal/server/features/checkout/actions.js +23 -12
- package/dist/_internal/server/features/events/actions.js +3 -3
- package/dist/_internal/server/features/orders/actions.js +4 -4
- package/dist/_internal/server/features/payments/data.d.ts +5 -3
- package/dist/_internal/server/features/payments/data.js +13 -8
- package/dist/_internal/server/features/payments/index.d.ts +1 -1
- package/dist/_internal/server/features/payments/index.js +1 -1
- package/dist/_internal/server/features/pre-orders/actions.js +1 -1
- package/dist/_internal/server/features/products/actions.js +6 -6
- package/dist/_internal/server/features/promotions/actions.js +3 -3
- package/dist/_internal/shared/features/categories/bundle-config.d.ts +24 -0
- package/dist/_internal/shared/features/categories/bundle-config.js +28 -0
- package/dist/_internal/shared/features/categories/bundle-copy.d.ts +7 -0
- package/dist/_internal/shared/features/categories/bundle-copy.js +9 -0
- package/dist/_internal/shared/features/categories/bundle-schemas.d.ts +69 -19
- package/dist/_internal/shared/features/categories/bundle-schemas.js +20 -2
- package/dist/_internal/shared/features/checkout/config.d.ts +1 -1
- package/dist/_internal/shared/features/checkout/config.js +1 -1
- package/dist/_internal/shared/features/payments/config.d.ts +2 -1
- package/dist/_internal/shared/features/payments/config.js +2 -1
- package/dist/_internal/shared/features/products/schema.d.ts +8 -8
- package/dist/constants/field-names.d.ts +8 -1
- package/dist/constants/field-names.js +9 -2
- package/dist/constants/table-keys.d.ts +3 -0
- package/dist/constants/table-keys.js +3 -0
- package/dist/features/admin/components/AdminSiteSettingsView.js +17 -15
- package/dist/features/admin/components/DashboardStats.js +20 -12
- package/dist/features/admin/components/analytics/AdminAnalyticsCharts.js +18 -2
- package/dist/features/admin/components/analytics/AdminStatCard.d.ts +7 -1
- package/dist/features/admin/components/analytics/AdminStatCard.js +13 -2
- package/dist/features/admin/schemas/firestore.d.ts +18 -4
- package/dist/features/admin/schemas/firestore.js +11 -1
- package/dist/features/categories/components/BundleBuyNowCta.d.ts +3 -1
- package/dist/features/categories/components/BundleBuyNowCta.js +3 -3
- package/dist/features/categories/components/BundleDynamicRuleEditor.d.ts +1 -1
- package/dist/features/categories/components/BundleDynamicRuleEditor.js +2 -1
- package/dist/features/categories/components/BundlesListView.d.ts +4 -1
- package/dist/features/categories/components/BundlesListView.js +2 -2
- package/dist/features/categories/components/CategoryBundlesListing.d.ts +4 -1
- package/dist/features/categories/components/CategoryBundlesListing.js +9 -8
- package/dist/features/categories/schemas/firestore.d.ts +29 -1
- package/dist/features/checkout/actions/checkout-actions.d.ts +11 -0
- package/dist/features/checkout/actions/checkout-actions.js +26 -0
- package/dist/features/filters/FilterFacetSection.d.ts +6 -1
- package/dist/features/filters/FilterFacetSection.js +25 -2
- package/dist/features/homepage/components/FeaturedBundlesSection.d.ts +4 -1
- package/dist/features/homepage/components/FeaturedBundlesSection.js +9 -8
- package/dist/features/orders/actions/refund-actions.js +10 -7
- package/dist/features/orders/schemas/firestore.d.ts +16 -13
- package/dist/features/orders/schemas/firestore.js +6 -5
- package/dist/features/pre-orders/components/MarketplacePreorderCard.js +1 -1
- package/dist/features/pre-orders/components/PreOrdersIndexListing.js +2 -1
- package/dist/features/products/components/AuctionsIndexListing.js +2 -1
- package/dist/features/products/components/ProductDetailPageView.js +3 -1
- package/dist/features/products/components/ProductDetailView.d.ts +3 -1
- package/dist/features/products/components/ProductDetailView.js +2 -2
- package/dist/features/products/components/ProductFilters.d.ts +10 -4
- package/dist/features/products/components/ProductFilters.js +20 -16
- package/dist/features/products/components/ProductGrid.js +3 -3
- package/dist/features/products/components/ProductsIndexListing.js +2 -1
- package/dist/features/products/constants/product-features.constants.js +5 -1
- package/dist/features/products/schemas/index.d.ts +2 -2
- package/dist/features/products/schemas/product-features.d.ts +1 -1
- package/dist/features/products/schemas/product-features.validators.d.ts +10 -10
- package/dist/features/products/schemas/product-features.validators.js +4 -0
- package/dist/features/seller/components/SellerCouponEditorView.js +2 -2
- package/dist/features/seller/components/analytics/SellerAnalyticsStats.js +10 -4
- package/dist/features/shell/FormShell.js +1 -1
- package/dist/features/stores/components/StoreBundlesPageView.d.ts +4 -1
- package/dist/features/stores/components/StoreBundlesPageView.js +2 -2
- package/dist/features/stores/components/StoresIndexListing.js +2 -1
- package/dist/features/wishlist/hooks/useWishlistCount.d.ts +2 -2
- package/dist/features/wishlist/hooks/useWishlistCount.js +5 -8
- package/dist/providers/db-firebase/admin-app-lite.js +8 -2
- package/dist/providers/db-firebase/admin.js +8 -2
- package/dist/seed/addresses-seed-data.js +24 -7
- package/dist/seed/cart-seed-data.d.ts +10 -10
- package/dist/seed/cart-seed-data.js +39 -2
- package/dist/seed/grouped-listings-seed-data.js +47 -0
- package/dist/seed/orders-seed-data.js +150 -0
- package/dist/seed/products-standard-seed-data.js +42 -0
- package/dist/seed/site-settings-seed-data.js +11 -4
- package/dist/seed/users-seed-data.js +1 -1
- package/dist/seed/wishlists-seed-data.js +7 -0
- package/dist/server-entry.d.ts +2 -1
- package/dist/server-entry.js +2 -1
- package/dist/server.d.ts +2 -1
- package/dist/server.js +3 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/Breadcrumb.style.css +1 -1
- package/dist/ui/components/ListingLayout.style.css +1 -1
- package/dist/ui/components/Select.style.css +1 -1
- package/dist/ui/components/SideDrawer.style.css +55 -12
- package/dist/ui/components/Typography.style.css +2 -2
- package/dist/ui/rich-text/RichText.style.css +4 -5
- package/package.json +1 -1
|
@@ -70,10 +70,11 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
|
|
|
70
70
|
const [watermarkImageUrl, setWatermarkImageUrl] = React.useState("");
|
|
71
71
|
const [watermarkSize, setWatermarkSize] = React.useState(30);
|
|
72
72
|
const [watermarkOpacity, setWatermarkOpacity] = React.useState(20);
|
|
73
|
-
// ⑦ Fees
|
|
74
|
-
const [
|
|
75
|
-
const [
|
|
76
|
-
const [
|
|
73
|
+
// ⑦ Fees — all read/written under commissions key
|
|
74
|
+
const [platformFeePercent, setPlatformFeePercent] = React.useState(5);
|
|
75
|
+
const [gstPercent, setGstPercent] = React.useState(18);
|
|
76
|
+
const [minimumTransactionFee, setMinimumTransactionFee] = React.useState(0);
|
|
77
|
+
const [gatewayFeePercent, setGatewayFeePercent] = React.useState(2);
|
|
77
78
|
const [payoutHoldDays, setPayoutHoldDays] = React.useState(7);
|
|
78
79
|
const [minPayoutAmount, setMinPayoutAmount] = React.useState(100);
|
|
79
80
|
const [auctionListingFee, setAuctionListingFee] = React.useState(0);
|
|
@@ -172,15 +173,16 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
|
|
|
172
173
|
setWatermarkImageUrl(s.watermark?.imageUrl ?? "");
|
|
173
174
|
setWatermarkSize(s.watermark?.size ?? 30);
|
|
174
175
|
setWatermarkOpacity(s.watermark?.opacity ?? 20);
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
176
|
+
setPlatformFeePercent(s.commissions?.platformFeePercent ?? 5);
|
|
177
|
+
setGstPercent(s.commissions?.gstPercent ?? 18);
|
|
178
|
+
setMinimumTransactionFee(s.commissions?.minimumTransactionFee ?? 0);
|
|
179
|
+
setGatewayFeePercent(s.commissions?.gatewayFeePercent ?? 2);
|
|
180
|
+
setPayoutHoldDays(s.commissions?.payoutHoldDays ?? 7);
|
|
181
|
+
setMinPayoutAmount(s.commissions?.minPayoutAmount ?? 100);
|
|
182
|
+
setAuctionListingFee(s.commissions?.auctionListingFee ?? 0);
|
|
183
|
+
setPreOrderListingFee(s.commissions?.preOrderListingFee ?? 0);
|
|
184
|
+
setFeaturedSlotFee(s.commissions?.featuredSlotFee ?? 999);
|
|
185
|
+
setPromotedSlotFee(s.commissions?.promotedSlotFee ?? 499);
|
|
184
186
|
setRazorpayKeyId(s.credentialsMasked?.razorpayKeyId ?? "");
|
|
185
187
|
setRazorpaySecret(s.credentialsMasked?.razorpaySecret ?? "");
|
|
186
188
|
setShiprocketToken(s.credentialsMasked?.shiprocketToken ?? "");
|
|
@@ -259,7 +261,7 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
|
|
|
259
261
|
watermark: { type: watermarkType, text: watermarkText, imageUrl: watermarkImageUrl, size: watermarkSize, opacity: watermarkOpacity },
|
|
260
262
|
}));
|
|
261
263
|
const feesMutation = useSave("Fees", () => ({
|
|
262
|
-
|
|
264
|
+
commissions: { platformFeePercent, gstPercent, minimumTransactionFee, gatewayFeePercent, payoutHoldDays, minPayoutAmount, auctionListingFee, preOrderListingFee, featuredSlotFee, promotedSlotFee },
|
|
263
265
|
}));
|
|
264
266
|
const integrationsMutation = useSave("Integrations", () => ({
|
|
265
267
|
credentials: {
|
|
@@ -328,7 +330,7 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
|
|
|
328
330
|
["limits", "⑪ Limits"],
|
|
329
331
|
["legal", "⑫ Legal"],
|
|
330
332
|
["whatsapp", "⑬ WhatsApp"],
|
|
331
|
-
].map(([value, label]) => (_jsx(TabsTrigger, { value: value, children: label }, value))) }), _jsx(TabsContent, { value: "about", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); aboutMutation.mutate(); }, className: "space-y-4 pt-4", children: [_jsx("p", { className: "text-xs text-zinc-500 dark:text-zinc-400", children: "Override the About page hero and mission text. Leave blank to use the platform defaults." }), _jsx(Input, { label: "Hero title", value: aboutTitle, onChange: (e) => setAboutTitle(e.target.value), placeholder: "About LetItRip" }), _jsx(Input, { label: "Hero subtitle", value: aboutSubtitle, onChange: (e) => setAboutSubtitle(e.target.value), placeholder: "Connecting buyers, sellers, and bidders in one vibrant marketplace" }), _jsx(Input, { label: "Mission section title", value: aboutMissionTitle, onChange: (e) => setAboutMissionTitle(e.target.value), placeholder: "Our Mission" }), _jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1", children: "Mission text" }), _jsx("textarea", { value: aboutMissionText, onChange: (e) => setAboutMissionText(e.target.value), placeholder: "LetItRip was built to democratise commerce\u2026", rows: 4, className: "w-full rounded-md border border-zinc-300 dark:border-zinc-600 bg-white dark:bg-zinc-800 p-3 text-sm text-zinc-800 dark:text-zinc-200 resize-y focus:outline-none focus:ring-2 focus:ring-blue-500" })] }), _jsx(Input, { label: "CTA banner title", value: aboutCtaTitle, onChange: (e) => setAboutCtaTitle(e.target.value), placeholder: "Ready to get started?" }), _jsx(GroupSaveButton, { isPending: aboutMutation.isPending })] }) }), _jsx(TabsContent, { value: "branding", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); brandingMutation.mutate(); }, className: "space-y-4 pt-4", children: [_jsx(Input, { label: "Site name", value: siteName, onChange: (e) => setSiteName(e.target.value), placeholder: "LetItRip" }), _jsx(Input, { label: "Tagline", value: tagline, onChange: (e) => setTagline(e.target.value), placeholder: "India's Largest Collectibles Marketplace" }), _jsx(ImageUpload, { label: "Logo", currentImage: logoUrl, onUpload: (file) => upload(file, "store"), onChange: setLogoUrl }), _jsx(ImageUpload, { label: "Favicon", currentImage: faviconUrl, onUpload: (file) => upload(file, "store"), onChange: setFaviconUrl }), _jsxs("div", { className: "space-y-3 rounded-lg border border-zinc-200 dark:border-zinc-700 p-4", children: [_jsx(Toggle, { label: "Maintenance mode", checked: maintenanceMode, onChange: setMaintenanceMode }), _jsx(Input, { label: "Maintenance message", value: maintenanceMessage, onChange: (e) => setMaintenanceMessage(e.target.value), placeholder: "We're back soon.", disabled: !maintenanceMode })] }), _jsx(GroupSaveButton, { isPending: brandingMutation.isPending })] }) }), _jsx(TabsContent, { value: "appearance", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); appearanceMutation.mutate(); }, className: "space-y-4 pt-4", children: [_jsxs("div", { className: "grid grid-cols-3 gap-4", children: [_jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1", children: "Primary color" }), _jsx("input", { type: "color", value: primaryColor || "#000000", onChange: (e) => setPrimaryColor(e.target.value), className: "h-10 w-full rounded border border-zinc-200 dark:border-zinc-700 cursor-pointer" })] }), _jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1", children: "Secondary color" }), _jsx("input", { type: "color", value: secondaryColor || "#000000", onChange: (e) => setSecondaryColor(e.target.value), className: "h-10 w-full rounded border border-zinc-200 dark:border-zinc-700 cursor-pointer" })] }), _jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1", children: "Accent color" }), _jsx("input", { type: "color", value: accentColor || "#000000", onChange: (e) => setAccentColor(e.target.value), className: "h-10 w-full rounded border border-zinc-200 dark:border-zinc-700 cursor-pointer" })] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(Select, { label: "Default theme", options: THEME_OPTIONS, value: defaultTheme, onValueChange: setDefaultTheme }), _jsx(Select, { label: "Font family", options: FONT_OPTIONS, value: fontFamily, onValueChange: setFontFamily })] }), _jsx(GroupSaveButton, { isPending: appearanceMutation.isPending })] }) }), _jsx(TabsContent, { value: "announcement", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); announcementMutation.mutate(); }, className: "space-y-4 pt-4", children: [_jsx(Toggle, { label: "Show announcement bar", checked: announcementEnabled, onChange: setAnnouncementEnabled }), _jsx(Input, { label: "Announcement text", value: announcementText, onChange: (e) => setAnnouncementText(e.target.value), placeholder: "\uD83C\uDF89 Free shipping on orders \u20B9999+", disabled: !announcementEnabled }), _jsx(Input, { label: "Link URL (optional)", value: announcementLink, onChange: (e) => setAnnouncementLink(e.target.value), placeholder: "/products", disabled: !announcementEnabled }), _jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1", children: "Background color" }), _jsx("input", { type: "color", value: announcementBg || "#1d4ed8", onChange: (e) => setAnnouncementBg(e.target.value), className: "h-10 w-32 rounded border border-zinc-200 dark:border-zinc-700 cursor-pointer", disabled: !announcementEnabled })] }), _jsx(GroupSaveButton, { isPending: announcementMutation.isPending })] }) }), _jsx(TabsContent, { value: "seo", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); seoMutation.mutate(); }, className: "space-y-4 pt-4", children: [_jsx(Input, { label: "Default meta title", value: seoTitle, onChange: (e) => setSeoTitle(e.target.value), placeholder: "LetItRip \u2014 Buy, Sell & Auction Collectibles in India", maxLength: 60, helperText: "Max 60 chars. Use {page} token for dynamic insertion." }), _jsx(Input, { label: "Default meta description", value: seoDescription, onChange: (e) => setSeoDescription(e.target.value), placeholder: "India's largest collectibles marketplace\u2026", maxLength: 160, helperText: "Max 160 chars." }), _jsx(ImageUpload, { label: "Default OG image", currentImage: seoOgImage, onUpload: (file) => upload(file, "store"), onChange: setSeoOgImage }), _jsx(Input, { label: "Canonical base URL", value: canonicalUrl, onChange: (e) => setCanonicalUrl(e.target.value), placeholder: "https://letitrip.in" }), _jsx(Toggle, { label: "Robots noindex (disables search indexing \u2014 use carefully)", checked: seoNoIndex, onChange: setSeoNoIndex }), _jsx(GroupSaveButton, { isPending: seoMutation.isPending })] }) }), _jsx(TabsContent, { value: "contact", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); contactMutation.mutate(); }, className: "space-y-4 pt-4", children: [_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(Input, { label: "Support email", value: supportEmail, onChange: (e) => setSupportEmail(e.target.value), type: "email", placeholder: "support@letitrip.in" }), _jsx(Input, { label: "Support phone", value: supportPhone, onChange: (e) => setSupportPhone(e.target.value), placeholder: "+91 XXXXX XXXXX" })] }), _jsx(Input, { label: "Physical address", value: supportAddress, onChange: (e) => setSupportAddress(e.target.value), placeholder: "Mumbai, Maharashtra, India" }), _jsx(Input, { label: "Support hours", value: supportHours, onChange: (e) => setSupportHours(e.target.value), placeholder: "Mon\u2013Fri, 10 AM \u2013 6 PM IST" }), _jsx("p", { className: "text-sm font-medium text-zinc-600 dark:text-zinc-400 pt-2", children: "Social links" }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(Input, { label: "Instagram URL", value: instagram, onChange: (e) => setInstagram(e.target.value), placeholder: "https://instagram.com/letitrip" }), _jsx(Input, { label: "Twitter / X URL", value: twitter, onChange: (e) => setTwitter(e.target.value), placeholder: "https://twitter.com/letitrip" }), _jsx(Input, { label: "Facebook URL", value: facebook, onChange: (e) => setFacebook(e.target.value), placeholder: "https://facebook.com/letitrip" }), _jsx(Input, { label: "YouTube URL", value: youtube, onChange: (e) => setYoutube(e.target.value), placeholder: "https://youtube.com/@letitrip" }), _jsx(Input, { label: "WhatsApp number", value: whatsapp, onChange: (e) => setWhatsapp(e.target.value), placeholder: "+91XXXXXXXXXX" }), _jsx(Input, { label: "LinkedIn URL", value: linkedin, onChange: (e) => setLinkedin(e.target.value), placeholder: "https://linkedin.com/company/letitrip" }), _jsx(Input, { label: "Pinterest URL", value: pinterest, onChange: (e) => setPinterest(e.target.value), placeholder: "https://pinterest.com/letitrip" })] }), _jsx(GroupSaveButton, { isPending: contactMutation.isPending })] }) }), _jsx(TabsContent, { value: "watermark", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); watermarkMutation.mutate(); }, className: "space-y-4 pt-4", children: [_jsx(Select, { label: "Watermark type", options: [{ label: "Text", value: "text" }, { label: "Image", value: "image" }], value: watermarkType, onValueChange: (v) => setWatermarkType(v) }), watermarkType === "text" ? (_jsx(Input, { label: "Watermark text", value: watermarkText, onChange: (e) => setWatermarkText(e.target.value), placeholder: "letitrip.in" })) : (_jsx(ImageUpload, { label: "Watermark image", currentImage: watermarkImageUrl, onUpload: (file) => upload(file, "store"), onChange: setWatermarkImageUrl })), _jsx(Slider, { label: `Size — ${watermarkSize}% of image width`, value: watermarkSize, onChange: setWatermarkSize, min: 5, max: 100, step: 5 }), _jsx(Slider, { label: `Opacity — ${watermarkOpacity}%`, value: watermarkOpacity, onChange: setWatermarkOpacity, min: 5, max: 100, step: 5 }), _jsxs("div", { className: "rounded-lg border border-zinc-200 dark:border-zinc-700 p-4 bg-zinc-50 dark:bg-zinc-800", children: [_jsx("p", { className: "text-xs text-zinc-500 mb-2", children: "Preview (text watermark only)" }), _jsx("div", { className: "relative bg-white dark:bg-zinc-900 rounded h-32 flex items-end justify-end overflow-hidden", children: _jsx("span", { className: "text-zinc-400 select-none p-2 font-medium", style: { fontSize: `${Math.max(10, watermarkSize / 5)}px`, opacity: watermarkOpacity / 100 }, children: watermarkText }) })] }), _jsx(GroupSaveButton, { isPending: watermarkMutation.isPending })] }) }), _jsx(TabsContent, { value: "fees", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); feesMutation.mutate(); }, className: "space-y-4 pt-4", children: [_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(Input, { label: "Platform commission (%)", value: String(platformCommission), onChange: (e) => setPlatformCommission(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1 }), _jsx(Input, { label: "Buyer convenience fee (%)", value: String(buyerFee), onChange: (e) => setBuyerFee(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1 }), _jsx(Input, { label: "Razorpay gateway fee (%)", value: String(razorpayFee), onChange: (e) => setRazorpayFee(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 10, step: 0.01 }), _jsx(Input, { label: "Seller payout hold (days)", value: String(payoutHoldDays), onChange: (e) => setPayoutHoldDays(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Minimum payout amount (\u20B9)", value: String(minPayoutAmount), onChange: (e) => setMinPayoutAmount(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Auction listing fee (\u20B9)", value: String(auctionListingFee), onChange: (e) => setAuctionListingFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Pre-order listing fee (\u20B9)", value: String(preOrderListingFee), onChange: (e) => setPreOrderListingFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Featured slot fee (\u20B9)", value: String(featuredSlotFee), onChange: (e) => setFeaturedSlotFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Promoted slot fee (\u20B9)", value: String(promotedSlotFee), onChange: (e) => setPromotedSlotFee(parseInt(e.target.value) || 0), type: "number", min: 0 })] }), _jsx(GroupSaveButton, { isPending: feesMutation.isPending })] }) }), _jsx(TabsContent, { value: "integrations", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); integrationsMutation.mutate(); }, className: "space-y-4 pt-4", children: [_jsx("p", { className: "text-xs text-zinc-500 dark:text-zinc-400", children: "Keys are masked in transit and stored encrypted. Click Reveal to view." }), _jsxs("div", { className: "space-y-3", children: [_jsx("p", { className: "text-sm font-medium text-zinc-600 dark:text-zinc-400", children: "Razorpay" }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(MaskedInput, { label: "Razorpay Key ID", value: razorpayKeyId, onChange: setRazorpayKeyId, placeholder: "rzp_live_\u2026" }), _jsx(MaskedInput, { label: "Razorpay Secret", value: razorpaySecret, onChange: setRazorpaySecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] }), _jsxs("div", { className: "space-y-3", children: [_jsx("p", { className: "text-sm font-medium text-zinc-600 dark:text-zinc-400", children: "Shiprocket" }), _jsx(MaskedInput, { label: "Shiprocket API token", value: shiprocketToken, onChange: setShiprocketToken, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] }), _jsxs("div", { className: "space-y-3", children: [_jsx("p", { className: "text-sm font-medium text-zinc-600 dark:text-zinc-400", children: "SMTP / Email" }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(Input, { label: "SMTP host", value: smtpHost, onChange: (e) => setSmtpHost(e.target.value), placeholder: "smtp.sendgrid.net" }), _jsx(Input, { label: "SMTP port", value: smtpPort, onChange: (e) => setSmtpPort(e.target.value), type: "number", placeholder: "587" }), _jsx(Input, { label: "SMTP user", value: smtpUser, onChange: (e) => setSmtpUser(e.target.value), placeholder: "apikey" }), _jsx(MaskedInput, { label: "SMTP password", value: smtpPassword, onChange: setSmtpPassword, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] }), _jsx(Input, { label: "From address", value: smtpFrom, onChange: (e) => setSmtpFrom(e.target.value), placeholder: "noreply@letitrip.in", type: "email" })] }), _jsxs("div", { className: "space-y-3", children: [_jsx("p", { className: "text-sm font-medium text-zinc-600 dark:text-zinc-400", children: "Analytics & Tracking" }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(Input, { label: "Google Analytics ID", value: gaMeasurementId, onChange: (e) => setGaMeasurementId(e.target.value), placeholder: "G-XXXXXXXXXX" }), _jsx(Input, { label: "Facebook Pixel ID", value: fbPixelId, onChange: (e) => setFbPixelId(e.target.value), placeholder: "XXXXXXXXXXXXXXXX" }), _jsx(Input, { label: "GTM Container ID", value: gtmContainerId, onChange: (e) => setGtmContainerId(e.target.value), placeholder: "GTM-XXXXXXX" })] })] }), _jsxs("div", { className: "space-y-3", children: [_jsx("p", { className: "text-sm font-medium text-zinc-600 dark:text-zinc-400", children: "Meta (Instagram & Facebook Social Feed)" }), _jsx("p", { className: "text-xs text-zinc-500 dark:text-zinc-400", children: "Used by the Social Feed section to fetch Instagram and Facebook posts via Meta Graph API v19." }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(MaskedInput, { label: "Page Access Token", value: metaPageAccessToken, onChange: setMetaPageAccessToken, placeholder: "EAAxxxxxxx\u2026" }), _jsx(Input, { label: "Page ID (or handle)", value: metaPageId, onChange: (e) => setMetaPageId(e.target.value), placeholder: "letitrip" })] })] }), _jsxs("div", { className: "space-y-3", children: [_jsx("p", { className: "text-sm font-medium text-zinc-600 dark:text-zinc-400", children: "TikTok for Developers (Social Feed)" }), _jsx("p", { className: "text-xs text-zinc-500 dark:text-zinc-400", children: "Client credentials + long-lived access token from TikTok for Developers. Used to list your account's public videos." }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(MaskedInput, { label: "Client Key", value: tiktokClientKey, onChange: setTiktokClientKey, placeholder: "aw\u2026" }), _jsx(MaskedInput, { label: "Client Secret", value: tiktokClientSecret, onChange: setTiktokClientSecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" }), _jsx(MaskedInput, { label: "Access Token (long-lived)", value: tiktokAccessToken, onChange: setTiktokAccessToken, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] }), _jsxs("div", { className: "space-y-3", children: [_jsx("p", { className: "text-sm font-medium text-zinc-600 dark:text-zinc-400", children: "DeviantArt OAuth2 (Social Feed)" }), _jsx("p", { className: "text-xs text-zinc-500 dark:text-zinc-400", children: "Client credentials for DeviantArt gallery fetching (client-credentials OAuth2 flow \u2014 no user login required)." }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(MaskedInput, { label: "Client ID", value: deviantartClientId, onChange: setDeviantartClientId, placeholder: "1234" }), _jsx(MaskedInput, { label: "Client Secret", value: deviantartClientSecret, onChange: setDeviantartClientSecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] }), _jsx(GroupSaveButton, { isPending: integrationsMutation.isPending })] }) }), _jsx(TabsContent, { value: "shipping", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); shippingMutation.mutate(); }, className: "space-y-4 pt-4", children: [_jsx(Input, { label: "Free shipping threshold (\u20B9)", value: String(freeShippingThreshold), onChange: (e) => setFreeShippingThreshold(parseInt(e.target.value) || 0), type: "number", min: 0, helperText: "Orders above this amount get free shipping." }), _jsx(Toggle, { label: "Cash on delivery (COD) enabled", checked: codEnabled, onChange: setCodEnabled }), _jsx(Select, { label: "Default carrier", options: CARRIER_OPTIONS, value: defaultCarrier, onValueChange: setDefaultCarrier }), _jsx(Input, { label: "Max delivery radius (km, 0 = no limit)", value: String(maxDeliveryRadius), onChange: (e) => setMaxDeliveryRadius(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(GroupSaveButton, { isPending: shippingMutation.isPending })] }) }), _jsx(TabsContent, { value: "auction", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); auctionMutation.mutate(); }, className: "space-y-4 pt-4", children: [_jsx(Input, { label: "Minimum bid increment (\u20B9)", value: String(minBidIncrement), onChange: (e) => setMinBidIncrement(parseInt(e.target.value) || 0), type: "number", min: 1, helperText: "Global default \u2014 individual auctions may override." }), _jsx(Input, { label: "Auto-extend window (minutes before end)", value: String(autoExtendWindow), onChange: (e) => setAutoExtendWindow(parseInt(e.target.value) || 0), type: "number", min: 0, helperText: "Extend auction end time if a bid arrives within this window." }), _jsx(Input, { label: "Settlement grace period (hours)", value: String(settlementGrace), onChange: (e) => setSettlementGrace(parseInt(e.target.value) || 0), type: "number", min: 1, helperText: "Time winner has to pay before the auction is re-listed." }), _jsx(GroupSaveButton, { isPending: auctionMutation.isPending })] }) }), _jsx(TabsContent, { value: "limits", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); limitsMutation.mutate(); }, className: "space-y-4 pt-4", children: [_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(Input, { label: "Max products per store", value: String(maxProductsPerStore), onChange: (e) => setMaxProductsPerStore(parseInt(e.target.value) || 0), type: "number", min: 1 }), _jsx(Input, { label: "Max images per product", value: String(maxImagesPerProduct), onChange: (e) => setMaxImagesPerProduct(parseInt(e.target.value) || 0), type: "number", min: 1 }), _jsx(Input, { label: "Max video size (MB)", value: String(maxVideoSizeMb), onChange: (e) => setMaxVideoSizeMb(parseInt(e.target.value) || 0), type: "number", min: 1 }), _jsx(Input, { label: "Max custom fields per product", value: String(maxCustomFields), onChange: (e) => setMaxCustomFields(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Max custom sections per product", value: String(maxCustomSections), onChange: (e) => setMaxCustomSections(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Order cancellation window (hours)", value: String(orderCancelWindow), onChange: (e) => setOrderCancelWindow(parseInt(e.target.value) || 0), type: "number", min: 0 })] }), _jsx(GroupSaveButton, { isPending: limitsMutation.isPending })] }) }), _jsx(TabsContent, { value: "whatsapp", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); whatsappMutation.mutate(); }, className: "space-y-4 pt-4", children: [_jsx("p", { className: "text-xs text-zinc-500 dark:text-zinc-400", children: "Platform-level WhatsApp Business Cloud API credentials. Used for automated purchase announcements to admin numbers when orders are placed. Store owners configure their own credentials in Store \u2192 WhatsApp." }), _jsx(Input, { label: "Phone Number ID", value: waPhoneNumberId, onChange: (e) => setWaPhoneNumberId(e.target.value), placeholder: "987654321098765", helperText: "From Meta for Developers \u2192 App \u2192 WhatsApp \u2192 API Setup" }), _jsx(MaskedInput, { label: "Cloud API System User Token", value: waCloudApiToken, onChange: setWaCloudApiToken, placeholder: "EAAxxxxxxxx\u2026", helperText: "Long-lived system user access token with WhatsApp Business permissions" }), _jsx(Input, { label: "Admin Notify Numbers", value: waAdminNotifyNumbers, onChange: (e) => setWaAdminNotifyNumbers(e.target.value), placeholder: "919876543210,918765432109", helperText: "Comma-separated, digits-only, include country code. These receive a WhatsApp message when any order is placed." }), _jsx(GroupSaveButton, { isPending: whatsappMutation.isPending })] }) }), _jsx(TabsContent, { value: "legal", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); legalMutation.mutate(); }, className: "space-y-5 pt-4", children: [[
|
|
333
|
+
].map(([value, label]) => (_jsx(TabsTrigger, { value: value, children: label }, value))) }), _jsx(TabsContent, { value: "about", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); aboutMutation.mutate(); }, className: "space-y-4 pt-4", children: [_jsx("p", { className: "text-xs text-zinc-500 dark:text-zinc-400", children: "Override the About page hero and mission text. Leave blank to use the platform defaults." }), _jsx(Input, { label: "Hero title", value: aboutTitle, onChange: (e) => setAboutTitle(e.target.value), placeholder: "About LetItRip" }), _jsx(Input, { label: "Hero subtitle", value: aboutSubtitle, onChange: (e) => setAboutSubtitle(e.target.value), placeholder: "Connecting buyers, sellers, and bidders in one vibrant marketplace" }), _jsx(Input, { label: "Mission section title", value: aboutMissionTitle, onChange: (e) => setAboutMissionTitle(e.target.value), placeholder: "Our Mission" }), _jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1", children: "Mission text" }), _jsx("textarea", { value: aboutMissionText, onChange: (e) => setAboutMissionText(e.target.value), placeholder: "LetItRip was built to democratise commerce\u2026", rows: 4, className: "w-full rounded-md border border-zinc-300 dark:border-zinc-600 bg-white dark:bg-zinc-800 p-3 text-sm text-zinc-800 dark:text-zinc-200 resize-y focus:outline-none focus:ring-2 focus:ring-blue-500" })] }), _jsx(Input, { label: "CTA banner title", value: aboutCtaTitle, onChange: (e) => setAboutCtaTitle(e.target.value), placeholder: "Ready to get started?" }), _jsx(GroupSaveButton, { isPending: aboutMutation.isPending })] }) }), _jsx(TabsContent, { value: "branding", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); brandingMutation.mutate(); }, className: "space-y-4 pt-4", children: [_jsx(Input, { label: "Site name", value: siteName, onChange: (e) => setSiteName(e.target.value), placeholder: "LetItRip" }), _jsx(Input, { label: "Tagline", value: tagline, onChange: (e) => setTagline(e.target.value), placeholder: "India's Largest Collectibles Marketplace" }), _jsx(ImageUpload, { label: "Logo", currentImage: logoUrl, onUpload: (file) => upload(file, "store"), onChange: setLogoUrl }), _jsx(ImageUpload, { label: "Favicon", currentImage: faviconUrl, onUpload: (file) => upload(file, "store"), onChange: setFaviconUrl }), _jsxs("div", { className: "space-y-3 rounded-lg border border-zinc-200 dark:border-zinc-700 p-4", children: [_jsx(Toggle, { label: "Maintenance mode", checked: maintenanceMode, onChange: setMaintenanceMode }), _jsx(Input, { label: "Maintenance message", value: maintenanceMessage, onChange: (e) => setMaintenanceMessage(e.target.value), placeholder: "We're back soon.", disabled: !maintenanceMode })] }), _jsx(GroupSaveButton, { isPending: brandingMutation.isPending })] }) }), _jsx(TabsContent, { value: "appearance", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); appearanceMutation.mutate(); }, className: "space-y-4 pt-4", children: [_jsxs("div", { className: "grid grid-cols-3 gap-4", children: [_jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1", children: "Primary color" }), _jsx("input", { type: "color", value: primaryColor || "#000000", onChange: (e) => setPrimaryColor(e.target.value), className: "h-10 w-full rounded border border-zinc-200 dark:border-zinc-700 cursor-pointer" })] }), _jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1", children: "Secondary color" }), _jsx("input", { type: "color", value: secondaryColor || "#000000", onChange: (e) => setSecondaryColor(e.target.value), className: "h-10 w-full rounded border border-zinc-200 dark:border-zinc-700 cursor-pointer" })] }), _jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1", children: "Accent color" }), _jsx("input", { type: "color", value: accentColor || "#000000", onChange: (e) => setAccentColor(e.target.value), className: "h-10 w-full rounded border border-zinc-200 dark:border-zinc-700 cursor-pointer" })] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(Select, { label: "Default theme", options: THEME_OPTIONS, value: defaultTheme, onValueChange: setDefaultTheme }), _jsx(Select, { label: "Font family", options: FONT_OPTIONS, value: fontFamily, onValueChange: setFontFamily })] }), _jsx(GroupSaveButton, { isPending: appearanceMutation.isPending })] }) }), _jsx(TabsContent, { value: "announcement", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); announcementMutation.mutate(); }, className: "space-y-4 pt-4", children: [_jsx(Toggle, { label: "Show announcement bar", checked: announcementEnabled, onChange: setAnnouncementEnabled }), _jsx(Input, { label: "Announcement text", value: announcementText, onChange: (e) => setAnnouncementText(e.target.value), placeholder: "\uD83C\uDF89 Free shipping on orders \u20B9999+", disabled: !announcementEnabled }), _jsx(Input, { label: "Link URL (optional)", value: announcementLink, onChange: (e) => setAnnouncementLink(e.target.value), placeholder: "/products", disabled: !announcementEnabled }), _jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1", children: "Background color" }), _jsx("input", { type: "color", value: announcementBg || "#1d4ed8", onChange: (e) => setAnnouncementBg(e.target.value), className: "h-10 w-32 rounded border border-zinc-200 dark:border-zinc-700 cursor-pointer", disabled: !announcementEnabled })] }), _jsx(GroupSaveButton, { isPending: announcementMutation.isPending })] }) }), _jsx(TabsContent, { value: "seo", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); seoMutation.mutate(); }, className: "space-y-4 pt-4", children: [_jsx(Input, { label: "Default meta title", value: seoTitle, onChange: (e) => setSeoTitle(e.target.value), placeholder: "LetItRip \u2014 Buy, Sell & Auction Collectibles in India", maxLength: 60, helperText: "Max 60 chars. Use {page} token for dynamic insertion." }), _jsx(Input, { label: "Default meta description", value: seoDescription, onChange: (e) => setSeoDescription(e.target.value), placeholder: "India's largest collectibles marketplace\u2026", maxLength: 160, helperText: "Max 160 chars." }), _jsx(ImageUpload, { label: "Default OG image", currentImage: seoOgImage, onUpload: (file) => upload(file, "store"), onChange: setSeoOgImage }), _jsx(Input, { label: "Canonical base URL", value: canonicalUrl, onChange: (e) => setCanonicalUrl(e.target.value), placeholder: "https://letitrip.in" }), _jsx(Toggle, { label: "Robots noindex (disables search indexing \u2014 use carefully)", checked: seoNoIndex, onChange: setSeoNoIndex }), _jsx(GroupSaveButton, { isPending: seoMutation.isPending })] }) }), _jsx(TabsContent, { value: "contact", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); contactMutation.mutate(); }, className: "space-y-4 pt-4", children: [_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(Input, { label: "Support email", value: supportEmail, onChange: (e) => setSupportEmail(e.target.value), type: "email", placeholder: "support@letitrip.in" }), _jsx(Input, { label: "Support phone", value: supportPhone, onChange: (e) => setSupportPhone(e.target.value), placeholder: "+91 XXXXX XXXXX" })] }), _jsx(Input, { label: "Physical address", value: supportAddress, onChange: (e) => setSupportAddress(e.target.value), placeholder: "Mumbai, Maharashtra, India" }), _jsx(Input, { label: "Support hours", value: supportHours, onChange: (e) => setSupportHours(e.target.value), placeholder: "Mon\u2013Fri, 10 AM \u2013 6 PM IST" }), _jsx("p", { className: "text-sm font-medium text-zinc-600 dark:text-zinc-400 pt-2", children: "Social links" }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(Input, { label: "Instagram URL", value: instagram, onChange: (e) => setInstagram(e.target.value), placeholder: "https://instagram.com/letitrip" }), _jsx(Input, { label: "Twitter / X URL", value: twitter, onChange: (e) => setTwitter(e.target.value), placeholder: "https://twitter.com/letitrip" }), _jsx(Input, { label: "Facebook URL", value: facebook, onChange: (e) => setFacebook(e.target.value), placeholder: "https://facebook.com/letitrip" }), _jsx(Input, { label: "YouTube URL", value: youtube, onChange: (e) => setYoutube(e.target.value), placeholder: "https://youtube.com/@letitrip" }), _jsx(Input, { label: "WhatsApp number", value: whatsapp, onChange: (e) => setWhatsapp(e.target.value), placeholder: "+91XXXXXXXXXX" }), _jsx(Input, { label: "LinkedIn URL", value: linkedin, onChange: (e) => setLinkedin(e.target.value), placeholder: "https://linkedin.com/company/letitrip" }), _jsx(Input, { label: "Pinterest URL", value: pinterest, onChange: (e) => setPinterest(e.target.value), placeholder: "https://pinterest.com/letitrip" })] }), _jsx(GroupSaveButton, { isPending: contactMutation.isPending })] }) }), _jsx(TabsContent, { value: "watermark", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); watermarkMutation.mutate(); }, className: "space-y-4 pt-4", children: [_jsx(Select, { label: "Watermark type", options: [{ label: "Text", value: "text" }, { label: "Image", value: "image" }], value: watermarkType, onValueChange: (v) => setWatermarkType(v) }), watermarkType === "text" ? (_jsx(Input, { label: "Watermark text", value: watermarkText, onChange: (e) => setWatermarkText(e.target.value), placeholder: "letitrip.in" })) : (_jsx(ImageUpload, { label: "Watermark image", currentImage: watermarkImageUrl, onUpload: (file) => upload(file, "store"), onChange: setWatermarkImageUrl })), _jsx(Slider, { label: `Size — ${watermarkSize}% of image width`, value: watermarkSize, onChange: setWatermarkSize, min: 5, max: 100, step: 5 }), _jsx(Slider, { label: `Opacity — ${watermarkOpacity}%`, value: watermarkOpacity, onChange: setWatermarkOpacity, min: 5, max: 100, step: 5 }), _jsxs("div", { className: "rounded-lg border border-zinc-200 dark:border-zinc-700 p-4 bg-zinc-50 dark:bg-zinc-800", children: [_jsx("p", { className: "text-xs text-zinc-500 mb-2", children: "Preview (text watermark only)" }), _jsx("div", { className: "relative bg-white dark:bg-zinc-900 rounded h-32 flex items-end justify-end overflow-hidden", children: _jsx("span", { className: "text-zinc-400 select-none p-2 font-medium", style: { fontSize: `${Math.max(10, watermarkSize / 5)}px`, opacity: watermarkOpacity / 100 }, children: watermarkText }) })] }), _jsx(GroupSaveButton, { isPending: watermarkMutation.isPending })] }) }), _jsx(TabsContent, { value: "fees", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); feesMutation.mutate(); }, className: "space-y-4 pt-4", children: [_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(Input, { label: "Platform fee \u2014 our cut (%)", helperText: "% charged on order value. Buyer pays this.", value: String(platformFeePercent), onChange: (e) => setPlatformFeePercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1 }), _jsx(Input, { label: "GST on platform fee (%)", helperText: "Applied to our fee only (not full order). Usually 18%.", value: String(gstPercent), onChange: (e) => setGstPercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1 }), _jsx(Input, { label: "Razorpay gateway cost (%)", helperText: "Gateway's own fee \u2014 absorbed by platform, not passed through.", value: String(gatewayFeePercent), onChange: (e) => setGatewayFeePercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 10, step: 0.01 }), _jsx(Input, { label: "Minimum transaction fee (\u20B9)", helperText: "Per-transaction floor. Total charge will never be below base + this.", value: String(minimumTransactionFee), onChange: (e) => setMinimumTransactionFee(parseFloat(e.target.value) || 0), type: "number", min: 0, step: 0.01 }), _jsx(Input, { label: "Seller payout hold (days)", value: String(payoutHoldDays), onChange: (e) => setPayoutHoldDays(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Minimum payout amount (\u20B9)", value: String(minPayoutAmount), onChange: (e) => setMinPayoutAmount(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Auction listing fee (\u20B9)", value: String(auctionListingFee), onChange: (e) => setAuctionListingFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Pre-order listing fee (\u20B9)", value: String(preOrderListingFee), onChange: (e) => setPreOrderListingFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Featured slot fee (\u20B9)", value: String(featuredSlotFee), onChange: (e) => setFeaturedSlotFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Promoted slot fee (\u20B9)", value: String(promotedSlotFee), onChange: (e) => setPromotedSlotFee(parseInt(e.target.value) || 0), type: "number", min: 0 })] }), _jsx(GroupSaveButton, { isPending: feesMutation.isPending })] }) }), _jsx(TabsContent, { value: "integrations", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); integrationsMutation.mutate(); }, className: "space-y-4 pt-4", children: [_jsx("p", { className: "text-xs text-zinc-500 dark:text-zinc-400", children: "Keys are masked in transit and stored encrypted. Click Reveal to view." }), _jsxs("div", { className: "space-y-3", children: [_jsx("p", { className: "text-sm font-medium text-zinc-600 dark:text-zinc-400", children: "Razorpay" }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(MaskedInput, { label: "Razorpay Key ID", value: razorpayKeyId, onChange: setRazorpayKeyId, placeholder: "rzp_live_\u2026" }), _jsx(MaskedInput, { label: "Razorpay Secret", value: razorpaySecret, onChange: setRazorpaySecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] }), _jsxs("div", { className: "space-y-3", children: [_jsx("p", { className: "text-sm font-medium text-zinc-600 dark:text-zinc-400", children: "Shiprocket" }), _jsx(MaskedInput, { label: "Shiprocket API token", value: shiprocketToken, onChange: setShiprocketToken, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] }), _jsxs("div", { className: "space-y-3", children: [_jsx("p", { className: "text-sm font-medium text-zinc-600 dark:text-zinc-400", children: "SMTP / Email" }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(Input, { label: "SMTP host", value: smtpHost, onChange: (e) => setSmtpHost(e.target.value), placeholder: "smtp.sendgrid.net" }), _jsx(Input, { label: "SMTP port", value: smtpPort, onChange: (e) => setSmtpPort(e.target.value), type: "number", placeholder: "587" }), _jsx(Input, { label: "SMTP user", value: smtpUser, onChange: (e) => setSmtpUser(e.target.value), placeholder: "apikey" }), _jsx(MaskedInput, { label: "SMTP password", value: smtpPassword, onChange: setSmtpPassword, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] }), _jsx(Input, { label: "From address", value: smtpFrom, onChange: (e) => setSmtpFrom(e.target.value), placeholder: "noreply@letitrip.in", type: "email" })] }), _jsxs("div", { className: "space-y-3", children: [_jsx("p", { className: "text-sm font-medium text-zinc-600 dark:text-zinc-400", children: "Analytics & Tracking" }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(Input, { label: "Google Analytics ID", value: gaMeasurementId, onChange: (e) => setGaMeasurementId(e.target.value), placeholder: "G-XXXXXXXXXX" }), _jsx(Input, { label: "Facebook Pixel ID", value: fbPixelId, onChange: (e) => setFbPixelId(e.target.value), placeholder: "XXXXXXXXXXXXXXXX" }), _jsx(Input, { label: "GTM Container ID", value: gtmContainerId, onChange: (e) => setGtmContainerId(e.target.value), placeholder: "GTM-XXXXXXX" })] })] }), _jsxs("div", { className: "space-y-3", children: [_jsx("p", { className: "text-sm font-medium text-zinc-600 dark:text-zinc-400", children: "Meta (Instagram & Facebook Social Feed)" }), _jsx("p", { className: "text-xs text-zinc-500 dark:text-zinc-400", children: "Used by the Social Feed section to fetch Instagram and Facebook posts via Meta Graph API v19." }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(MaskedInput, { label: "Page Access Token", value: metaPageAccessToken, onChange: setMetaPageAccessToken, placeholder: "EAAxxxxxxx\u2026" }), _jsx(Input, { label: "Page ID (or handle)", value: metaPageId, onChange: (e) => setMetaPageId(e.target.value), placeholder: "letitrip" })] })] }), _jsxs("div", { className: "space-y-3", children: [_jsx("p", { className: "text-sm font-medium text-zinc-600 dark:text-zinc-400", children: "TikTok for Developers (Social Feed)" }), _jsx("p", { className: "text-xs text-zinc-500 dark:text-zinc-400", children: "Client credentials + long-lived access token from TikTok for Developers. Used to list your account's public videos." }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(MaskedInput, { label: "Client Key", value: tiktokClientKey, onChange: setTiktokClientKey, placeholder: "aw\u2026" }), _jsx(MaskedInput, { label: "Client Secret", value: tiktokClientSecret, onChange: setTiktokClientSecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" }), _jsx(MaskedInput, { label: "Access Token (long-lived)", value: tiktokAccessToken, onChange: setTiktokAccessToken, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] }), _jsxs("div", { className: "space-y-3", children: [_jsx("p", { className: "text-sm font-medium text-zinc-600 dark:text-zinc-400", children: "DeviantArt OAuth2 (Social Feed)" }), _jsx("p", { className: "text-xs text-zinc-500 dark:text-zinc-400", children: "Client credentials for DeviantArt gallery fetching (client-credentials OAuth2 flow \u2014 no user login required)." }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(MaskedInput, { label: "Client ID", value: deviantartClientId, onChange: setDeviantartClientId, placeholder: "1234" }), _jsx(MaskedInput, { label: "Client Secret", value: deviantartClientSecret, onChange: setDeviantartClientSecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] }), _jsx(GroupSaveButton, { isPending: integrationsMutation.isPending })] }) }), _jsx(TabsContent, { value: "shipping", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); shippingMutation.mutate(); }, className: "space-y-4 pt-4", children: [_jsx(Input, { label: "Free shipping threshold (\u20B9)", value: String(freeShippingThreshold), onChange: (e) => setFreeShippingThreshold(parseInt(e.target.value) || 0), type: "number", min: 0, helperText: "Orders above this amount get free shipping." }), _jsx(Toggle, { label: "Cash on delivery (COD) enabled", checked: codEnabled, onChange: setCodEnabled }), _jsx(Select, { label: "Default carrier", options: CARRIER_OPTIONS, value: defaultCarrier, onValueChange: setDefaultCarrier }), _jsx(Input, { label: "Max delivery radius (km, 0 = no limit)", value: String(maxDeliveryRadius), onChange: (e) => setMaxDeliveryRadius(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(GroupSaveButton, { isPending: shippingMutation.isPending })] }) }), _jsx(TabsContent, { value: "auction", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); auctionMutation.mutate(); }, className: "space-y-4 pt-4", children: [_jsx(Input, { label: "Minimum bid increment (\u20B9)", value: String(minBidIncrement), onChange: (e) => setMinBidIncrement(parseInt(e.target.value) || 0), type: "number", min: 1, helperText: "Global default \u2014 individual auctions may override." }), _jsx(Input, { label: "Auto-extend window (minutes before end)", value: String(autoExtendWindow), onChange: (e) => setAutoExtendWindow(parseInt(e.target.value) || 0), type: "number", min: 0, helperText: "Extend auction end time if a bid arrives within this window." }), _jsx(Input, { label: "Settlement grace period (hours)", value: String(settlementGrace), onChange: (e) => setSettlementGrace(parseInt(e.target.value) || 0), type: "number", min: 1, helperText: "Time winner has to pay before the auction is re-listed." }), _jsx(GroupSaveButton, { isPending: auctionMutation.isPending })] }) }), _jsx(TabsContent, { value: "limits", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); limitsMutation.mutate(); }, className: "space-y-4 pt-4", children: [_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(Input, { label: "Max products per store", value: String(maxProductsPerStore), onChange: (e) => setMaxProductsPerStore(parseInt(e.target.value) || 0), type: "number", min: 1 }), _jsx(Input, { label: "Max images per product", value: String(maxImagesPerProduct), onChange: (e) => setMaxImagesPerProduct(parseInt(e.target.value) || 0), type: "number", min: 1 }), _jsx(Input, { label: "Max video size (MB)", value: String(maxVideoSizeMb), onChange: (e) => setMaxVideoSizeMb(parseInt(e.target.value) || 0), type: "number", min: 1 }), _jsx(Input, { label: "Max custom fields per product", value: String(maxCustomFields), onChange: (e) => setMaxCustomFields(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Max custom sections per product", value: String(maxCustomSections), onChange: (e) => setMaxCustomSections(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Order cancellation window (hours)", value: String(orderCancelWindow), onChange: (e) => setOrderCancelWindow(parseInt(e.target.value) || 0), type: "number", min: 0 })] }), _jsx(GroupSaveButton, { isPending: limitsMutation.isPending })] }) }), _jsx(TabsContent, { value: "whatsapp", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); whatsappMutation.mutate(); }, className: "space-y-4 pt-4", children: [_jsx("p", { className: "text-xs text-zinc-500 dark:text-zinc-400", children: "Platform-level WhatsApp Business Cloud API credentials. Used for automated purchase announcements to admin numbers when orders are placed. Store owners configure their own credentials in Store \u2192 WhatsApp." }), _jsx(Input, { label: "Phone Number ID", value: waPhoneNumberId, onChange: (e) => setWaPhoneNumberId(e.target.value), placeholder: "987654321098765", helperText: "From Meta for Developers \u2192 App \u2192 WhatsApp \u2192 API Setup" }), _jsx(MaskedInput, { label: "Cloud API System User Token", value: waCloudApiToken, onChange: setWaCloudApiToken, placeholder: "EAAxxxxxxxx\u2026", helperText: "Long-lived system user access token with WhatsApp Business permissions" }), _jsx(Input, { label: "Admin Notify Numbers", value: waAdminNotifyNumbers, onChange: (e) => setWaAdminNotifyNumbers(e.target.value), placeholder: "919876543210,918765432109", helperText: "Comma-separated, digits-only, include country code. These receive a WhatsApp message when any order is placed." }), _jsx(GroupSaveButton, { isPending: whatsappMutation.isPending })] }) }), _jsx(TabsContent, { value: "legal", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); legalMutation.mutate(); }, className: "space-y-5 pt-4", children: [[
|
|
332
334
|
["Terms of Service", termsHtml, setTermsHtml],
|
|
333
335
|
["Privacy Policy", privacyHtml, setPrivacyHtml],
|
|
334
336
|
["Refund Policy", refundHtml, setRefundHtml],
|
|
@@ -1,21 +1,29 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
+
import { TrendingUp, ShoppingBag, Users, Package, Clock, Star } from "lucide-react";
|
|
4
|
+
import { Grid } from "../../../ui";
|
|
3
5
|
import { formatCurrency } from "../../../utils/number.formatter";
|
|
4
6
|
import { getDefaultCurrency } from "../../../core/baseline-resolver";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
return (_jsxs(
|
|
7
|
+
// Brand gradient stop colors (mirrors SiteLogo wordmark gradient)
|
|
8
|
+
const BRAND_FROM = "var(--appkit-color-primary-700, #1343de)";
|
|
9
|
+
const BRAND_MID = "var(--appkit-color-cobalt, #3570fc)";
|
|
10
|
+
const BRAND_TO = "var(--appkit-color-secondary-400, #84e122)";
|
|
11
|
+
function StatCard({ label, value, sub, icon: Icon, gradient, iconColor }) {
|
|
12
|
+
return (_jsxs("div", { className: "relative rounded-xl border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] overflow-hidden shadow-sm hover:shadow-md transition-shadow", children: [_jsx("div", { className: "absolute top-0 left-0 right-0 h-[3px]", style: { background: gradient }, "aria-hidden": "true" }), _jsxs("div", { className: "px-5 pb-5 pt-6 flex items-start justify-between gap-3", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("p", { className: "text-[11px] font-semibold uppercase tracking-widest text-[var(--appkit-color-text-muted)]", children: label }), _jsx("p", { className: "mt-2 text-2xl font-bold text-[var(--appkit-color-text)] tabular-nums leading-none", children: value }), sub && (_jsx("p", { className: "mt-1.5 text-xs text-[var(--appkit-color-text-muted)]", children: sub }))] }), _jsx("div", { className: "flex-shrink-0 w-10 h-10 rounded-lg flex items-center justify-center", style: { background: gradient, opacity: 0.92 }, children: _jsx(Icon, { className: "w-5 h-5 text-white drop-shadow-sm" }) })] })] }));
|
|
13
|
+
}
|
|
14
|
+
function SkeletonCard() {
|
|
15
|
+
return (_jsxs("div", { className: "relative rounded-xl border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] overflow-hidden p-5 animate-pulse", children: [_jsx("div", { className: "absolute top-0 left-0 right-0 h-[3px] bg-[var(--appkit-color-border)]" }), _jsxs("div", { className: "flex items-start justify-between gap-3 pt-1", children: [_jsxs("div", { className: "flex-1 space-y-2", children: [_jsx("div", { className: "h-2.5 w-20 rounded bg-[var(--appkit-color-border)]" }), _jsx("div", { className: "h-7 w-24 rounded bg-[var(--appkit-color-border)]" })] }), _jsx("div", { className: "w-10 h-10 rounded-lg bg-[var(--appkit-color-border)]" })] })] }));
|
|
14
16
|
}
|
|
15
17
|
export function DashboardStatsGrid({ stats, isLoading, labels = {}, }) {
|
|
16
18
|
if (isLoading) {
|
|
17
|
-
return (_jsx(Grid, { cols: "statTiles", children: Array.from({ length: 4 }).map((_, i) =>
|
|
19
|
+
return (_jsx(Grid, { cols: "statTiles", children: Array.from({ length: 4 }).map((_, i) => _jsx(SkeletonCard, {}, i)) }));
|
|
18
20
|
}
|
|
19
21
|
const currency = stats.currency ?? getDefaultCurrency();
|
|
20
|
-
|
|
22
|
+
// Brand gradient (left→right, matches logo wordmark)
|
|
23
|
+
const brandGrad = `linear-gradient(135deg, ${BRAND_FROM} 0%, ${BRAND_MID} 55%, ${BRAND_TO} 100%)`;
|
|
24
|
+
const blueGrad = `linear-gradient(135deg, ${BRAND_FROM} 0%, ${BRAND_MID} 100%)`;
|
|
25
|
+
const greenGrad = `linear-gradient(135deg, ${BRAND_MID} 0%, ${BRAND_TO} 100%)`;
|
|
26
|
+
const amberGrad = "linear-gradient(135deg, #f59e0b 0%, #f97316 100%)";
|
|
27
|
+
const roseGrad = "linear-gradient(135deg, #f43f5e 0%, #e11d48 100%)";
|
|
28
|
+
return (_jsxs(Grid, { cols: "statTiles", children: [_jsx(StatCard, { label: labels.totalOrders ?? "Total Orders", value: stats.totalOrders ?? 0, icon: ShoppingBag, gradient: brandGrad, iconColor: "#fff" }), _jsx(StatCard, { label: labels.totalRevenue ?? "Total Revenue", value: formatCurrency(stats.totalRevenue ?? 0, currency), icon: TrendingUp, gradient: greenGrad, iconColor: "#fff" }), _jsx(StatCard, { label: labels.totalUsers ?? "Total Users", value: stats.totalUsers ?? 0, icon: Users, gradient: blueGrad, iconColor: "#fff" }), _jsx(StatCard, { label: labels.totalProducts ?? "Total Products", value: stats.totalProducts ?? 0, icon: Package, gradient: brandGrad, iconColor: "#fff" }), stats.pendingOrders !== undefined && (_jsx(StatCard, { label: labels.pendingOrders ?? "Pending Orders", value: stats.pendingOrders, icon: Clock, gradient: amberGrad, iconColor: "#fff" })), stats.pendingReviews !== undefined && (_jsx(StatCard, { label: labels.pendingReviews ?? "Pending Reviews", value: stats.pendingReviews, icon: Star, gradient: roseGrad, iconColor: "#fff" }))] }));
|
|
21
29
|
}
|
|
@@ -13,9 +13,25 @@ const XAxis = dynamic(() => import("recharts").then((m) => m.XAxis), { ssr: fals
|
|
|
13
13
|
const YAxis = dynamic(() => import("recharts").then((m) => m.YAxis), { ssr: false });
|
|
14
14
|
const CartesianGrid = dynamic(() => import("recharts").then((m) => m.CartesianGrid), { ssr: false });
|
|
15
15
|
const Tooltip = dynamic(() => import("recharts").then((m) => m.Tooltip), { ssr: false });
|
|
16
|
+
// Shared chart card wrapper
|
|
17
|
+
function ChartCard({ title, children, className = "", }) {
|
|
18
|
+
return (_jsxs(Div, { className: `relative rounded-xl border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] overflow-hidden shadow-sm ${className}`, children: [_jsx("div", { className: "absolute top-0 left-0 right-0 h-[3px]", style: {
|
|
19
|
+
background: "linear-gradient(to right, var(--appkit-color-primary-700,#1343de) 0%, var(--appkit-color-cobalt,#3570fc) 55%, var(--appkit-color-secondary-400,#84e122) 100%)",
|
|
20
|
+
}, "aria-hidden": "true" }), _jsxs(Div, { className: "p-4 sm:p-6 pt-5 sm:pt-7", children: [title && (_jsx(Heading, { level: 3, className: "text-sm font-semibold text-[var(--appkit-color-text)] mb-4", children: title })), children] })] }));
|
|
21
|
+
}
|
|
16
22
|
export function AdminRevenueChart({ data, labels = {}, className = "", }) {
|
|
17
|
-
return (
|
|
23
|
+
return (_jsx(ChartCard, { title: labels.title, className: className, children: _jsx(ResponsiveContainer, { width: "100%", height: 280, children: _jsxs(AreaChart, { data: data, margin: { top: 4, right: 4, left: 0, bottom: 0 }, children: [_jsxs("defs", { children: [_jsxs("linearGradient", { id: "revenueGrad", x1: "0", y1: "0", x2: "1", y2: "0", children: [_jsx("stop", { offset: "0%", stopColor: "#1343de" }), _jsx("stop", { offset: "55%", stopColor: "#3570fc" }), _jsx("stop", { offset: "100%", stopColor: "#84e122" })] }), _jsxs("linearGradient", { id: "revenueAreaGrad", x1: "0", y1: "0", x2: "0", y2: "1", children: [_jsx("stop", { offset: "5%", stopColor: "#3570fc", stopOpacity: 0.25 }), _jsx("stop", { offset: "95%", stopColor: "#3570fc", stopOpacity: 0 })] })] }), _jsx(CartesianGrid, { strokeDasharray: "4 4", stroke: "var(--appkit-color-border)", vertical: false }), _jsx(XAxis, { dataKey: "month", tick: { fontSize: 11, fill: "var(--appkit-color-text-muted)" }, axisLine: false, tickLine: false }), _jsx(YAxis, { tick: { fontSize: 11, fill: "var(--appkit-color-text-muted)" }, axisLine: false, tickLine: false, width: 48 }), _jsx(Tooltip, { contentStyle: {
|
|
24
|
+
background: "var(--appkit-color-surface)",
|
|
25
|
+
border: "1px solid var(--appkit-color-border)",
|
|
26
|
+
borderRadius: "8px",
|
|
27
|
+
fontSize: 12,
|
|
28
|
+
}, cursor: { stroke: "var(--appkit-color-border)", strokeWidth: 1 } }), _jsx(Area, { type: "monotone", dataKey: "revenue", stroke: "url(#revenueGrad)", strokeWidth: 2.5, fillOpacity: 1, fill: "url(#revenueAreaGrad)", dot: false, activeDot: { r: 4, fill: "#3570fc" } })] }) }) }));
|
|
18
29
|
}
|
|
19
30
|
export function AdminOrdersChart({ data, labels = {}, className = "", }) {
|
|
20
|
-
return (
|
|
31
|
+
return (_jsx(ChartCard, { title: labels.title, className: className, children: _jsx(ResponsiveContainer, { width: "100%", height: 280, children: _jsxs(BarChart, { data: data, margin: { top: 4, right: 4, left: 0, bottom: 0 }, barSize: 20, children: [_jsx("defs", { children: _jsxs("linearGradient", { id: "ordersBarGrad", x1: "0", y1: "0", x2: "0", y2: "1", children: [_jsx("stop", { offset: "0%", stopColor: "#3570fc", stopOpacity: 0.9 }), _jsx("stop", { offset: "100%", stopColor: "#84e122", stopOpacity: 0.7 })] }) }), _jsx(CartesianGrid, { strokeDasharray: "4 4", stroke: "var(--appkit-color-border)", vertical: false }), _jsx(XAxis, { dataKey: "month", tick: { fontSize: 11, fill: "var(--appkit-color-text-muted)" }, axisLine: false, tickLine: false }), _jsx(YAxis, { tick: { fontSize: 11, fill: "var(--appkit-color-text-muted)" }, axisLine: false, tickLine: false, width: 40 }), _jsx(Tooltip, { contentStyle: {
|
|
32
|
+
background: "var(--appkit-color-surface)",
|
|
33
|
+
border: "1px solid var(--appkit-color-border)",
|
|
34
|
+
borderRadius: "8px",
|
|
35
|
+
fontSize: 12,
|
|
36
|
+
}, cursor: { fill: "var(--appkit-color-border)", opacity: 0.3 } }), _jsx(Bar, { dataKey: "orders", fill: "url(#ordersBarGrad)", radius: [4, 4, 0, 0] })] }) }) }));
|
|
21
37
|
}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
+
declare const GRADIENTS: Record<string, string>;
|
|
2
3
|
export interface AdminStatCardProps {
|
|
3
4
|
label: string;
|
|
4
5
|
value: string;
|
|
6
|
+
sub?: string;
|
|
7
|
+
/** Emoji or ReactNode icon */
|
|
5
8
|
icon?: React.ReactNode | string;
|
|
9
|
+
/** Named preset or raw CSS gradient string */
|
|
10
|
+
gradient?: keyof typeof GRADIENTS | string;
|
|
6
11
|
className?: string;
|
|
7
12
|
}
|
|
8
|
-
export declare function AdminStatCard({ label, value, icon, className, }: AdminStatCardProps): import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
export declare function AdminStatCard({ label, value, sub, icon, gradient, className, }: AdminStatCardProps): import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
export {};
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Div, Text, Heading } from "../../../../ui";
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
const BRAND_FROM = "var(--appkit-color-primary-700, #1343de)";
|
|
4
|
+
const BRAND_MID = "var(--appkit-color-cobalt, #3570fc)";
|
|
5
|
+
const BRAND_TO = "var(--appkit-color-secondary-400, #84e122)";
|
|
6
|
+
const GRADIENTS = {
|
|
7
|
+
brand: `linear-gradient(135deg, ${BRAND_FROM} 0%, ${BRAND_MID} 55%, ${BRAND_TO} 100%)`,
|
|
8
|
+
blue: `linear-gradient(135deg, ${BRAND_FROM} 0%, ${BRAND_MID} 100%)`,
|
|
9
|
+
green: `linear-gradient(135deg, ${BRAND_MID} 0%, ${BRAND_TO} 100%)`,
|
|
10
|
+
amber: "linear-gradient(135deg, #f59e0b 0%, #f97316 100%)",
|
|
11
|
+
rose: "linear-gradient(135deg, #f43f5e 0%, #e11d48 100%)",
|
|
12
|
+
};
|
|
13
|
+
export function AdminStatCard({ label, value, sub, icon, gradient = "brand", className = "", }) {
|
|
14
|
+
const resolvedGradient = GRADIENTS[gradient] ?? gradient;
|
|
15
|
+
return (_jsxs(Div, { className: `relative rounded-xl border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] overflow-hidden shadow-sm hover:shadow-md transition-shadow ${className}`, children: [_jsx("div", { className: "absolute top-0 left-0 right-0 h-[3px]", style: { background: resolvedGradient }, "aria-hidden": "true" }), _jsxs(Div, { className: "px-5 pb-5 pt-6 flex items-start justify-between gap-3", children: [_jsxs(Div, { className: "min-w-0 flex-1", children: [_jsx(Text, { className: "text-[11px] font-semibold uppercase tracking-widest text-[var(--appkit-color-text-muted)]", children: label }), _jsx(Heading, { level: 3, className: "mt-2 text-2xl font-bold tabular-nums leading-none text-[var(--appkit-color-text)]", children: value }), sub && (_jsx(Text, { className: "mt-1.5 text-xs text-[var(--appkit-color-text-muted)]", children: sub }))] }), icon && (_jsx(Div, { className: "flex-shrink-0 w-10 h-10 rounded-lg flex items-center justify-center text-xl", style: { background: resolvedGradient }, children: icon }))] })] }));
|
|
5
16
|
}
|
|
@@ -232,15 +232,27 @@ export interface SiteSettingsDocument {
|
|
|
232
232
|
codEnabled: boolean;
|
|
233
233
|
};
|
|
234
234
|
commissions: {
|
|
235
|
-
|
|
235
|
+
/** Our platform cut charged to the buyer as a % of order value (e.g. 5 = 5%). */
|
|
236
|
+
platformFeePercent: number;
|
|
237
|
+
/** GST applied on top of our platform fee (18%). buyer pays platformFee × (1 + gstPercent/100). */
|
|
238
|
+
gstPercent: number;
|
|
239
|
+
/** Per-transaction gateway minimum fee in rupees. Ensures total charge is never below this floor. 0 = no minimum. */
|
|
240
|
+
minimumTransactionFee: number;
|
|
241
|
+
/** Razorpay gateway cost % (absorbed by platform, not passed through separately). */
|
|
242
|
+
gatewayFeePercent: number;
|
|
236
243
|
codDepositPercent: number;
|
|
237
244
|
sellerShippingFixed: number;
|
|
238
245
|
platformShippingPercent: number;
|
|
239
246
|
platformShippingFixedMin: number;
|
|
240
|
-
processingFeePercent?: number;
|
|
241
|
-
gstPercent?: number;
|
|
242
|
-
gatewayFeePercent?: number;
|
|
243
247
|
autoPayoutWindowDays?: number;
|
|
248
|
+
/** Days before a settled order's funds are released for payout. */
|
|
249
|
+
payoutHoldDays?: number;
|
|
250
|
+
/** Minimum payout amount in rupees. */
|
|
251
|
+
minPayoutAmount?: number;
|
|
252
|
+
auctionListingFee?: number;
|
|
253
|
+
preOrderListingFee?: number;
|
|
254
|
+
featuredSlotFee?: number;
|
|
255
|
+
promotedSlotFee?: number;
|
|
244
256
|
};
|
|
245
257
|
socialLinks: {
|
|
246
258
|
facebook?: string;
|
|
@@ -280,6 +292,8 @@ export interface SiteSettingsDocument {
|
|
|
280
292
|
sellerRegistration: boolean;
|
|
281
293
|
preOrders: boolean;
|
|
282
294
|
seedPanel: boolean;
|
|
295
|
+
/** When true, admin users see a bypass button in checkout that skips OTP and payment. Server-enforced. */
|
|
296
|
+
adminCheckoutBypass?: boolean;
|
|
283
297
|
listingTypes?: {
|
|
284
298
|
standard?: boolean;
|
|
285
299
|
auction?: boolean;
|
|
@@ -136,11 +136,21 @@ export const DEFAULT_SITE_SETTINGS_DATA = {
|
|
|
136
136
|
codEnabled: true,
|
|
137
137
|
},
|
|
138
138
|
commissions: {
|
|
139
|
-
|
|
139
|
+
platformFeePercent: 5,
|
|
140
|
+
gstPercent: 18,
|
|
141
|
+
minimumTransactionFee: 0,
|
|
142
|
+
gatewayFeePercent: 2,
|
|
140
143
|
codDepositPercent: 10,
|
|
141
144
|
sellerShippingFixed: 0,
|
|
142
145
|
platformShippingPercent: 10,
|
|
143
146
|
platformShippingFixedMin: 0,
|
|
147
|
+
autoPayoutWindowDays: 7,
|
|
148
|
+
payoutHoldDays: 2,
|
|
149
|
+
minPayoutAmount: 100,
|
|
150
|
+
auctionListingFee: 0,
|
|
151
|
+
preOrderListingFee: 0,
|
|
152
|
+
featuredSlotFee: 999,
|
|
153
|
+
promotedSlotFee: 499,
|
|
144
154
|
},
|
|
145
155
|
featureFlags: {
|
|
146
156
|
chats: true,
|
|
@@ -4,5 +4,7 @@ export interface BundleBuyNowCtaProps {
|
|
|
4
4
|
onBuyNow: (input: {
|
|
5
5
|
bundleSlug: string;
|
|
6
6
|
}) => Promise<unknown>;
|
|
7
|
+
/** Renders a smaller single-line button suited for list cards. */
|
|
8
|
+
compact?: boolean;
|
|
7
9
|
}
|
|
8
|
-
export declare function BundleBuyNowCta({ bundleSlug, outOfStock, onBuyNow, }: BundleBuyNowCtaProps): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export declare function BundleBuyNowCta({ bundleSlug, outOfStock, onBuyNow, compact, }: BundleBuyNowCtaProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -12,7 +12,7 @@ import { Button, LoginRequiredModal, Stack, Text } from "../../../ui";
|
|
|
12
12
|
import { useToast } from "../../../ui";
|
|
13
13
|
import { isAuthError } from "../../../utils/auth-error";
|
|
14
14
|
import { BUNDLE_COPY } from "../../../_internal/shared/features/categories/bundle-copy";
|
|
15
|
-
export function BundleBuyNowCta({ bundleSlug, outOfStock = false, onBuyNow, }) {
|
|
15
|
+
export function BundleBuyNowCta({ bundleSlug, outOfStock = false, onBuyNow, compact = false, }) {
|
|
16
16
|
const { showToast } = useToast();
|
|
17
17
|
const [submitting, setSubmitting] = useState(false);
|
|
18
18
|
const [error, setError] = useState(null);
|
|
@@ -38,7 +38,7 @@ export function BundleBuyNowCta({ bundleSlug, outOfStock = false, onBuyNow, }) {
|
|
|
38
38
|
}
|
|
39
39
|
}, [bundleSlug, onBuyNow, showToast]);
|
|
40
40
|
if (outOfStock) {
|
|
41
|
-
return (_jsxs(Stack, { gap: "xs", "aria-live": "polite", children: [_jsx(Button, { variant: "primary", disabled: true, "aria-disabled": true, children: BUNDLE_COPY.detail.ctaOutOfStock }), _jsx(Text, { size: "xs", color: "muted", children: BUNDLE_COPY.detail.ctaHint })] }));
|
|
41
|
+
return (_jsxs(Stack, { gap: "xs", "aria-live": "polite", children: [_jsx(Button, { variant: "primary", size: compact ? "sm" : "md", disabled: true, "aria-disabled": true, className: compact ? "w-full" : undefined, children: BUNDLE_COPY.detail.ctaOutOfStock }), !compact && (_jsx(Text, { size: "xs", color: "muted", children: BUNDLE_COPY.detail.ctaHint }))] }));
|
|
42
42
|
}
|
|
43
|
-
return (_jsxs(_Fragment, { children: [_jsx(LoginRequiredModal, { isOpen: showLoginModal, onClose: () => setShowLoginModal(false), message: "You need to be signed in to purchase this bundle. Please log in or create an account to continue." }), _jsxs(Stack, { gap: "sm", children: [_jsx(Button, { variant: "primary", onClick: handleClick, disabled: submitting, "aria-busy": submitting, children: submitting ? BUNDLE_COPY.detail.ctaAdding : BUNDLE_COPY.detail.ctaBuyNow }), error && (_jsx(Text, { size: "sm", color: "danger", role: "alert", children: error }))] })] }));
|
|
43
|
+
return (_jsxs(_Fragment, { children: [_jsx(LoginRequiredModal, { isOpen: showLoginModal, onClose: () => setShowLoginModal(false), message: "You need to be signed in to purchase this bundle. Please log in or create an account to continue." }), _jsxs(Stack, { gap: "sm", children: [_jsx(Button, { variant: "primary", size: compact ? "sm" : "md", onClick: handleClick, disabled: submitting, "aria-busy": submitting, className: compact ? "w-full" : undefined, children: submitting ? BUNDLE_COPY.detail.ctaAdding : BUNDLE_COPY.detail.ctaBuyNow }), error && (_jsx(Text, { size: "sm", color: "danger", role: "alert", children: error }))] })] }));
|
|
44
44
|
}
|
|
@@ -4,7 +4,7 @@ type DynamicRule = {
|
|
|
4
4
|
categorySlug?: string;
|
|
5
5
|
brandSlug?: string;
|
|
6
6
|
tags?: string[];
|
|
7
|
-
listingType?: "standard" | "pre-order";
|
|
7
|
+
listingType?: "standard" | "pre-order" | "prize-draw";
|
|
8
8
|
};
|
|
9
9
|
orderBy?: "price-asc" | "price-desc" | "createdAt-desc";
|
|
10
10
|
limit: number;
|
|
@@ -29,9 +29,10 @@ const ORDER_BY_OPTIONS = [
|
|
|
29
29
|
{ label: "Price (high → low)", value: "price-desc" },
|
|
30
30
|
];
|
|
31
31
|
const LISTING_TYPE_OPTIONS = [
|
|
32
|
-
{ label: "Any
|
|
32
|
+
{ label: "Any", value: "" },
|
|
33
33
|
{ label: "Standard only", value: "standard" },
|
|
34
34
|
{ label: "Pre-order only", value: "pre-order" },
|
|
35
|
+
{ label: "Prize draw only", value: "prize-draw" },
|
|
35
36
|
];
|
|
36
37
|
export function BundleDynamicRuleEditor({ value, onChange, disabled = false, }) {
|
|
37
38
|
const rule = value ?? DEFAULT_DYNAMIC_RULE;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
type SearchParams = Record<string, string | string[]>;
|
|
2
2
|
export interface BundlesListViewProps {
|
|
3
3
|
searchParams?: SearchParams;
|
|
4
|
+
onBuyNow?: (input: {
|
|
5
|
+
bundleSlug: string;
|
|
6
|
+
}) => Promise<unknown>;
|
|
4
7
|
}
|
|
5
8
|
/**
|
|
6
9
|
* Public `/bundles` index. Bundles live as `categoryType:"bundle"` rows on
|
|
@@ -9,5 +12,5 @@ export interface BundlesListViewProps {
|
|
|
9
12
|
* Optional `?storeId=…` post-filters to a single seller's bundles.
|
|
10
13
|
* Sort UI is owned by `CategoryBundlesListing` (client).
|
|
11
14
|
*/
|
|
12
|
-
export declare function BundlesListView({ searchParams, }: BundlesListViewProps): Promise<import("react/jsx-runtime").JSX.Element>;
|
|
15
|
+
export declare function BundlesListView({ searchParams, onBuyNow, }: BundlesListViewProps): Promise<import("react/jsx-runtime").JSX.Element>;
|
|
13
16
|
export {};
|
|
@@ -15,7 +15,7 @@ function sp(params, key) {
|
|
|
15
15
|
* Optional `?storeId=…` post-filters to a single seller's bundles.
|
|
16
16
|
* Sort UI is owned by `CategoryBundlesListing` (client).
|
|
17
17
|
*/
|
|
18
|
-
export async function BundlesListView({ searchParams = {}, }) {
|
|
18
|
+
export async function BundlesListView({ searchParams = {}, onBuyNow, }) {
|
|
19
19
|
const storeId = sp(searchParams, "storeId");
|
|
20
20
|
const all = await categoriesRepository
|
|
21
21
|
.listByType("bundle", { activeOnly: true, limit: DEFAULT_LIMIT })
|
|
@@ -23,5 +23,5 @@ export async function BundlesListView({ searchParams = {}, }) {
|
|
|
23
23
|
const bundles = storeId
|
|
24
24
|
? all.filter((c) => c.createdByStoreId === storeId)
|
|
25
25
|
: all;
|
|
26
|
-
return (_jsx(Main, { children: _jsx(Section, { className: "py-10", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 1, className: "mb-2 text-3xl font-semibold text-zinc-900 dark:text-zinc-100", children: "Collectible Bundles" }), _jsx(Text, { className: "mb-6 text-sm text-[var(--appkit-color-text-muted)]", children: "Curated multi-product bundles \u2014 Pok\u00E9mon starter sets, Beyblade tournament packs, Hot Wheels collector boxes and more. One price, one checkout, one shipment." }), _jsx(AdSlot, { id: "listing-sidebar-top", className: "mb-6" }), _jsx(CategoryBundlesListing, { initialBundles: bundles }), _jsx(AdSlot, { id: "listing-sidebar-bottom", className: "mt-8" })] }) }) }));
|
|
26
|
+
return (_jsx(Main, { children: _jsx(Section, { className: "py-10", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 1, className: "mb-2 text-3xl font-semibold text-zinc-900 dark:text-zinc-100", children: "Collectible Bundles" }), _jsx(Text, { className: "mb-6 text-sm text-[var(--appkit-color-text-muted)]", children: "Curated multi-product bundles \u2014 Pok\u00E9mon starter sets, Beyblade tournament packs, Hot Wheels collector boxes and more. One price, one checkout, one shipment." }), _jsx(AdSlot, { id: "listing-sidebar-top", className: "mb-6" }), _jsx(CategoryBundlesListing, { initialBundles: bundles, onBuyNow: onBuyNow }), _jsx(AdSlot, { id: "listing-sidebar-bottom", className: "mt-8" })] }) }) }));
|
|
27
27
|
}
|
|
@@ -2,5 +2,8 @@ import type { CategoryDocument } from "../schemas";
|
|
|
2
2
|
export interface CategoryBundlesListingProps {
|
|
3
3
|
initialBundles: CategoryDocument[];
|
|
4
4
|
brandName?: string;
|
|
5
|
+
onBuyNow?: (input: {
|
|
6
|
+
bundleSlug: string;
|
|
7
|
+
}) => Promise<unknown>;
|
|
5
8
|
}
|
|
6
|
-
export declare function CategoryBundlesListing({ initialBundles, brandName, }: CategoryBundlesListingProps): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
export declare function CategoryBundlesListing({ initialBundles, brandName, onBuyNow, }: CategoryBundlesListingProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -12,6 +12,7 @@ import Link from "next/link";
|
|
|
12
12
|
import { Badge, Div, Row, Select, Stack, Text } from "../../../ui";
|
|
13
13
|
import { ROUTES } from "../../../next/routing/route-map";
|
|
14
14
|
import { formatCurrency } from "../../../utils/number.formatter";
|
|
15
|
+
import { BundleBuyNowCta } from "./BundleBuyNowCta";
|
|
15
16
|
const SORT_OPTIONS = [
|
|
16
17
|
{ label: "Newest", value: "newest" },
|
|
17
18
|
{ label: "Price ↑", value: "price-asc" },
|
|
@@ -47,7 +48,7 @@ function comparator(sort) {
|
|
|
47
48
|
return bT - aT;
|
|
48
49
|
};
|
|
49
50
|
}
|
|
50
|
-
export function CategoryBundlesListing({ initialBundles, brandName, }) {
|
|
51
|
+
export function CategoryBundlesListing({ initialBundles, brandName, onBuyNow, }) {
|
|
51
52
|
const [sort, setSort] = useState("newest");
|
|
52
53
|
const filtered = useMemo(() => initialBundles
|
|
53
54
|
.filter((c) => c.categoryType === "bundle")
|
|
@@ -56,17 +57,17 @@ export function CategoryBundlesListing({ initialBundles, brandName, }) {
|
|
|
56
57
|
if (filtered.length === 0) {
|
|
57
58
|
return (_jsx(Div, { className: "rounded-2xl border border-dashed border-zinc-200 py-16 text-center dark:border-zinc-700", children: _jsxs(Text, { color: "muted", children: [COPY.empty, brandName ? COPY.forBrand(brandName) : "", COPY.yetSuffix] }) }));
|
|
58
59
|
}
|
|
59
|
-
return (_jsxs(Stack, { gap: "md", children: [_jsxs(Row, { gap: "sm", align: "center", justify: "end", children: [_jsx(Text, { size: "sm", color: "muted", children: COPY.sortLabel }), _jsx(Select, { options: SORT_OPTIONS, value: sort, onValueChange: setSort, "aria-label": COPY.sortLabel })] }), _jsx(Div, { className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3", children: filtered.map((bundle) => (_jsx(BundleCard, { bundle: bundle }, bundle.id))) })] }));
|
|
60
|
+
return (_jsxs(Stack, { gap: "md", children: [_jsxs(Row, { gap: "sm", align: "center", justify: "end", children: [_jsx(Text, { size: "sm", color: "muted", children: COPY.sortLabel }), _jsx(Select, { options: SORT_OPTIONS, value: sort, onValueChange: setSort, "aria-label": COPY.sortLabel })] }), _jsx(Div, { className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3", children: filtered.map((bundle) => (_jsx(BundleCard, { bundle: bundle, onBuyNow: onBuyNow }, bundle.id))) })] }));
|
|
60
61
|
}
|
|
61
|
-
function BundleCard({ bundle }) {
|
|
62
|
+
function BundleCard({ bundle, onBuyNow }) {
|
|
62
63
|
const memberCount = bundle.bundleProductIds?.length ?? 0;
|
|
63
64
|
const stock = bundle.bundleStockStatus ?? "in_stock";
|
|
64
65
|
const badge = STOCK_BADGE_TEXT[stock];
|
|
65
66
|
const cover = bundle.display?.coverImage;
|
|
66
67
|
const href = String(ROUTES.PUBLIC.BUNDLE_DETAIL?.(bundle.slug) ?? "#");
|
|
67
|
-
return (_jsxs(
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
return (_jsxs(Stack, { gap: "none", className: "rounded-xl border border-zinc-200 bg-white dark:border-zinc-800 dark:bg-zinc-900", children: [_jsxs(Link, { href: href, className: "group block p-3 hover:no-underline", children: [_jsx(Div, { className: "mb-2 aspect-video overflow-hidden rounded-lg bg-zinc-100 dark:bg-zinc-800", children: cover ? (
|
|
69
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
70
|
+
_jsx("img", { src: cover, alt: bundle.name, className: "h-full w-full object-cover transition-transform group-hover:scale-105", loading: "lazy" })) : (_jsx(Div, { className: "flex h-full w-full items-center justify-center text-3xl", children: PLACEHOLDER_EMOJI })) }), _jsx(Text, { className: "line-clamp-2 text-sm font-semibold", children: bundle.name }), _jsxs(Row, { gap: "sm", align: "center", className: "mt-1", children: [_jsx(Text, { size: "sm", weight: "bold", children: bundle.bundlePriceInPaise
|
|
71
|
+
? formatCurrency(bundle.bundlePriceInPaise / 100, "INR")
|
|
72
|
+
: COPY.priceFallback }), _jsx(Text, { size: "xs", color: "muted", children: COPY.items(memberCount) })] }), badge && (_jsx(Badge, { variant: STOCK_BADGE_VARIANT[stock], className: "mt-1", children: badge }))] }), onBuyNow && (_jsx(Div, { className: "border-t border-zinc-100 p-3 pt-2 dark:border-zinc-800", children: _jsx(BundleBuyNowCta, { bundleSlug: bundle.slug, outOfStock: stock === "out_of_stock", onBuyNow: onBuyNow, compact: true }) }))] }));
|
|
72
73
|
}
|
|
@@ -27,11 +27,24 @@ export type BundleQueryRule = {
|
|
|
27
27
|
categorySlug?: string;
|
|
28
28
|
brandSlug?: string;
|
|
29
29
|
tags?: string[];
|
|
30
|
-
|
|
30
|
+
/** Eligible listing types that may appear in a dynamic bundle. */
|
|
31
|
+
listingType?: "standard" | "pre-order" | "prize-draw";
|
|
31
32
|
};
|
|
32
33
|
orderBy?: "price-asc" | "price-desc" | "createdAt-desc";
|
|
33
34
|
limit: number;
|
|
34
35
|
};
|
|
36
|
+
/**
|
|
37
|
+
* Per-member metadata stored alongside `bundleProductIds`.
|
|
38
|
+
* `drawCount` — only meaningful when the member product has
|
|
39
|
+
* `listingType === "prize-draw"`. Represents how many draw entries
|
|
40
|
+
* the buyer receives for that product when they purchase the bundle
|
|
41
|
+
* (e.g. drawCount=5 means 5 raffle entries, not 5 copies of the draw).
|
|
42
|
+
*/
|
|
43
|
+
export interface BundleItemDetail {
|
|
44
|
+
productId: string;
|
|
45
|
+
/** Number of draw entries included when product is a prize-draw. */
|
|
46
|
+
drawCount?: number;
|
|
47
|
+
}
|
|
35
48
|
/** Full Firestore category document (includes server-only fields) */
|
|
36
49
|
export interface CategoryDocumentSEO {
|
|
37
50
|
title: string;
|
|
@@ -100,6 +113,14 @@ export interface CategoryDocument {
|
|
|
100
113
|
brandFounded?: number;
|
|
101
114
|
/** Banner image for the brand storefront page — categoryType==="brand". */
|
|
102
115
|
brandBannerImage?: string;
|
|
116
|
+
/**
|
|
117
|
+
* "special" — admin-curated bundle; writes reverse `partOfBundleIds`
|
|
118
|
+
* pointers on member products and shows the "Bundled" badge on cards.
|
|
119
|
+
*
|
|
120
|
+
* "brand" — auto-generated brand collection (dynamic brandSlug query).
|
|
121
|
+
* Does NOT update `partOfBundleIds`; used as a discovery surface only.
|
|
122
|
+
*/
|
|
123
|
+
bundleKind?: "special" | "brand";
|
|
103
124
|
/** Discounted bundle price in paise. */
|
|
104
125
|
bundlePriceInPaise?: number;
|
|
105
126
|
/** Rule resolving the bundle's member products — static list or live query. */
|
|
@@ -110,6 +131,13 @@ export interface CategoryDocument {
|
|
|
110
131
|
bundleQueryResolvedAt?: Date;
|
|
111
132
|
/** Hand-picked products list (mirror of bundleQueryRule for static rules); kept for index-friendly queries. */
|
|
112
133
|
bundleProductIds?: string[];
|
|
134
|
+
/**
|
|
135
|
+
* Per-member metadata parallel to `bundleProductIds`.
|
|
136
|
+
* Carries `drawCount` for prize-draw members (how many raffle entries
|
|
137
|
+
* the buyer receives). Flat `bundleProductIds` is kept for Firestore
|
|
138
|
+
* array-contains queries; this array holds the richer shape.
|
|
139
|
+
*/
|
|
140
|
+
bundleItemDetails?: BundleItemDetail[];
|
|
113
141
|
seo: CategoryDocumentSEO;
|
|
114
142
|
display: CategoryDocumentDisplay;
|
|
115
143
|
isActive: boolean;
|
|
@@ -17,4 +17,15 @@ export declare function verifyCheckoutConsentOtp(userId: string, addressId: stri
|
|
|
17
17
|
* registered phone number.
|
|
18
18
|
*/
|
|
19
19
|
export declare function grantCheckoutConsentViaSms(userId: string, userPhone: string | undefined, addressId: string): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Grants checkout consent for an admin test order, bypassing the normal OTP flow.
|
|
22
|
+
*
|
|
23
|
+
* The resulting Firestore doc is identical to a verified consent OTP so that
|
|
24
|
+
* `createCheckoutOrderAction` can proceed without modification. The
|
|
25
|
+
* `verifiedVia: "admin_bypass"` marker prevents bypass-credit grants.
|
|
26
|
+
*
|
|
27
|
+
* Security: callers MUST verify the requesting user is an admin AND that
|
|
28
|
+
* `siteSettings.featureFlags.adminCheckoutBypass === true` before calling this.
|
|
29
|
+
*/
|
|
30
|
+
export declare function grantAdminCheckoutBypass(userId: string, addressId: string, bypassingAdminUid: string): Promise<void>;
|
|
20
31
|
export { userRepository };
|