@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
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { ShoppingBag, Hash, Scale, Wallet, RotateCcw, DollarSign, Gavel } from "lucide-react";
|
|
3
|
+
import { Div, Heading, Text, Section } from "../../../ui";
|
|
4
|
+
import { GC } from "../../_guide-cls";
|
|
5
|
+
export function AdminOrdersGuideView() {
|
|
6
|
+
return (_jsxs(Div, { className: "space-y-8 pb-10 max-w-3xl mx-auto", children: [_jsxs(Section, { children: [_jsxs(Div, { className: "flex items-center gap-3 mb-2", children: [_jsx(Div, { className: "flex-shrink-0 w-10 h-10 rounded-xl flex items-center justify-center", style: { background: "linear-gradient(135deg,var(--appkit-color-primary-700,#4f46e5) 0%,var(--appkit-color-cobalt,#2563eb) 100%)" }, children: _jsx(ShoppingBag, { className: "w-5 h-5 text-white" }) }), _jsx(Text, { className: "text-sm font-semibold text-[var(--appkit-color-text-muted)] uppercase tracking-widest", children: "Admin Guide" })] }), _jsx(Heading, { level: 1, className: "text-2xl md:text-3xl font-bold text-[var(--appkit-color-text)] mb-2", children: "Orders & Finance" }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", children: "Order statuses, payouts, disputes, returns, and commission math on LetItRip." })] }), [
|
|
7
|
+
{
|
|
8
|
+
Icon: ShoppingBag, title: "Order Statuses",
|
|
9
|
+
content: (_jsxs(Div, { className: "space-y-2 text-sm", children: [[
|
|
10
|
+
["PENDING", "Payment received. Can still be cancelled by buyer or admin."],
|
|
11
|
+
["PROCESSING", "Seller has acknowledged and is packing."],
|
|
12
|
+
["SHIPPED", "Dispatched. Tracking number provided by seller."],
|
|
13
|
+
["DELIVERED", "Carrier confirmed delivery."],
|
|
14
|
+
["RETURN_REQUESTED", "Buyer opened a return. Seller has 3 days to respond."],
|
|
15
|
+
["REFUNDED", "Return accepted and refund issued via Razorpay."],
|
|
16
|
+
["CANCELLED", "Cancelled before shipping. Payment returned."],
|
|
17
|
+
].map(([status, note]) => (_jsxs(Div, { className: "flex gap-3", children: [_jsx(Text, { className: "flex-shrink-0 font-mono font-semibold text-[var(--appkit-color-primary)] w-40 text-xs", children: status }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", children: note })] }, status))), _jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)] pt-2", children: "Admin-only transitions: force-cancel (any status before DELIVERED), force-refund (post-DELIVERED dispute)." })] })),
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
Icon: Hash, title: "Order ID Format",
|
|
21
|
+
content: (_jsxs(_Fragment, { children: [_jsxs(Text, { className: "text-sm text-[var(--appkit-color-text-muted)] mb-3", children: ["Format: ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 py-0.5 rounded", children: "order-{itemCount}-{YYYYMMDD}-{rand6}" })] }), _jsxs(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: ["The item count in the ID enables quick triage \u2014 a ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "order-8-..." }), " is a large multi-item order that needs more careful handling than a ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "order-1-..." }), ". Search by order ID in the admin panel using the full ID string."] })] })),
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
Icon: Scale, title: "Intervening in a Dispute",
|
|
25
|
+
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsx("li", { children: "Admin steps in when: seller is unresponsive for 3+ days on a return, or buyer escalates via support ticket." }), _jsxs("li", { children: ["Admin dispute tools: ", _jsx("strong", { children: "force-accept return" }), " (moves to REFUNDED and withholds seller payout), ", _jsx("strong", { children: "platform credit" }), " (future feature)."] }), _jsx("li", { children: "Document your reasoning in the support ticket before taking any force action." })] })),
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
Icon: Wallet, title: "Payouts",
|
|
29
|
+
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Payout cycle" }), ": Weekly. All DELIVERED orders (minus platform commission) are batched into a single payout per store."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Status flow" }), ": PENDING \u2192 PROCESSING \u2192 PAID / FAILED."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "FAILED payout" }), ": Check the store's ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "payoutDetails" }), " \u2014 UPI VPA or bank details may be stale. Contact the seller via their store email before retrying."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "ordersIncluded" }), ": Array of order IDs bundled in this payout batch \u2014 use to audit which orders are covered."] })] })),
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
Icon: RotateCcw, title: "Return Requests",
|
|
33
|
+
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsx("li", { children: "Admin can view the return reason and attached photos on the order detail page." }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "When to override a seller rejection" }), ": Only when photographic evidence clearly supports the buyer's claim of a significant not-as-described defect. Do not override for buyer's remorse."] }), _jsx("li", { children: "After admin override: refund is issued automatically via Razorpay (3\u20137 business days for card, 1\u20132 for UPI). Seller payout for that order is withheld." })] })),
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
Icon: DollarSign, title: "Commission & Settlement Math",
|
|
37
|
+
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "platformFee" }), ": Set in ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "siteSettings.fees.platformFee" }), " (percentage). Applied to the order's item subtotal."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "customCommissionRate" }), ": Stored on ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "StoreDocument" }), " for stores with the ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "lower_commission_rate" }), " capability. Overrides the platform default for that store."] }), _jsx("li", { children: "Payout = order subtotal \u00D7 (1 \u2212 commission rate). Shipping fees are passed through to the seller at cost." })] })),
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
Icon: Gavel, title: "Auction Settlement",
|
|
41
|
+
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsx("li", { children: "When an auction ends and the winner does not pay within 48 hours: the order auto-cancels and the system notifies the next-highest bidder." }), _jsx("li", { children: "If the auto-cancel system fails (rare), admin can manually cancel from the order detail page and trigger the next-bidder flow via a support action." }), _jsxs("li", { children: ["Repeat non-paying winners can receive a ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "place_bids" }), " soft ban from the Trust & Safety section."] })] })),
|
|
42
|
+
},
|
|
43
|
+
].map(({ Icon, title, content }) => (_jsxs(Section, { className: "rounded-2xl border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] overflow-hidden", children: [_jsxs(Div, { className: "flex items-center gap-3 px-6 py-4 border-b border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface-2,var(--appkit-color-border))]/20", children: [_jsx(Icon, { className: "w-5 h-5 text-[var(--appkit-color-primary)]" }), _jsx(Heading, { level: 2, className: "text-base font-semibold text-[var(--appkit-color-text)]", children: title })] }), _jsx(Div, { className: "px-6 py-5", children: content })] }, title)))] }));
|
|
44
|
+
}
|
|
@@ -86,7 +86,7 @@ export function AdminProductsView({ children, actionHref, getRowHref, ...props }
|
|
|
86
86
|
setPendingFilters(Object.fromEntries(FILTER_KEYS.map((k) => [k, ""])));
|
|
87
87
|
}, []);
|
|
88
88
|
const resetAll = useCallback(() => {
|
|
89
|
-
const updates = { q: "", sort: "" };
|
|
89
|
+
const updates = { q: "", sort: "", showSold: "" };
|
|
90
90
|
for (const k of FILTER_KEYS)
|
|
91
91
|
updates[k] = "";
|
|
92
92
|
table.setMany(updates);
|
|
@@ -95,9 +95,12 @@ export function AdminProductsView({ children, actionHref, getRowHref, ...props }
|
|
|
95
95
|
const commitSearch = useCallback(() => {
|
|
96
96
|
table.set("q", searchInput.trim());
|
|
97
97
|
}, [searchInput, table]);
|
|
98
|
+
const showSold = table.get("showSold") === "true";
|
|
98
99
|
const activeFilterCount = FILTER_KEYS.filter((k) => !!table.get(k)).length;
|
|
99
|
-
const hasActiveState = !!table.get("q") || table.get("sort") !== DEFAULT_SORT || activeFilterCount > 0;
|
|
100
|
+
const hasActiveState = !!table.get("q") || table.get("sort") !== DEFAULT_SORT || activeFilterCount > 0 || showSold;
|
|
100
101
|
const filterParts = [];
|
|
102
|
+
if (!showSold)
|
|
103
|
+
filterParts.push("isSold==false");
|
|
101
104
|
const statusRaw = table.get("status");
|
|
102
105
|
if (statusRaw && statusRaw !== "All")
|
|
103
106
|
filterParts.push(`status==${statusRaw}`);
|
|
@@ -176,7 +179,9 @@ export function AdminProductsView({ children, actionHref, getRowHref, ...props }
|
|
|
176
179
|
className: "w-56",
|
|
177
180
|
render: (row) => (_jsx("div", { className: "flex flex-wrap gap-x-3 gap-y-1", onClick: (e) => e.stopPropagation(), onKeyDown: (e) => e.stopPropagation(), role: "presentation", children: FLAG_DEFS.map(({ key, label }) => (_jsx(Toggle, { size: "sm", label: label, checked: !!row[key], onChange: (v) => { void handleToggle(row.id, key, v); } }, key))) })),
|
|
178
181
|
};
|
|
179
|
-
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(Heading, { level: 1, className: "sr-only", children: "Products" }), _jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search products, SKUs, or seller names", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState,
|
|
182
|
+
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(Heading, { level: 1, className: "sr-only", children: "Products" }), _jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search products, SKUs, or seller names", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState, toggles: [
|
|
183
|
+
{ label: "Show sold", active: showSold, onChange: (next) => table.set("showSold", next ? "true" : "") },
|
|
184
|
+
], extra: _jsxs(Button, { size: "sm", onClick: openCreatePanel, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), "Add Product"] }) }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
|
|
180
185
|
{ id: "feature", label: "Toggle Featured", variant: "secondary", onClick: () => { for (const id of selection.selectedIds)
|
|
181
186
|
void handleToggle(id, "featured", !rows.find(r => r.id === id)?.featured); selection.clearSelection(); } },
|
|
182
187
|
{ id: "promote", label: "Toggle Promoted", variant: "secondary", onClick: () => { for (const id of selection.selectedIds)
|
|
@@ -5,6 +5,7 @@ import { createPortal } from "react-dom";
|
|
|
5
5
|
import Link from "next/link";
|
|
6
6
|
import { Nav } from "../../../ui";
|
|
7
7
|
import { BottomSheet } from "../../layout/BottomSheet";
|
|
8
|
+
import { SidebarCollapseToggle } from "../../../_internal/client/features/layout/SidebarCollapseToggle";
|
|
8
9
|
function isNavItemActive(item, activePath) {
|
|
9
10
|
return activePath === item.href || activePath.startsWith(item.href + "/");
|
|
10
11
|
}
|
|
@@ -47,7 +48,7 @@ export function AdminSidebar({ renderNavItems, renderHeader, renderFooter, activ
|
|
|
47
48
|
height: "calc(100vh - var(--header-height, 3.5rem))",
|
|
48
49
|
width: "18rem",
|
|
49
50
|
transform: desktopOpen ? "translateX(0)" : "translateX(calc(-100% + 1.25rem))",
|
|
50
|
-
}, children: [_jsxs("div", { className: "flex-1 bg-white dark:bg-slate-950 border-r border-zinc-200 dark:border-slate-800 flex flex-col overflow-hidden shadow-xl", children: [_jsx("div", { className: "px-4 py-3.5 border-b border-zinc-100 dark:border-slate-800 shrink-0", children: renderHeader ? renderHeader() : _jsx("span", { className: "text-xs font-semibold uppercase tracking-widest text-zinc-400 dark:text-zinc-500", children: "Admin Panel" }) }), _jsx("div", { className: "flex-1 overflow-y-auto", children: navContent }), renderFooter && _jsx("div", { className: "px-4 py-3 border-t border-zinc-200 dark:border-slate-800", children: renderFooter() })] }), _jsx(
|
|
51
|
+
}, children: [_jsxs("div", { className: "flex-1 bg-white dark:bg-slate-950 border-r border-zinc-200 dark:border-slate-800 flex flex-col overflow-hidden shadow-xl", children: [_jsx("div", { className: "px-4 py-3.5 border-b border-zinc-100 dark:border-slate-800 shrink-0", children: renderHeader ? renderHeader() : _jsx("span", { className: "text-xs font-semibold uppercase tracking-widest text-zinc-400 dark:text-zinc-500", children: "Admin Panel" }) }), _jsx("div", { className: "flex-1 overflow-y-auto", children: navContent }), renderFooter && _jsx("div", { className: "px-4 py-3 border-t border-zinc-200 dark:border-slate-800", children: renderFooter() })] }), _jsx(SidebarCollapseToggle, { expanded: desktopOpen, onToggle: handleToggle })] }), _jsx("div", { className: "md:hidden", children: _jsx(BottomSheet, { open: mobileOpen, onClose: close, title: "Admin Panel", children: mobileNavContent }) })] }));
|
|
51
52
|
}
|
|
52
53
|
return (_jsxs(_Fragment, { children: [mounted && mobileOpen &&
|
|
53
54
|
createPortal(_jsx(DrawerPanel, { title: "Admin Panel", onClose: close, children: navContent }), document.body), _jsx("div", { className: "md:hidden", children: _jsx(BottomSheet, { open: mobileOpen, onClose: close, title: "Admin Panel", children: mobileNavContent }) })] }));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function AdminSiteConfigGuideView(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Settings } from "lucide-react";
|
|
3
|
+
import { Div, Heading, Text, Section, Alert } from "../../../ui";
|
|
4
|
+
import { GC } from "../../_guide-cls";
|
|
5
|
+
export function AdminSiteConfigGuideView() {
|
|
6
|
+
const SETTING_GROUPS = [
|
|
7
|
+
{ name: "branding", desc: "Site name, tagline, logo URL, favicon. These values override the default SVG wordmark." },
|
|
8
|
+
{ name: "appearance", desc: "Theme colour tokens, dark mode default, custom CSS overrides." },
|
|
9
|
+
{ name: "announcementBanner", desc: "Sitewide banner — message, type (info/warning/promo), start/end dates, dismissible toggle." },
|
|
10
|
+
{ name: "seoDefaults", desc: "Default meta title suffix, meta description, og:image, and robots directives for pages without explicit metadata." },
|
|
11
|
+
{ name: "contactSocial", desc: "Email, phone, social media URLs, WhatsApp business number." },
|
|
12
|
+
{ name: "watermark", desc: "Watermark text and opacity applied to all product images via the /api/media proxy." },
|
|
13
|
+
{ name: "fees", desc: "platformFee (%), Razorpay gateway fee pass-through toggle, GST rate on platform fees." },
|
|
14
|
+
{ name: "integrations", desc: "API keys: Razorpay (live/test), Shiprocket, Resend, WhatsApp Cloud API. Keys are encrypted at rest — never log or export." },
|
|
15
|
+
{ name: "shipping", desc: "Default shipping carriers, free shipping threshold (paise), COD availability toggle." },
|
|
16
|
+
{ name: "auctionConfig", desc: "Global bid increment default, auto-extend window (minutes), minimum auction duration." },
|
|
17
|
+
{ name: "platformLimits", desc: "Cart item cap (50), wishlist cap (20), history cap (50), support ticket limits (2 general + 1 per order)." },
|
|
18
|
+
{ name: "legalPages", desc: "Body HTML for Terms of Service, Privacy Policy, Refund Policy, and Shipping Policy. Rendered on their respective static pages." },
|
|
19
|
+
];
|
|
20
|
+
return (_jsxs(Div, { className: "space-y-8 pb-10 max-w-3xl mx-auto", children: [_jsxs(Section, { children: [_jsxs(Div, { className: "flex items-center gap-3 mb-2", children: [_jsx(Div, { className: "flex-shrink-0 w-10 h-10 rounded-xl flex items-center justify-center", style: { background: "linear-gradient(135deg,var(--appkit-color-primary-700,#4f46e5) 0%,var(--appkit-color-cobalt,#2563eb) 100%)" }, children: _jsx(Settings, { className: "w-5 h-5 text-white" }) }), _jsx(Text, { className: "text-sm font-semibold text-[var(--appkit-color-text-muted)] uppercase tracking-widest", children: "Admin Guide" })] }), _jsx(Heading, { level: 1, className: "text-2xl md:text-3xl font-bold text-[var(--appkit-color-text)] mb-2", children: "Site Configuration" }), _jsxs(Text, { className: "text-[var(--appkit-color-text-muted)]", children: ["All site settings are stored in a single Firestore document at ", _jsx("code", { className: "text-xs", children: "site_settings/global" }), ". Changes take effect on the next page render (ISR or cache revalidation)."] })] }), _jsxs(Section, { className: GC.sectionWrap, children: [_jsxs(Div, { className: GC.sectionHeader, children: [_jsx(Settings, { className: "w-5 h-5 text-[var(--appkit-color-primary)]" }), _jsx(Heading, { level: 2, className: GC.sectionTitle, children: "Settings Groups" })] }), _jsx(Div, { className: "px-6 py-5 space-y-4", children: SETTING_GROUPS.map(({ name, desc }) => (_jsxs(Div, { className: "flex gap-3 text-sm", children: [_jsx(Text, { className: "flex-shrink-0 font-mono font-semibold text-[var(--appkit-color-primary)] w-40 text-xs", children: name }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", children: desc })] }, name))) })] }), _jsxs(Section, { className: GC.sectionWrap, children: [_jsxs(Div, { className: GC.sectionHeader, children: [_jsx(Settings, { className: "w-5 h-5 text-amber-500" }), _jsx(Heading, { level: 2, className: GC.sectionTitle, children: "API Keys & Integrations" })] }), _jsx(Div, { className: "px-6 py-5", children: _jsxs(Alert, { variant: "warning", children: ["API keys in ", _jsx("code", { className: "text-xs", children: "integrations" }), " are stored encrypted. They are masked in the admin UI \u2014 you see ", _jsx("code", { className: "text-xs", children: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" }), " by design. To rotate a key: paste the new value into the field and save. The old key is immediately invalidated on your payment/shipping provider's side \u2014 test in staging first. Never paste API keys into Slack, email, or any external service."] }) })] }), _jsxs(Section, { className: GC.sectionWrap, children: [_jsxs(Div, { className: GC.sectionHeader, children: [_jsx(Settings, { className: "w-5 h-5 text-[var(--appkit-color-primary)]" }), _jsx(Heading, { level: 2, className: GC.sectionTitle, children: "Feature Flags" })] }), _jsxs(Div, { className: "px-6 py-5 space-y-3", children: [_jsxs(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: ["Feature flags are boolean toggles under ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "siteSettings" }), ". They control platform-wide features without a code deploy:"] }), _jsxs("ul", { className: "list-disc list-inside space-y-1 text-sm text-[var(--appkit-color-text-muted)]", children: [_jsxs("li", { children: [_jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "enableAuctions" }), " \u2014 globally enable/disable all auction listings."] }), _jsxs("li", { children: [_jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "enablePreOrders" }), " \u2014 globally enable/disable pre-order creation."] }), _jsxs("li", { children: [_jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "maintenanceMode" }), " \u2014 shows a maintenance banner and blocks non-admin access."] })] }), _jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: "Flags are evaluated per-request via ISR cache \u2014 a change may take up to the revalidation interval (3600s default) to propagate to all pages." })] })] })] }));
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function AdminStoresGuideView(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Store, GitBranch, Edit, Shield, MapPin, Ban } from "lucide-react";
|
|
3
|
+
import { Div, Heading, Text, Section, Alert } from "../../../ui";
|
|
4
|
+
import { GC } from "../../_guide-cls";
|
|
5
|
+
export function AdminStoresGuideView() {
|
|
6
|
+
return (_jsxs(Div, { className: "space-y-8 pb-10 max-w-3xl mx-auto", children: [_jsxs(Section, { children: [_jsxs(Div, { className: "flex items-center gap-3 mb-2", children: [_jsx(Div, { className: "flex-shrink-0 w-10 h-10 rounded-xl flex items-center justify-center", style: { background: "linear-gradient(135deg,var(--appkit-color-primary-700,#4f46e5) 0%,var(--appkit-color-cobalt,#2563eb) 100%)" }, children: _jsx(Store, { className: "w-5 h-5 text-white" }) }), _jsx(Text, { className: "text-sm font-semibold text-[var(--appkit-color-text-muted)] uppercase tracking-widest", children: "Admin Guide" })] }), _jsx(Heading, { level: 1, className: "text-2xl md:text-3xl font-bold text-[var(--appkit-color-text)] mb-2", children: "Stores & Sellers" }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", children: "Store lifecycle, identity architecture, capabilities, and suspension on LetItRip." })] }), [
|
|
7
|
+
{
|
|
8
|
+
Icon: Store, title: "Store Lifecycle",
|
|
9
|
+
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Creation" }), ": Seller registers \u2192 completes onboarding wizard \u2192 admin reviews \u2192 store goes ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "active" }), "."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Status values" }), ": ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "active" }), " (visible and selling), ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "suspended" }), " (hidden, not selling), ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "pending_review" }), " (awaiting admin approval)."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "isVerified badge" }), ": Awarded when the store has a consistent track record (\u226520 delivered orders, \u22654.0 rating, 3+ months active). Click the toggle in AdminStoreEditorView after reviewing the store's history."] })] })),
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
Icon: GitBranch, title: "Store Identity Architecture",
|
|
13
|
+
content: (_jsxs(_Fragment, { children: [_jsxs(Alert, { variant: "info", title: "Critical rule \u2014 read before editing any store or order.", children: [_jsx("code", { className: "text-xs", children: "storeId = storeSlug" }), " (public-facing, used in all product and order references).", _jsx("br", {}), _jsx("code", { className: "text-xs", children: "ownerId = Firebase Auth UID" }), " (internal only \u2014 never exposed in API responses)."] }), _jsxs("ul", { className: "space-y-2 text-sm text-[var(--appkit-color-text-muted)] mt-4", children: [_jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Why separate?" }), " Allows future store ownership transfer without rewriting all product/order documents."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Two-step lookup" }), ": To find a store by owner, query ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "stores where ownerId == uid" }), ". Never join products by ownerId directly."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Anti-patterns to reject" }), ": PRs that filter ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "products where ownerId ==" }), " \u2014 the correct field is ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "storeId" }), "."] })] })] })),
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
Icon: Edit, title: "Store Editor Walkthrough",
|
|
17
|
+
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "storeName / storeDescription" }), ": Public-facing. Shown on the store profile page."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "ownerId" }), ": Read-only after store creation. Changing ownership requires engineering involvement."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "status Select" }), ": Suspend a store with ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "suspended" }), "; this hides all listings and shows a banner on the store profile."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "payoutDetails" }), ": UPI VPA and bank account details. Treated as PII \u2014 masked in the UI and encrypted in Firestore. Always verify details with the seller before issuing payouts."] })] })),
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
Icon: Shield, title: "Capabilities Management",
|
|
21
|
+
content: (_jsxs(_Fragment, { children: [_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)] mb-3", children: "Capabilities are admin-granted feature flags on a store. The Capabilities section in AdminStoreEditorView shows all 18 flags with toggles." }), _jsxs(Alert, { variant: "warning", children: ["These capabilities require documented contractual approval before granting: ", _jsx("strong", { children: "lower_commission_rate" }), " (commission rate change agreement), ", _jsx("strong", { children: "extended_return_window" }), " (operational review), ", _jsx("strong", { children: "api_access" }), " (technical integration agreement). Do not toggle these without a senior admin sign-off on file."] })] })),
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
Icon: MapPin, title: "Store Addresses (Pickup Locations)",
|
|
25
|
+
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: ["Stored in the ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "storeAddresses" }), " collection. ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "storeId" }), " must exactly match the store's slug (e.g. ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "store-pokemon-palace" }), ")."] }), _jsxs("li", { children: [_jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "isPickupLocation: true" }), " flags this address as a buyer pickup option during checkout."] }), _jsx("li", { children: "Convention: no more than 3 active pickup locations per store." })] })),
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
Icon: Ban, title: "Suspending a Store",
|
|
29
|
+
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "status: \"suspended\"" }), " \u2014 hides all the store's products from search; the store profile shows \"Currently Unavailable\"; the seller receives an in-app notification."] }), _jsx("li", { children: "Suspension does NOT cancel pending orders \u2014 those must be handled separately." }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Hard-banning the owner" }), " is a separate, more severe action \u2014 see the Trust & Safety guide. A hard ban cascades to suspend the store automatically."] })] })),
|
|
30
|
+
},
|
|
31
|
+
].map(({ Icon, title, content }) => (_jsxs(Section, { className: "rounded-2xl border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] overflow-hidden", children: [_jsxs(Div, { className: "flex items-center gap-3 px-6 py-4 border-b border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface-2,var(--appkit-color-border))]/20", children: [_jsx(Icon, { className: "w-5 h-5 text-[var(--appkit-color-primary)]" }), _jsx(Heading, { level: 2, className: "text-base font-semibold text-[var(--appkit-color-text)]", children: title })] }), _jsx(Div, { className: "px-6 py-5", children: content })] }, title)))] }));
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function AdminTeamGuideView(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Users, Shield } from "lucide-react";
|
|
3
|
+
import { Div, Heading, Text, Section, Alert } from "../../../ui";
|
|
4
|
+
export function AdminTeamGuideView() {
|
|
5
|
+
const PERMISSION_GROUPS = [
|
|
6
|
+
{ name: "content_manager", desc: "Blog, FAQs, Events, Carousel, Sections, Ads, Newsletter read + write." },
|
|
7
|
+
{ name: "catalog_manager", desc: "Products, Categories, Brands, Reviews read + write." },
|
|
8
|
+
{ name: "store_manager", desc: "Stores, Store Addresses, Capabilities read + write." },
|
|
9
|
+
{ name: "order_manager", desc: "Orders, Returns, Payouts read + write + dispute tools." },
|
|
10
|
+
{ name: "trust_safety", desc: "Scam Registry, Support Tickets, Moderation, Reports, Bans." },
|
|
11
|
+
{ name: "analytics_viewer", desc: "Analytics, Revenue dashboard — read only." },
|
|
12
|
+
{ name: "site_admin", desc: "Site Settings, Feature Flags, Navigation, Roles — write." },
|
|
13
|
+
{ name: "superadmin", desc: "All of the above + Team management + User role changes." },
|
|
14
|
+
];
|
|
15
|
+
return (_jsxs(Div, { className: "space-y-8 pb-10 max-w-3xl mx-auto", children: [_jsxs(Section, { children: [_jsxs(Div, { className: "flex items-center gap-3 mb-2", children: [_jsx(Div, { className: "flex-shrink-0 w-10 h-10 rounded-xl flex items-center justify-center", style: { background: "linear-gradient(135deg,var(--appkit-color-primary-700,#4f46e5) 0%,var(--appkit-color-cobalt,#2563eb) 100%)" }, children: _jsx(Users, { className: "w-5 h-5 text-white" }) }), _jsx(Text, { className: "text-sm font-semibold text-[var(--appkit-color-text-muted)] uppercase tracking-widest", children: "Admin Guide" })] }), _jsx(Heading, { level: 1, className: "text-2xl md:text-3xl font-bold text-[var(--appkit-color-text)] mb-2", children: "Team & Permissions" }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", children: "How roles, permissions, and employee accounts work on LetItRip." })] }), [
|
|
16
|
+
{
|
|
17
|
+
Icon: Shield, title: "Role vs Permission: Key Difference",
|
|
18
|
+
content: (_jsxs(_Fragment, { children: [_jsxs(Text, { className: "text-sm text-[var(--appkit-color-text-muted)] mb-3", children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Role" }), " (", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "user.role" }), ") is a coarse-grained classification: ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "user" }), ", ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "seller" }), ", ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "moderator" }), ", ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "employee" }), ", ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "admin" }), "."] }), _jsxs(Text, { className: "text-sm text-[var(--appkit-color-text-muted)] mb-3", children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Permissions" }), " (", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "user.permissions[]" }), ") are fine-grained capabilities: e.g. ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "admin:orders:read" }), ", ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "admin:products:write" }), "."] }), _jsxs(Alert, { variant: "info", children: [_jsx("code", { className: "text-xs", children: "role: \"admin\"" }), " bypasses all RBAC permission checks \u2014 it's a superuser flag. ", _jsx("code", { className: "text-xs", children: "role: \"employee\"" }), " is governed entirely by ", _jsx("code", { className: "text-xs", children: "user.permissions[]" }), "."] })] })),
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
Icon: Users, title: "Permission Groups",
|
|
22
|
+
content: (_jsxs(_Fragment, { children: [_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)] mb-3", children: "Permission groups are predefined bundles. Assign a group to an employee to give them a coherent set of access rights:" }), _jsx(Div, { className: "space-y-2", children: PERMISSION_GROUPS.map(({ name, desc }) => (_jsxs(Div, { className: "flex gap-3 text-sm", children: [_jsx(Text, { className: "flex-shrink-0 font-mono text-xs font-semibold text-[var(--appkit-color-primary)] w-40", children: name }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", children: desc })] }, name))) })] })),
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
Icon: Users, title: "Employee Invite Flow",
|
|
26
|
+
content: (_jsxs("ul", { className: "space-y-2 text-sm text-[var(--appkit-color-text-muted)]", children: [_jsxs("li", { children: ["Go to ", _jsx("strong", { children: "Admin \u2192 Team \u2192 Invite Employee" }), ". Enter the person's email and select their permission group."] }), _jsxs("li", { children: ["They receive an invitation email with a sign-up link. On first login, their Firestore profile is created with ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "role: \"employee\"" }), " and the selected permissions array."] }), _jsx("li", { children: "To update permissions later: edit the employee in the Team section. Changes take effect on their next login (session tokens carry the old permissions until refreshed)." }), _jsxs("li", { children: ["To offboard: use ", _jsx("strong", { children: "Revoke Access" }), " in Team \u2014 sets ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "isDisabled: true" }), " on their Auth account and clears all permissions."] })] })),
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
Icon: Shield, title: "The 85+ Permission System",
|
|
30
|
+
content: (_jsxs(_Fragment, { children: [_jsxs(Text, { className: "text-sm text-[var(--appkit-color-text-muted)] mb-3", children: ["Permissions follow the pattern ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "admin:{resource}:{action}" }), ". Actions: ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "read" }), ", ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "write" }), ", ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "delete" }), ", ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "view" }), "."] }), _jsxs(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: ["The full permission list is defined in ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "appkit/src/features/auth/schemas/firestore.ts" }), ". Custom roles (specific permission combinations) can be created at ", _jsx("strong", { children: "Admin \u2192 Custom Roles" }), " \u2014 use these when a predefined group is too broad or too narrow for a hire."] })] })),
|
|
31
|
+
},
|
|
32
|
+
].map(({ Icon, title, content }) => (_jsxs(Section, { className: "rounded-2xl border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] overflow-hidden", children: [_jsxs(Div, { className: "flex items-center gap-3 px-6 py-4 border-b border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface-2,var(--appkit-color-border))]/20", children: [_jsx(Icon, { className: "w-5 h-5 text-[var(--appkit-color-primary)]" }), _jsx(Heading, { level: 2, className: "text-base font-semibold text-[var(--appkit-color-text)]", children: title })] }), _jsx(Div, { className: "px-6 py-5", children: content })] }, title)))] }));
|
|
33
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function AdminTrustGuideView(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Shield, Ban, AlertTriangle, Headphones, Flag, ClipboardList } from "lucide-react";
|
|
3
|
+
import { Div, Heading, Text, Section, Alert } from "../../../ui";
|
|
4
|
+
import { GC } from "../../_guide-cls";
|
|
5
|
+
const STILL_ALLOWED = "✓ Still allowed";
|
|
6
|
+
export function AdminTrustGuideView() {
|
|
7
|
+
return (_jsxs(Div, { className: "space-y-8 pb-10 max-w-3xl mx-auto", children: [_jsxs(Section, { children: [_jsxs(Div, { className: "flex items-center gap-3 mb-2", children: [_jsx(Div, { className: "flex-shrink-0 w-10 h-10 rounded-xl flex items-center justify-center", style: { background: "linear-gradient(135deg,var(--appkit-color-primary-700,#4f46e5) 0%,var(--appkit-color-cobalt,#2563eb) 100%)" }, children: _jsx(Shield, { className: "w-5 h-5 text-white" }) }), _jsx(Text, { className: "text-sm font-semibold text-[var(--appkit-color-text-muted)] uppercase tracking-widest", children: "Admin Guide" })] }), _jsx(Heading, { level: 1, className: "text-2xl md:text-3xl font-bold text-[var(--appkit-color-text)] mb-2", children: "Trust & Safety" }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", children: "Bans, scam registry, support tickets, moderation, and reports on LetItRip." })] }), [
|
|
8
|
+
{
|
|
9
|
+
Icon: Ban, title: "Soft Ban vs Hard Ban",
|
|
10
|
+
content: (_jsxs(_Fragment, { children: [_jsx(Div, { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full text-sm border-collapse", children: [_jsx("thead", { children: _jsx("tr", { className: "border-b border-[var(--appkit-color-border)]", children: ["", "Soft Ban", "Hard Ban"].map((h) => (_jsx("th", { className: "text-left py-2 pr-4 font-semibold text-[var(--appkit-color-text)]", children: h }, h))) }) }), _jsx("tbody", { className: "text-[var(--appkit-color-text-muted)] text-sm", children: [
|
|
11
|
+
["Login", STILL_ALLOWED, "✗ Blocked"],
|
|
12
|
+
["Browse", STILL_ALLOWED, STILL_ALLOWED],
|
|
13
|
+
["Buy", "Depends on scope", "✗ Blocked"],
|
|
14
|
+
["Sell", "Depends on scope", "✗ Blocked — store suspended"],
|
|
15
|
+
["Bid", "Can restrict with place_bids scope", "✗ Blocked"],
|
|
16
|
+
["Duration", "Set expiry date", "Permanent until admin lifts"],
|
|
17
|
+
].map(([action, soft, hard]) => (_jsxs("tr", { className: "border-b border-[var(--appkit-color-border)]/50", children: [_jsx("td", { className: "py-2 pr-4 font-medium text-[var(--appkit-color-text)]", children: action }), _jsx("td", { className: "py-2 pr-4", children: soft }), _jsx("td", { className: "py-2", children: hard })] }, action))) })] }) }), _jsxs(Text, { className: "text-sm text-[var(--appkit-color-text-muted)] mt-3", children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Soft ban scopes" }), ": ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "place_bids" }), ", ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "write_reviews" }), ", ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "create_listings" }), ", ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "open_tickets" }), ", ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "join_events" }), ". Multiple scopes can be combined."] })] })),
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
Icon: AlertTriangle, title: "Scam Registry",
|
|
21
|
+
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "27 scam types" }), ": Documented in the registry, covering fake payments, empty-box shipping, counterfeit graded cards, and more."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Scammer profile" }), ": Stored with slug prefix ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "scammer-" }), ". Includes phone, UPI VPA, and social handles as identifiers."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Report review" }), ": All community-submitted reports go through a review queue before publishing. Require at least 2 corroborating reports or 1 strong piece of evidence (payment screenshot, police complaint)."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Takedown requests" }), ": Route to senior admin with legal involvement. Preserve evidence before any deletion."] })] })),
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
Icon: Headphones, title: "Support Tickets",
|
|
25
|
+
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "SLA" }), ": 24\u201348 hours first response. Escalation: 72-hour unresponded tickets go to senior admin queue automatically."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Categories" }), ": order_issue, billing_payment, account_access, seller_dispute, technical, other."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Limit" }), ": Buyers can have 2 active general tickets + 1 per active order. This prevents abuse. Admins can override the limit for legitimate edge cases."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Closing tickets" }), ": Only close a ticket after confirming the issue is resolved. Auto-close (7 days no response from buyer) is enabled for status-inquiry tickets only."] })] })),
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
Icon: Flag, title: "Moderation Queue",
|
|
29
|
+
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsx("li", { children: "The moderation queue surfaces flagged listings, reviews, and user-generated content for review." }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Listing flags" }), ": Counterfeit claim, inappropriate images, misleading description. Each flag requires a decision: dismiss, warn seller, remove listing."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Review flags" }), ": PII in review text, slurs, spam. Decision: edit (remove PII), hide (dispute pending), or delete (violation confirmed)."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Appeals" }), ": Any moderation action can be appealed by the seller/user via a support ticket. Document your original decision clearly in the mod log."] })] })),
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
Icon: ClipboardList, title: "Item Requests",
|
|
33
|
+
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsx("li", { children: "Buyers can submit \"Want to Buy\" item requests for items not currently listed on LetItRip." }), _jsx("li", { children: "Admins review requests and can forward them to relevant verified sellers via the Item Requests section." }), _jsx("li", { children: "High-frequency requests (5+ requests for the same item) should be shared with the seller community newsletter." })] })),
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
Icon: Shield, title: "Reports",
|
|
37
|
+
content: (_jsxs(_Fragment, { children: [_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)] mb-3", children: "User-submitted reports about accounts, listings, or behaviour. Different from scam registry entries \u2014 reports are internal and not published publicly." }), _jsx(Alert, { variant: "warning", children: "Reports contain PII (reporter identity). Access is restricted to trust_safety permission group. Do not discuss report details outside the admin system. If a report involves a current employee, escalate to the highest admin immediately." })] })),
|
|
38
|
+
},
|
|
39
|
+
].map(({ Icon, title, content }) => (_jsxs(Section, { className: "rounded-2xl border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] overflow-hidden", children: [_jsxs(Div, { className: "flex items-center gap-3 px-6 py-4 border-b border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface-2,var(--appkit-color-border))]/20", children: [_jsx(Icon, { className: "w-5 h-5 text-[var(--appkit-color-primary)]" }), _jsx(Heading, { level: 2, className: "text-base font-semibold text-[var(--appkit-color-text)]", children: title })] }), _jsx(Div, { className: "px-6 py-5", children: content })] }, title)))] }));
|
|
40
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function AdminUsersGuideView(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Users, Search, Edit, Monitor, UserCog, AlertTriangle } from "lucide-react";
|
|
3
|
+
import { Div, Heading, Text, Section, Alert } from "../../../ui";
|
|
4
|
+
import { GC } from "../../_guide-cls";
|
|
5
|
+
export function AdminUsersGuideView() {
|
|
6
|
+
return (_jsxs(Div, { className: "space-y-8 pb-10 max-w-3xl mx-auto", children: [_jsxs(Section, { children: [_jsxs(Div, { className: "flex items-center gap-3 mb-2", children: [_jsx(Div, { className: "flex-shrink-0 w-10 h-10 rounded-xl flex items-center justify-center", style: { background: "linear-gradient(135deg,var(--appkit-color-primary-700,#4f46e5) 0%,var(--appkit-color-cobalt,#2563eb) 100%)" }, children: _jsx(Users, { className: "w-5 h-5 text-white" }) }), _jsx(Text, { className: "text-sm font-semibold text-[var(--appkit-color-text-muted)] uppercase tracking-widest", children: "Admin Guide" })] }), _jsx(Heading, { level: 1, className: "text-2xl md:text-3xl font-bold text-[var(--appkit-color-text)] mb-2", children: "Users & Accounts" }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", children: "Managing users, roles, sessions, and employee accounts on LetItRip." })] }), [
|
|
7
|
+
{
|
|
8
|
+
Icon: Users, title: "User Roles Overview",
|
|
9
|
+
content: (_jsx(Div, { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full text-sm border-collapse", children: [_jsx("thead", { children: _jsx("tr", { className: "border-b border-[var(--appkit-color-border)]", children: ["Role", "What they can do", "How assigned"].map((h) => (_jsx("th", { className: "text-left py-2 pr-4 font-semibold text-[var(--appkit-color-text)]", children: h }, h))) }) }), _jsx("tbody", { className: "text-[var(--appkit-color-text-muted)]", children: [
|
|
10
|
+
["user", "Browse, buy, bid, wishlist, reviews, support tickets", "Auto on registration"],
|
|
11
|
+
["seller", "Everything above + store management + listings", "Auto when store is created"],
|
|
12
|
+
["moderator", "Review content, manage scam reports, FAQ moderation", "Admin team invite"],
|
|
13
|
+
["employee", "Assigned permission-group access to admin sections", "Admin team invite"],
|
|
14
|
+
["admin", "Full access to all admin sections, bypasses RBAC checks", "Manual via Firebase console"],
|
|
15
|
+
].map(([role, can, how]) => (_jsxs("tr", { className: "border-b border-[var(--appkit-color-border)]/50", children: [_jsx("td", { className: "py-2 pr-4 font-mono text-xs text-[var(--appkit-color-primary)]", children: role }), _jsx("td", { className: "py-2 pr-4", children: can }), _jsx("td", { className: "py-2", children: how })] }, role))) })] }) })),
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
Icon: Search, title: "User List & Search",
|
|
19
|
+
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Columns" }), ": display name, email, role badge, emailVerified, createdAt, last login."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Filter chips" }), ": All, by role, verified only, banned."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Search" }), ": by email or displayName. Email matches use the HMAC blind index \u2014 searching partial emails returns no results."] })] })),
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
Icon: Edit, title: "Editing a User",
|
|
23
|
+
content: (_jsxs(_Fragment, { children: [_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)] mb-3", children: "The AdminUserEditorView opens in a side drawer. Fields:" }), _jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "role Select" }), " \u2014 changing to ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "admin" }), " bypasses all RBAC checks. Only do this with explicit senior approval."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "emailVerified toggle" }), " \u2014 manually mark an email as verified (e.g. after manual ID check)."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "isDisabled" }), " \u2014 disables Firebase Auth login without a full hard ban. User sees \"account suspended\" on login."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "PII note" }), " \u2014 email and phone are HMAC-indexed. The UI shows the hashed index, not the plaintext value. Never store plaintext in Firestore."] })] })] })),
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
Icon: Monitor, title: "Sessions Management",
|
|
27
|
+
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Session table" }), " shows: browser, OS, device type, masked IP (last octet zeroed), last activity, and estimated location."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Force expire" }), " a session if you suspect account takeover \u2014 the user will be signed out on their next request."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Hard ban cascade" }), " \u2014 issuing a hard ban automatically expires all active sessions for that user."] })] })),
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
Icon: UserCog, title: "Employee Accounts",
|
|
31
|
+
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: ["Create employee accounts via ", _jsx("strong", { children: "Admin \u2192 Team" }), ", not by manually writing to Firestore."] }), _jsxs("li", { children: [_jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "role: \"employee\"" }), " has no elevated Firestore rules by default \u2014 access is governed entirely by the permission group assigned to them."] }), _jsxs("li", { children: [_jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "role: \"admin\"" }), " bypasses all RBAC \u2014 use sparingly. One admin account per real person maximum."] })] })),
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
Icon: AlertTriangle, title: "PII Handling Notice",
|
|
35
|
+
content: (_jsx(Alert, { variant: "warning", children: "Email and phone are encrypted at rest and indexed via HMAC blind index. Never export raw user data outside of the admin panel. Any CSV export (future) de-identifies PII before download. If you encounter plaintext email or phone data in Firestore directly \u2014 report it to the engineering team immediately." })),
|
|
36
|
+
},
|
|
37
|
+
].map(({ Icon, title, content }) => (_jsxs(Section, { className: "rounded-2xl border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] overflow-hidden", children: [_jsxs(Div, { className: "flex items-center gap-3 px-6 py-4 border-b border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface-2,var(--appkit-color-border))]/20", children: [_jsx(Icon, { className: "w-5 h-5 text-[var(--appkit-color-primary)]" }), _jsx(Heading, { level: 2, className: "text-base font-semibold text-[var(--appkit-color-text)]", children: title })] }), _jsx(Div, { className: "px-6 py-5", children: content })] }, title)))] }));
|
|
38
|
+
}
|
|
@@ -35,7 +35,22 @@ export class SessionRepository extends BaseRepository {
|
|
|
35
35
|
lastActivity: new Date(),
|
|
36
36
|
...(data?.location && { location: data.location }),
|
|
37
37
|
};
|
|
38
|
-
|
|
38
|
+
try {
|
|
39
|
+
await this.update(sessionId, updateData);
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
// The session doc may have been revoked/expired/wiped while the client
|
|
43
|
+
// still holds the cookie. Firestore Admin throws `5 NOT_FOUND` in that
|
|
44
|
+
// case — swallow it so the activity ping is best-effort, not a 500.
|
|
45
|
+
// Any other error is rethrown so genuine failures still surface.
|
|
46
|
+
const code = err?.code;
|
|
47
|
+
const msg = err?.message ?? "";
|
|
48
|
+
const isNotFound = code === 5 ||
|
|
49
|
+
code === "not-found" ||
|
|
50
|
+
/No document to update|NOT_FOUND/i.test(msg);
|
|
51
|
+
if (!isNotFound)
|
|
52
|
+
throw err;
|
|
53
|
+
}
|
|
39
54
|
}
|
|
40
55
|
async revokeSession(sessionId, revokedBy) {
|
|
41
56
|
await this.update(sessionId, {
|
|
@@ -11,6 +11,8 @@ export declare function useGuestCart(): {
|
|
|
11
11
|
productTitle?: string;
|
|
12
12
|
productImage?: string;
|
|
13
13
|
price?: number;
|
|
14
|
+
storeId?: string;
|
|
15
|
+
storeName?: string;
|
|
14
16
|
}) => void;
|
|
15
17
|
remove: (productId: string) => void;
|
|
16
18
|
updateQuantity: (productId: string, quantity: number) => void;
|
|
@@ -10,6 +10,11 @@ export interface GuestCartItem {
|
|
|
10
10
|
productTitle?: string;
|
|
11
11
|
productImage?: string;
|
|
12
12
|
price?: number;
|
|
13
|
+
/** Store identifier (storeSlug) — required to group cart lines by seller. */
|
|
14
|
+
storeId?: string;
|
|
15
|
+
/** Denormalised store display name — keeps the cart-group header readable
|
|
16
|
+
* for guest carts where we can't round-trip to Firestore at render time. */
|
|
17
|
+
storeName?: string;
|
|
13
18
|
}
|
|
14
19
|
export interface GuestCartStorage {
|
|
15
20
|
getItem(key: string): string | null;
|
|
@@ -21,6 +26,8 @@ export declare function addToGuestCart(productId: string, quantity: number, snap
|
|
|
21
26
|
productTitle?: string;
|
|
22
27
|
productImage?: string;
|
|
23
28
|
price?: number;
|
|
29
|
+
storeId?: string;
|
|
30
|
+
storeName?: string;
|
|
24
31
|
}, storage?: GuestCartStorage | null, key?: string): GuestCartItem[];
|
|
25
32
|
export declare function removeFromGuestCart(productId: string, storage?: GuestCartStorage | null, key?: string): GuestCartItem[];
|
|
26
33
|
export declare function updateGuestCartQuantity(productId: string, quantity: number, storage?: GuestCartStorage | null, key?: string): GuestCartItem[];
|
|
@@ -15,6 +15,12 @@ export interface CartOp {
|
|
|
15
15
|
productTitle?: string;
|
|
16
16
|
productImage?: string;
|
|
17
17
|
price?: number;
|
|
18
|
+
/** Store identifier (storeSlug) — carried into the server cart on sync so
|
|
19
|
+
* cart lines group by seller correctly for both guest + signed-in flows. */
|
|
20
|
+
storeId?: string;
|
|
21
|
+
/** Denormalised store display name — keeps the group header readable when
|
|
22
|
+
* the server-side hydration has not yet run. */
|
|
23
|
+
storeName?: string;
|
|
18
24
|
ts: number;
|
|
19
25
|
}
|
|
20
26
|
export interface WishlistOp {
|
|
@@ -9,13 +9,11 @@ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
|
9
9
|
* sort/search/filter/pagination on top of the initial snapshot.
|
|
10
10
|
*/
|
|
11
11
|
import { useMemo, useCallback, useState } from "react";
|
|
12
|
-
import
|
|
13
|
-
import { Badge, Div, Row, Text } from "../../../ui";
|
|
12
|
+
import { Div, Text } from "../../../ui";
|
|
14
13
|
import { ListingToolbar, Pagination, FilterDrawer } from "../../../ui";
|
|
15
14
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
16
|
-
import { ROUTES } from "../../../next/routing/route-map";
|
|
17
|
-
import { formatCurrency } from "../../../utils/number.formatter";
|
|
18
15
|
import { BundleBuyNowCta } from "./BundleBuyNowCta";
|
|
16
|
+
import { MarketplaceBundleCard } from "../../products/components/MarketplaceBundleCard";
|
|
19
17
|
import { TABLE_KEYS } from "../../../constants/table-keys";
|
|
20
18
|
const PAGE_SIZE = 12;
|
|
21
19
|
const FILTER_KEYS = ["showOutOfStock"];
|
|
@@ -26,15 +24,6 @@ const SORT_OPTIONS = [
|
|
|
26
24
|
{ value: "-price", label: "Price: High to Low" },
|
|
27
25
|
];
|
|
28
26
|
const DEFAULT_SORT = "-createdAt";
|
|
29
|
-
const STOCK_BADGE_TEXT = {
|
|
30
|
-
in_stock: "",
|
|
31
|
-
out_of_stock: "Not active",
|
|
32
|
-
};
|
|
33
|
-
const STOCK_BADGE_VARIANT = {
|
|
34
|
-
in_stock: "success",
|
|
35
|
-
out_of_stock: "warning",
|
|
36
|
-
};
|
|
37
|
-
const PLACEHOLDER_EMOJI = "📦";
|
|
38
27
|
export function CategoryBundlesListing({ initialBundles, brandName, onBuyNow, }) {
|
|
39
28
|
const table = useUrlTable({ defaults: { sort: DEFAULT_SORT } });
|
|
40
29
|
const [searchInput, setSearchInput] = useState(table.get(TABLE_KEYS.QUERY) || "");
|
|
@@ -94,16 +83,5 @@ export function CategoryBundlesListing({ initialBundles, brandName, onBuyNow, })
|
|
|
94
83
|
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: ["No bundles available", brandName ? ` for ${brandName}` : "", " yet."] }) }));
|
|
95
84
|
}
|
|
96
85
|
return (_jsxs(Div, { className: "min-h-[40vh]", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search bundles...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, onSearchKeyDown: (e) => { if (e.key === "Enter")
|
|
97
|
-
commitSearch(); }, sortValue: sort, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: safePage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsx("div", { className: "py-6", children: pageItems.length === 0 ? (_jsxs(Text, { className: "py-16 text-center text-sm text-zinc-500 dark:text-zinc-400", children: ["No bundles match your search", brandName ? ` for ${brandName}` : "", "."] })) : (_jsx("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3", children: pageItems.map((bundle) => (_jsx(
|
|
98
|
-
}
|
|
99
|
-
function BundleCard({ bundle, onBuyNow }) {
|
|
100
|
-
const memberCount = bundle.bundleProductIds?.length ?? 0;
|
|
101
|
-
const stock = bundle.bundleStockStatus ?? "in_stock";
|
|
102
|
-
const badge = STOCK_BADGE_TEXT[stock];
|
|
103
|
-
const cover = bundle.display?.coverImage;
|
|
104
|
-
const href = String(ROUTES.PUBLIC.BUNDLE_DETAIL?.(bundle.slug) ?? "#");
|
|
105
|
-
const price = bundle.bundlePriceInPaise;
|
|
106
|
-
return (_jsxs("div", { className: "flex flex-col rounded-xl border border-zinc-200 bg-white dark:border-zinc-800 dark:bg-zinc-900", children: [_jsxs(Link, { href: href, className: "group block flex-1 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 ? (
|
|
107
|
-
// eslint-disable-next-line @next/next/no-img-element
|
|
108
|
-
_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: price ? formatCurrency(price / 100, "INR") : "—" }), _jsxs(Text, { size: "xs", color: "muted", children: [memberCount, " item", memberCount !== 1 ? "s" : ""] })] }), 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 }) }))] }));
|
|
86
|
+
commitSearch(); }, sortValue: sort, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: safePage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsx("div", { className: "py-6", children: pageItems.length === 0 ? (_jsxs(Text, { className: "py-16 text-center text-sm text-zinc-500 dark:text-zinc-400", children: ["No bundles match your search", brandName ? ` for ${brandName}` : "", "."] })) : (_jsx("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3", children: pageItems.map((bundle) => (_jsxs("div", { className: "flex flex-col", children: [_jsx(MarketplaceBundleCard, { bundle: bundle }), onBuyNow && (_jsx("div", { className: "border-t border-zinc-100 px-3 pt-2 pb-3 dark:border-zinc-800 -mt-px rounded-b-xl border border-t-0 border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-900", children: _jsx(BundleBuyNowCta, { bundleSlug: bundle.slug, outOfStock: bundle.bundleStockStatus === "out_of_stock", onBuyNow: onBuyNow, compact: true }) }))] }, bundle.id))) })) }), _jsx(FilterDrawer, { open: filterOpen, onOpen: openFilters, onClose: () => setFilterOpen(false), onApply: applyFilters, onReset: () => setPendingShowOutOfStock(false), activeCount: filterActiveCount, hideTrigger: true, children: _jsx("div", { className: "space-y-4 p-4", children: _jsxs("label", { className: "flex items-center justify-between gap-3 cursor-pointer", children: [_jsx("span", { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300", children: "Show out-of-stock bundles" }), _jsx("button", { type: "button", role: "switch", "aria-checked": pendingShowOutOfStock, onClick: () => setPendingShowOutOfStock((v) => !v), className: `relative inline-flex h-5 w-9 flex-shrink-0 items-center rounded-full transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1 ${pendingShowOutOfStock ? "bg-primary" : "bg-zinc-300 dark:bg-slate-600"}`, children: _jsx("span", { className: `inline-block h-3.5 w-3.5 transform rounded-full bg-white shadow-sm transition-transform duration-200 ${pendingShowOutOfStock ? "translate-x-[19px]" : "translate-x-[3px]"}` }) })] }) }) })] }));
|
|
109
87
|
}
|
|
@@ -85,19 +85,15 @@ export function CategoryProductsListing({ categorySlug, categoryId, brandName, i
|
|
|
85
85
|
});
|
|
86
86
|
}, [wishlistedIds, localWishlist, showToast, requireAuth]);
|
|
87
87
|
const handleAddToCart = useCallback((product) => {
|
|
88
|
-
|
|
88
|
+
const snapshot = {
|
|
89
89
|
productTitle: product.title,
|
|
90
90
|
productImage: product.mainImage,
|
|
91
91
|
price: product.price,
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
productTitle: product.title,
|
|
98
|
-
productImage: product.mainImage,
|
|
99
|
-
price: product.price,
|
|
100
|
-
});
|
|
92
|
+
storeId: product.storeId,
|
|
93
|
+
storeName: product.storeName,
|
|
94
|
+
};
|
|
95
|
+
localCart.add(product.id, 1, snapshot);
|
|
96
|
+
pushCartOp({ op: "add", productId: product.id, quantity: 1, ...snapshot });
|
|
101
97
|
showToast("Added to cart", "success");
|
|
102
98
|
}, [localCart, showToast]);
|
|
103
99
|
return (_jsxs("div", { className: "min-h-[200px]", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: `Search in ${categorySlug.replace(/-/g, " ")}...`, onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || "-createdAt", sortOptions: PRODUCT_PUBLIC_SORT_OPTIONS, onSortChange: (v) => {
|
|
@@ -6,8 +6,6 @@ export type { BreadcrumbTrailProps } from "./BreadcrumbTrail";
|
|
|
6
6
|
export { BreadcrumbTrail } from "./BreadcrumbTrail";
|
|
7
7
|
export { ConcernCard } from "./ConcernCard";
|
|
8
8
|
export { ConcernGrid } from "./ConcernGrid";
|
|
9
|
-
export { CategoriesListView } from "./CategoriesListView";
|
|
10
|
-
export type { CategoriesListViewProps } from "./CategoriesListView";
|
|
11
9
|
export { CategoryFilters, CATEGORY_FILTER_KEYS, CATEGORY_ADMIN_SORT_OPTIONS, CATEGORY_SELLER_SORT_OPTIONS, CATEGORY_PUBLIC_SORT_OPTIONS, getCategoryFilterKeys, getCategorySortOptions, } from "./CategoryFilters";
|
|
12
10
|
export type { CategoryFiltersProps, CategoryFilterVariant, } from "./CategoryFilters";
|
|
13
11
|
export { CategorySortSelect } from "./CategorySortSelect";
|
|
@@ -3,7 +3,6 @@ export { CategoryTree } from "./CategoryTree";
|
|
|
3
3
|
export { BreadcrumbTrail } from "./BreadcrumbTrail";
|
|
4
4
|
export { ConcernCard } from "./ConcernCard";
|
|
5
5
|
export { ConcernGrid } from "./ConcernGrid";
|
|
6
|
-
export { CategoriesListView } from "./CategoriesListView";
|
|
7
6
|
export { CategoryFilters, CATEGORY_FILTER_KEYS, CATEGORY_ADMIN_SORT_OPTIONS, CATEGORY_SELLER_SORT_OPTIONS, CATEGORY_PUBLIC_SORT_OPTIONS, getCategoryFilterKeys, getCategorySortOptions, } from "./CategoryFilters";
|
|
8
7
|
export { CategorySortSelect } from "./CategorySortSelect";
|
|
9
8
|
export { CategoryProductsView } from "./CategoryProductsView";
|
|
@@ -44,6 +44,15 @@ export interface BundleItemDetail {
|
|
|
44
44
|
productId: string;
|
|
45
45
|
/** Number of draw entries included when product is a prize-draw. */
|
|
46
46
|
drawCount?: number;
|
|
47
|
+
/**
|
|
48
|
+
* Denormalised member product image URL — snapshotted at bundle-edit time
|
|
49
|
+
* by the admin/store bundle editor (or the `onProductWrite` fan-out) so the
|
|
50
|
+
* BundleCard can render a 2x2 collage without N+1 product fetches.
|
|
51
|
+
* When absent the card falls back to `display.coverImage`.
|
|
52
|
+
*/
|
|
53
|
+
imageURL?: string;
|
|
54
|
+
/** Denormalised member product title — used as alt text for the collage tile. */
|
|
55
|
+
title?: string;
|
|
47
56
|
}
|
|
48
57
|
/** Full Firestore category document (includes server-only fields) */
|
|
49
58
|
export interface CategoryDocumentSEO {
|