@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
|
@@ -14,6 +14,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
14
14
|
*/
|
|
15
15
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
16
16
|
import { Button, Checkbox, Container, Heading, Input, Row, Section, Select, Stack, Text, Textarea, } from "../../../ui";
|
|
17
|
+
import { FieldInput, FormShellContext, useFormShellState } from "../../../ui/forms";
|
|
17
18
|
import { BundleItemsPicker, defaultBundleItemsFetch, } from "../../categories/components/BundleItemsPicker";
|
|
18
19
|
import { BundleDynamicRuleEditor } from "../../categories/components/BundleDynamicRuleEditor";
|
|
19
20
|
import { BUNDLE_COPY } from "../../../_internal/shared/features/categories/bundle-copy";
|
|
@@ -76,8 +77,9 @@ export function AdminBundleEditorView({ bundleId, onSaved, onDeleted, }) {
|
|
|
76
77
|
const [loading, setLoading] = useState(isEdit);
|
|
77
78
|
const [saving, setSaving] = useState(false);
|
|
78
79
|
const [deleting, setDeleting] = useState(false);
|
|
79
|
-
const [
|
|
80
|
+
const [apiError, setApiError] = useState(null);
|
|
80
81
|
const [metadata, setMetadata] = useState({});
|
|
82
|
+
const { shellCtx, setFieldError, clearErrors } = useFormShellState();
|
|
81
83
|
// Load existing bundle on mount when editing
|
|
82
84
|
useEffect(() => {
|
|
83
85
|
if (!bundleId)
|
|
@@ -97,7 +99,7 @@ export function AdminBundleEditorView({ bundleId, onSaved, onDeleted, }) {
|
|
|
97
99
|
.catch((err) => {
|
|
98
100
|
if (cancelled)
|
|
99
101
|
return;
|
|
100
|
-
|
|
102
|
+
setApiError(err instanceof Error
|
|
101
103
|
? err.message
|
|
102
104
|
: BUNDLE_COPY.adminEditor.errors.loadFailed);
|
|
103
105
|
})
|
|
@@ -110,16 +112,20 @@ export function AdminBundleEditorView({ bundleId, onSaved, onDeleted, }) {
|
|
|
110
112
|
};
|
|
111
113
|
}, [bundleId]);
|
|
112
114
|
const handleSave = useCallback(async () => {
|
|
113
|
-
|
|
115
|
+
clearErrors();
|
|
116
|
+
setApiError(null);
|
|
114
117
|
const priceInPaise = parsePriceRupees(form.priceRupees);
|
|
118
|
+
let hasError = false;
|
|
115
119
|
if (!form.name.trim()) {
|
|
116
|
-
|
|
117
|
-
|
|
120
|
+
setFieldError("name", BUNDLE_COPY.adminEditor.errors.nameRequired);
|
|
121
|
+
hasError = true;
|
|
118
122
|
}
|
|
119
123
|
if (priceInPaise === null) {
|
|
120
|
-
|
|
121
|
-
|
|
124
|
+
setFieldError("price", BUNDLE_COPY.adminEditor.errors.priceInvalid);
|
|
125
|
+
hasError = true;
|
|
122
126
|
}
|
|
127
|
+
if (hasError)
|
|
128
|
+
return;
|
|
123
129
|
setSaving(true);
|
|
124
130
|
try {
|
|
125
131
|
// SB-UNI-5 2026-05-13 — static vs dynamic rule branching.
|
|
@@ -171,14 +177,14 @@ export function AdminBundleEditorView({ bundleId, onSaved, onDeleted, }) {
|
|
|
171
177
|
}
|
|
172
178
|
}
|
|
173
179
|
catch (err) {
|
|
174
|
-
|
|
180
|
+
setApiError(err instanceof Error
|
|
175
181
|
? err.message
|
|
176
182
|
: BUNDLE_COPY.adminEditor.errors.saveFailed);
|
|
177
183
|
}
|
|
178
184
|
finally {
|
|
179
185
|
setSaving(false);
|
|
180
186
|
}
|
|
181
|
-
}, [form, bundleId, isEdit, onSaved]);
|
|
187
|
+
}, [form, bundleId, isEdit, onSaved, clearErrors, setFieldError]);
|
|
182
188
|
const handleDelete = useCallback(async () => {
|
|
183
189
|
if (!bundleId)
|
|
184
190
|
return;
|
|
@@ -186,7 +192,7 @@ export function AdminBundleEditorView({ bundleId, onSaved, onDeleted, }) {
|
|
|
186
192
|
return;
|
|
187
193
|
}
|
|
188
194
|
setDeleting(true);
|
|
189
|
-
|
|
195
|
+
setApiError(null);
|
|
190
196
|
try {
|
|
191
197
|
const res = await fetch(`/api/admin/bundles/${encodeURIComponent(bundleId)}`, { method: "DELETE" });
|
|
192
198
|
if (!res.ok)
|
|
@@ -194,7 +200,7 @@ export function AdminBundleEditorView({ bundleId, onSaved, onDeleted, }) {
|
|
|
194
200
|
onDeleted?.();
|
|
195
201
|
}
|
|
196
202
|
catch (err) {
|
|
197
|
-
|
|
203
|
+
setApiError(err instanceof Error
|
|
198
204
|
? err.message
|
|
199
205
|
: BUNDLE_COPY.adminEditor.errors.deleteFailed);
|
|
200
206
|
}
|
|
@@ -206,7 +212,7 @@ export function AdminBundleEditorView({ bundleId, onSaved, onDeleted, }) {
|
|
|
206
212
|
if (loading) {
|
|
207
213
|
return (_jsx(Section, { className: "py-10", children: _jsx(Container, { size: "lg", children: _jsx(Text, { children: BUNDLE_COPY.adminEditor.loading }) }) }));
|
|
208
214
|
}
|
|
209
|
-
return (_jsx(Section, { className: "py-10", children: _jsx(Container, { size: "lg", children: _jsxs(Stack, { gap: "lg", children: [_jsxs(Row, { gap: "sm", align: "center", justify: "between", className: "flex-wrap", children: [_jsx(Heading, { level: 1, className: "text-2xl font-semibold text-zinc-900 dark:text-zinc-100", children: isEdit
|
|
210
|
-
|
|
211
|
-
|
|
215
|
+
return (_jsx(FormShellContext.Provider, { value: shellCtx, children: _jsx(Section, { className: "py-10", children: _jsx(Container, { size: "lg", children: _jsxs(Stack, { gap: "lg", children: [_jsxs(Row, { gap: "sm", align: "center", justify: "between", className: "flex-wrap", children: [_jsx(Heading, { level: 1, className: "text-2xl font-semibold text-zinc-900 dark:text-zinc-100", children: isEdit
|
|
216
|
+
? BUNDLE_COPY.adminEditorTitleEdit
|
|
217
|
+
: BUNDLE_COPY.adminEditorTitleNew }), isEdit && (_jsx(Button, { variant: "danger", onClick: handleDelete, disabled: deleting, children: BUNDLE_COPY.adminEditor.deleteButton(deleting) }))] }), apiError && (_jsx(Text, { color: "danger", role: "alert", children: apiError })), _jsxs(Stack, { gap: "md", children: [_jsx(FieldInput, { name: "name", label: BUNDLE_COPY.adminEditor.fields.nameLabel, value: form.name, onChange: (v) => setForm((f) => ({ ...f, name: v })), placeholder: BUNDLE_COPY.adminEditor.fields.namePlaceholder, disabled: saving, required: true }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: BUNDLE_COPY.adminEditor.fields.descriptionLabel }), _jsx(Textarea, { value: form.description, onChange: (e) => setForm((f) => ({ ...f, description: e.target.value })), placeholder: BUNDLE_COPY.adminEditor.fields.descriptionPlaceholder, rows: 4, disabled: saving })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(FieldInput, { name: "price", label: BUNDLE_COPY.adminEditor.fields.priceLabel, type: "number", inputMode: "decimal", min: 1, step: 1, value: form.priceRupees, onChange: (v) => setForm((f) => ({ ...f, priceRupees: v })), placeholder: BUNDLE_COPY.adminEditor.fields.pricePlaceholder, disabled: saving, required: true }), _jsx(Text, { size: "xs", color: "muted", children: BUNDLE_COPY.adminEditor.fields.pricePaiseHint(parsePriceRupees(form.priceRupees)) })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: BUNDLE_COPY.adminEditor.fields.coverImageLabel }), _jsx(Input, { type: "url", value: form.coverImage, onChange: (e) => setForm((f) => ({ ...f, coverImage: e.target.value })), placeholder: "https://\u2026", disabled: saving })] }), _jsx(Checkbox, { checked: form.isActive, onChange: (e) => setForm((f) => ({ ...f, isActive: e.target.checked })), disabled: saving, label: BUNDLE_COPY.adminEditor.fields.activeLabel }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: BUNDLE_COPY.adminEditor.ruleTypeLabel }), _jsx(Select, { options: RULE_TYPE_OPTIONS, value: form.ruleType, onValueChange: (next) => setForm((f) => ({ ...f, ruleType: next })), disabled: saving, "aria-label": BUNDLE_COPY.adminEditor.ruleTypeLabel })] }), form.ruleType === "static" ? (_jsx(BundleItemsPicker, { value: form.productIds, onChange: (next) => setForm((f) => ({ ...f, productIds: next })), fetchProducts: fetchProducts, initialMetadata: metadata })) : (_jsx(BundleDynamicRuleEditor, { value: form.dynamicRule, onChange: (next) => setForm((f) => ({ ...f, dynamicRule: next })), disabled: saving }))] }), _jsx(Row, { gap: "sm", align: "center", justify: "end", children: _jsx(Button, { variant: "primary", onClick: handleSave, disabled: saving, "aria-busy": saving, children: BUNDLE_COPY.adminEditor.saveButton(saving, isEdit) }) })] }) }) }) }));
|
|
212
218
|
}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
export interface AdminBundlesViewProps {
|
|
2
|
-
/** Builds the href for the row-click edit action. */
|
|
3
2
|
getEditHref: (row: {
|
|
4
3
|
id: string;
|
|
5
4
|
}) => string;
|
|
6
|
-
/** Builds the href for the "New bundle" CTA. */
|
|
7
5
|
newHref: string;
|
|
8
6
|
}
|
|
9
|
-
export declare function AdminBundlesView({ getEditHref, newHref
|
|
7
|
+
export declare function AdminBundlesView({ getEditHref, newHref }: AdminBundlesViewProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,40 +1,142 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
* panel-url sync) because bundles are admin-only + low cardinality. Upgrade
|
|
12
|
-
* paths to the full pattern remain open if volume grows.
|
|
13
|
-
*/
|
|
14
|
-
import { useCallback, useEffect, useState } from "react";
|
|
15
|
-
import Link from "next/link";
|
|
16
|
-
import { Badge, Button, Container, Div, Heading, Row, Section, Stack, Text, useToast, } from "../../../ui";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useCallback } from "react";
|
|
4
|
+
import { Plus, X } from "lucide-react";
|
|
5
|
+
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
6
|
+
import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
|
|
7
|
+
import { Badge, BulkActionBar, Button, Heading, ListingToolbar, Pagination, Stack, Text, useToast, } from "../../../ui";
|
|
8
|
+
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
9
|
+
import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
|
|
10
|
+
import { DataTable } from "./DataTable";
|
|
17
11
|
import { BUNDLE_COPY, BUNDLE_STOCK_VARIANT, } from "../../../_internal/shared/features/categories/bundle-copy";
|
|
18
12
|
import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
13
|
+
const PAGE_SIZE = 25;
|
|
14
|
+
const FILTER_KEYS = ["isActive", "bundleStockStatus"];
|
|
15
|
+
const DEFAULT_SORT = "name";
|
|
16
|
+
const SORT_OPTIONS = [
|
|
17
|
+
{ value: "name", label: "Name A–Z" },
|
|
18
|
+
{ value: "-name", label: "Name Z–A" },
|
|
19
|
+
{ value: "-bundlePriceInPaise", label: "Price high→low" },
|
|
20
|
+
{ value: "bundlePriceInPaise", label: "Price low→high" },
|
|
21
|
+
{ value: "-createdAt", label: "Newest" },
|
|
22
|
+
{ value: "createdAt", label: "Oldest" },
|
|
23
|
+
];
|
|
23
24
|
function formatPrice(paise) {
|
|
24
25
|
if (typeof paise !== "number" || paise <= 0)
|
|
25
26
|
return "—";
|
|
26
27
|
return `₹${Math.round(paise / 100).toLocaleString("en-IN")}`;
|
|
27
28
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
const COLUMNS = [
|
|
30
|
+
{
|
|
31
|
+
key: "primary",
|
|
32
|
+
header: "Name",
|
|
33
|
+
render: (row) => (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { className: "font-medium text-zinc-900 dark:text-zinc-100", children: row.primary }), _jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: row.secondary })] })),
|
|
34
|
+
},
|
|
35
|
+
{ key: "price", header: "Price", className: "w-28" },
|
|
36
|
+
{ key: "members", header: "Members", className: "w-24" },
|
|
37
|
+
{
|
|
38
|
+
key: "stockStatus",
|
|
39
|
+
header: "Stock",
|
|
40
|
+
className: "w-28",
|
|
41
|
+
render: (row) => (_jsx(Badge, { variant: BUNDLE_STOCK_VARIANT[row.stockStatus] ?? "default", children: row.stockStatus === "in_stock"
|
|
42
|
+
? BUNDLE_COPY.stockBadge.listVariantInStock
|
|
43
|
+
: BUNDLE_COPY.stockBadge.listVariantOutOfStock })),
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
key: "status",
|
|
47
|
+
header: "Status",
|
|
48
|
+
className: "w-24",
|
|
49
|
+
render: (row) => (_jsx(Badge, { variant: row.isActive ? "success" : "default", children: row.status })),
|
|
50
|
+
},
|
|
51
|
+
{ key: "updatedAt", header: "Updated", className: "w-28" },
|
|
52
|
+
];
|
|
53
|
+
function BundlesFilterDrawer({ filterOpen, setFilterOpen, activeFilterCount, clearFilters, pendingFilters, setPendingFilters, applyFilters, }) {
|
|
54
|
+
if (!filterOpen)
|
|
55
|
+
return null;
|
|
56
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-black/40", "aria-hidden": "true", onClick: () => setFilterOpen(false) }), _jsxs("div", { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-white dark:bg-slate-900 shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: [_jsx("span", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "Filters" }), _jsxs("div", { className: "flex items-center gap-2", children: [activeFilterCount > 0 && (_jsx("button", { type: "button", onClick: clearFilters, className: "text-xs text-zinc-500 hover:text-rose-500 dark:text-zinc-400 transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close", className: "rounded-lg p-1.5 text-zinc-500 hover:bg-zinc-100 dark:hover:bg-slate-800 transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsxs("div", { className: "flex-1 overflow-y-auto px-4 py-4 space-y-5", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Text, { className: "text-xs font-semibold uppercase tracking-widest text-zinc-500 dark:text-zinc-400", children: "Status" }), _jsx("div", { className: "flex flex-wrap gap-2", children: [
|
|
57
|
+
{ label: "All", value: "" },
|
|
58
|
+
{ label: "Active", value: "true" },
|
|
59
|
+
{ label: "Inactive", value: "false" },
|
|
60
|
+
].map((opt) => (_jsx("button", { type: "button", onClick: () => setPendingFilters((p) => ({ ...p, isActive: opt.value })), className: `rounded-full px-3 py-1 text-xs font-medium border transition-colors ${(pendingFilters.isActive || "") === opt.value
|
|
61
|
+
? "bg-primary text-white border-primary"
|
|
62
|
+
: "border-zinc-300 dark:border-slate-600 text-zinc-700 dark:text-zinc-300 hover:bg-zinc-50 dark:hover:bg-slate-800"}`, children: opt.label }, opt.label))) })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Text, { className: "text-xs font-semibold uppercase tracking-widest text-zinc-500 dark:text-zinc-400", children: "Stock" }), _jsx("div", { className: "flex flex-wrap gap-2", children: [
|
|
63
|
+
{ label: "All", value: "" },
|
|
64
|
+
{ label: "Sold out", value: "out_of_stock" },
|
|
65
|
+
].map((opt) => (_jsx("button", { type: "button", onClick: () => setPendingFilters((p) => ({ ...p, bundleStockStatus: opt.value })), className: `rounded-full px-3 py-1 text-xs font-medium border transition-colors ${(pendingFilters.bundleStockStatus || "") === opt.value
|
|
66
|
+
? "bg-primary text-white border-primary"
|
|
67
|
+
: "border-zinc-300 dark:border-slate-600 text-zinc-700 dark:text-zinc-300 hover:bg-zinc-50 dark:hover:bg-slate-800"}`, children: opt.label }, opt.label))) })] })] }), _jsx("div", { className: "border-t border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: _jsxs("button", { type: "button", onClick: applyFilters, className: "w-full rounded-lg bg-primary py-2.5 text-sm font-semibold text-white hover:bg-primary-600 transition-colors active:scale-[0.98]", children: ["Apply Filters", activeFilterCount > 0 ? ` (${activeFilterCount})` : ""] }) })] })] }));
|
|
68
|
+
}
|
|
69
|
+
export function AdminBundlesView({ getEditHref, newHref }) {
|
|
70
|
+
const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
|
|
71
|
+
const [searchInput, setSearchInput] = useState(table.get("q") || "");
|
|
72
|
+
const [filterOpen, setFilterOpen] = useState(false);
|
|
73
|
+
const [pendingFilters, setPendingFilters] = useState(() => Object.fromEntries(FILTER_KEYS.map((k) => [k, table.get(k)])));
|
|
32
74
|
const [rebuildingId, setRebuildingId] = useState(null);
|
|
33
75
|
const toast = useToast();
|
|
76
|
+
const openFilters = useCallback(() => {
|
|
77
|
+
setPendingFilters(Object.fromEntries(FILTER_KEYS.map((k) => [k, table.get(k)])));
|
|
78
|
+
setFilterOpen(true);
|
|
79
|
+
}, [table]);
|
|
80
|
+
const applyFilters = useCallback(() => {
|
|
81
|
+
const updates = { page: "1" };
|
|
82
|
+
for (const k of FILTER_KEYS)
|
|
83
|
+
updates[k] = pendingFilters[k] ?? "";
|
|
84
|
+
table.setMany(updates);
|
|
85
|
+
setFilterOpen(false);
|
|
86
|
+
}, [pendingFilters, table]);
|
|
87
|
+
const clearFilters = useCallback(() => {
|
|
88
|
+
setPendingFilters(Object.fromEntries(FILTER_KEYS.map((k) => [k, ""])));
|
|
89
|
+
}, []);
|
|
90
|
+
const resetAll = useCallback(() => {
|
|
91
|
+
const updates = { q: "", sort: "" };
|
|
92
|
+
for (const k of FILTER_KEYS)
|
|
93
|
+
updates[k] = "";
|
|
94
|
+
table.setMany(updates);
|
|
95
|
+
setSearchInput("");
|
|
96
|
+
}, [table]);
|
|
97
|
+
const commitSearch = useCallback(() => {
|
|
98
|
+
table.set("q", searchInput.trim());
|
|
99
|
+
}, [searchInput, table]);
|
|
100
|
+
const activeFilterCount = FILTER_KEYS.filter((k) => !!table.get(k)).length;
|
|
101
|
+
const hasActiveState = !!table.get("q") || table.get("sort") !== DEFAULT_SORT || activeFilterCount > 0;
|
|
102
|
+
const filterParts = [];
|
|
103
|
+
const isActiveRaw = table.get("isActive");
|
|
104
|
+
if (isActiveRaw)
|
|
105
|
+
filterParts.push(`isActive==${isActiveRaw}`);
|
|
106
|
+
const stockStatusRaw = table.get("bundleStockStatus");
|
|
107
|
+
if (stockStatusRaw)
|
|
108
|
+
filterParts.push(`bundleStockStatus==${stockStatusRaw}`);
|
|
109
|
+
const filters = filterParts.join(",") || undefined;
|
|
110
|
+
const { rows, total, isLoading, errorMessage, refetch } = useAdminListingData({
|
|
111
|
+
queryKey: ["admin", "bundles", "listing"],
|
|
112
|
+
endpoint: ADMIN_ENDPOINTS.BUNDLES,
|
|
113
|
+
page: table.getNumber("page", 1),
|
|
114
|
+
pageSize: PAGE_SIZE,
|
|
115
|
+
sorts: table.get("sort") || DEFAULT_SORT,
|
|
116
|
+
filters,
|
|
117
|
+
q: table.get("q") || undefined,
|
|
118
|
+
mapRows: (response) => toRecordArray(response.items).map((item, index) => ({
|
|
119
|
+
id: toStringValue(item.id, `bundle-${index}`),
|
|
120
|
+
primary: toStringValue(item.name, "Untitled bundle"),
|
|
121
|
+
secondary: toStringValue(item.slug, "no-slug"),
|
|
122
|
+
price: formatPrice(item.bundlePriceInPaise),
|
|
123
|
+
members: String(Array.isArray(item.bundleProductIds) ? item.bundleProductIds.length : 0),
|
|
124
|
+
stockStatus: toStringValue(item.bundleStockStatus, "in_stock"),
|
|
125
|
+
isActive: item.isActive === true,
|
|
126
|
+
status: item.isActive === true
|
|
127
|
+
? BUNDLE_COPY.adminList.activeBadge
|
|
128
|
+
: BUNDLE_COPY.adminList.inactiveBadge,
|
|
129
|
+
updatedAt: toRelativeDate(item.updatedAt ?? item.createdAt),
|
|
130
|
+
})),
|
|
131
|
+
getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
|
|
132
|
+
});
|
|
133
|
+
const currentPage = table.getNumber("page", 1);
|
|
134
|
+
const totalPages = Math.ceil(total / PAGE_SIZE);
|
|
135
|
+
const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
|
|
34
136
|
const handleRebuild = useCallback(async (bundleId) => {
|
|
35
137
|
setRebuildingId(bundleId);
|
|
36
138
|
try {
|
|
37
|
-
const res = await fetch(
|
|
139
|
+
const res = await fetch(ADMIN_ENDPOINTS.BUNDLE_REBUILD(bundleId), { method: "POST" });
|
|
38
140
|
if (!res.ok)
|
|
39
141
|
throw new Error("Rebuild failed");
|
|
40
142
|
toast.showToast("Bundle stock rebuilt.", "success");
|
|
@@ -44,33 +146,52 @@ export function AdminBundlesView({ getEditHref, newHref, }) {
|
|
|
44
146
|
}
|
|
45
147
|
finally {
|
|
46
148
|
setRebuildingId(null);
|
|
149
|
+
refetch();
|
|
47
150
|
}
|
|
48
|
-
}, [toast]);
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
151
|
+
}, [refetch, toast]);
|
|
152
|
+
const bulkActions = [
|
|
153
|
+
{
|
|
154
|
+
id: "activate",
|
|
155
|
+
label: "Activate",
|
|
156
|
+
variant: "primary",
|
|
157
|
+
onClick: async () => {
|
|
158
|
+
await Promise.all(selection.selectedIds.map((id) => fetch(ADMIN_ENDPOINTS.BUNDLE_BY_ID(id), {
|
|
159
|
+
method: "PUT",
|
|
160
|
+
headers: { "Content-Type": "application/json" },
|
|
161
|
+
body: JSON.stringify({ isActive: true }),
|
|
162
|
+
})));
|
|
163
|
+
selection.clearSelection();
|
|
164
|
+
refetch();
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
id: "deactivate",
|
|
169
|
+
label: "Deactivate",
|
|
170
|
+
variant: "secondary",
|
|
171
|
+
onClick: async () => {
|
|
172
|
+
await Promise.all(selection.selectedIds.map((id) => fetch(ADMIN_ENDPOINTS.BUNDLE_BY_ID(id), {
|
|
173
|
+
method: "PUT",
|
|
174
|
+
headers: { "Content-Type": "application/json" },
|
|
175
|
+
body: JSON.stringify({ isActive: false }),
|
|
176
|
+
})));
|
|
177
|
+
selection.clearSelection();
|
|
178
|
+
refetch();
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
id: "delete",
|
|
183
|
+
label: "Delete",
|
|
184
|
+
variant: "danger",
|
|
185
|
+
onClick: async () => {
|
|
186
|
+
await Promise.all(selection.selectedIds.map((id) => fetch(ADMIN_ENDPOINTS.BUNDLE_BY_ID(id), { method: "DELETE" })));
|
|
187
|
+
selection.clearSelection();
|
|
188
|
+
refetch();
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
];
|
|
192
|
+
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(Heading, { level: 1, className: "sr-only", children: "Bundles" }), _jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search bundles by name or slug\u2026", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => {
|
|
193
|
+
table.set("sort", v);
|
|
194
|
+
}, onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsx(Button, { asChild: true, size: "sm", variant: "primary", children: _jsxs("a", { href: newHref, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), BUNDLE_COPY.adminList.newButton] }) }) }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: bulkActions }), 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: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { columns: COLUMNS, rows: rows, isLoading: isLoading, emptyLabel: BUNDLE_COPY.adminList.empty, getRowHref: getEditHref, selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle, onToggleSelectAll: (next) => next
|
|
195
|
+
? selection.setSelectedIds(rows.map((r) => r.id))
|
|
196
|
+
: selection.clearSelection(), renderRowActions: (row) => (_jsx(Button, { variant: "ghost", size: "sm", isLoading: rebuildingId === row.id, disabled: rebuildingId === row.id, onClick: () => void handleRebuild(row.id), children: ACTIONS.ADMIN["rebuild-bundle"].label })) })] }), _jsx(BundlesFilterDrawer, { filterOpen: filterOpen, setFilterOpen: setFilterOpen, activeFilterCount: activeFilterCount, clearFilters: clearFilters, pendingFilters: pendingFilters, setPendingFilters: setPendingFilters, applyFilters: applyFilters })] }));
|
|
76
197
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function AdminCatalogGuideView(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Package, FolderTree, Tag, Star, AlertTriangle } from "lucide-react";
|
|
3
|
+
import { Div, Heading, Text, Section, Alert } from "../../../ui";
|
|
4
|
+
import { GC } from "../../_guide-cls";
|
|
5
|
+
export function AdminCatalogGuideView() {
|
|
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(Package, { 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: "Catalog" }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", children: "Products, categories, brands, and reviews \u2014 how the LetItRip catalog is structured and managed." })] }), [
|
|
7
|
+
{
|
|
8
|
+
Icon: Package, title: "Product Management",
|
|
9
|
+
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Listing types" }), ": ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "standard" }), " (prefix ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "product-" }), "), ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "auction" }), " (prefix ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "auction-" }), "), ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "pre-order" }), " (prefix ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "preorder-" }), ")."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Admin vs store creation" }), ": Admins can create products on behalf of any store via the store picker in AdminProductEditorView."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Status lifecycle" }), ": DRAFT \u2192 PUBLISHED \u2192 ARCHIVED. Published products appear in search. Archived products are hidden but not deleted."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "J13 rule" }), ": Always use ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "listingType" }), " field \u2014 the legacy ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "isAuction" }), "/", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "isPreOrder" }), " booleans have been removed. All queries use ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "where(\"listingType\", \"==\", x)" }), "."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Media URLs" }), ": Never write raw Firebase Storage URLs to Firestore. All product image URLs must use the ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "/api/media/[slug]" }), " proxy format."] })] })),
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
Icon: FolderTree, title: "Category Taxonomy",
|
|
13
|
+
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "3-tier system" }), ": Root (tier 1) \u2192 Subcategory (tier 2) \u2192 Leaf (tier 3). Only leaf categories can be assigned to products."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Key fields" }), ": ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "parentId" }), " points to the direct parent; ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "rootId" }), " always points to the tier-1 root; ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "path" }), " stores the full hierarchy path."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "isLeaf" }), ": Must be ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "true" }), " on any category that can be selected in the product form. Tier-1 and tier-2 categories must have ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "isLeaf: false" }), "."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Slug prefix" }), ": All category slugs start with ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "category-" }), "."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Adding a new root" }), ": Rare. Requires: ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "display.icon" }), ", decision on ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "showOnHomepage" }), ", and a senior admin sign-off."] })] })),
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
Icon: Tag, title: "Brands",
|
|
17
|
+
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Slug prefix" }), ": ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "brand-" }), ". Slugs are immutable after products reference them."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "displayOrder" }), ": Controls the sort order on public brand pages. Lower numbers appear first."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "isActive: false" }), ": Hides the brand from public discovery but preserves historical product links. Use this instead of deleting a brand."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "logoURL / bannerURL" }), ": Must use the ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "/api/media/" }), " proxy \u2014 never raw Storage URLs."] })] })),
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
Icon: Star, title: "Reviews",
|
|
21
|
+
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "isVerifiedPurchase" }), ": Set automatically by the system after order DELIVERED. Never manually toggle this field."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "sellerResponse" }), ": Written by the seller via their store dashboard. Admins should not edit seller responses \u2014 only remove clearly violating ones."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "When to delete vs hide" }), ": Delete for violations (PII, slurs, spam). Hide (move to unlisted) if a dispute is pending resolution."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "helpfulCount" }), ": Read-only \u2014 incremented by buyer votes. Do not manually edit this field."] })] })),
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
Icon: AlertTriangle, title: "Common Admin Mistakes",
|
|
25
|
+
content: (_jsx(Alert, { variant: "warning", children: _jsxs("ul", { className: "space-y-1 text-sm", children: [_jsx("li", { children: "\u2717 Publishing a product with no images \u2014 buyers cannot evaluate it." }), _jsxs("li", { children: ["\u2717 Creating a subcategory without setting ", _jsx("code", { className: "text-xs bg-amber-100 dark:bg-amber-900/30 px-1 rounded", children: "rootId" }), " \u2014 category tree breaks."] }), _jsxs("li", { children: ["\u2717 Setting ", _jsx("code", { className: "text-xs bg-amber-100 dark:bg-amber-900/30 px-1 rounded", children: "parentId" }), " of a root category to anything other than ", _jsx("code", { className: "text-xs bg-amber-100 dark:bg-amber-900/30 px-1 rounded", children: "null" }), " \u2014 creates circular hierarchy."] }), _jsx("li", { children: "\u2717 Duplicating brand slugs \u2014 Firestore document ID collision, second write silently overwrites the first." }), _jsx("li", { children: "\u2717 Writing raw Firebase Storage URLs to Firestore \u2014 breaks the media watermark proxy and CDN caching." })] }) })),
|
|
26
|
+
},
|
|
27
|
+
].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)))] }));
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function AdminContentGuideView(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { FileText, Calendar, BookOpen, Layout, Grid, Megaphone, Mail } from "lucide-react";
|
|
3
|
+
import { Div, Heading, Text, Section, Alert } from "../../../ui";
|
|
4
|
+
import { GC } from "../../_guide-cls";
|
|
5
|
+
export function AdminContentGuideView() {
|
|
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(FileText, { 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: "Content & Marketing" }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", children: "Blog posts, events, FAQs, carousel, homepage sections, ads, and newsletter on LetItRip." })] }), [
|
|
7
|
+
{
|
|
8
|
+
Icon: FileText, title: "Blog Posts",
|
|
9
|
+
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Statuses" }), ": ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "draft" }), " (not public), ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "published" }), " (live, indexed)."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "readTimeMinutes" }), ": Auto-calculated from content word count (200 wpm). Override manually if the post has heavy media."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "youtubeId" }), ": Optional embedded YouTube video. Use only the video ID (e.g. ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "dQw4w9WgXcQ" }), "), not the full URL."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Media in body" }), ": All images in rich text must use the ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "/api/media/" }), " proxy URL, not raw Firebase Storage links."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "isFeatured" }), ": Sets the post as featured in the blog listing. Different from the homepage featured section \u2014 that is driven by a separate ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "homepageSections" }), " config."] })] })),
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
Icon: Calendar, title: "Events",
|
|
13
|
+
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Event types" }), ": ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "sale" }), ", ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "offer" }), ", ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "poll" }), ", ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "survey" }), ", ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "feedback" }), ", ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "raffle" }), ", ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "spin_wheel" }), "."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Status" }), ": Computed from ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "startsAt" }), "/", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "endsAt" }), " (upcoming \u2192 active \u2192 ended). Do not set a status field manually \u2014 it will be overwritten."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "stats.totalEntries vs approvedEntries" }), ": ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "totalEntries" }), " = all submitted (including waitlisted); ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "approvedEntries" }), " = CONFIRMED only."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Cancelling an entry" }), ": Set status to CANCELLED in the entry doc. Do not delete entries \u2014 audit trail is needed for raffle fairness."] })] })),
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
Icon: BookOpen, title: "FAQs",
|
|
17
|
+
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Categories" }), " and where they appear: Shipping (help + footer), Returns (help), Payments (help), Auctions (help), Pre-orders (help). Set ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "showOnHomepage: true" }), " for up to 5 FAQs shown on the public home page."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "searchTokens[]" }), ": Must be manually filled \u2014 drives the FAQ search feature. Include synonyms, misspellings, and related keywords."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "priority + order" }), ": ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "priority" }), " sorts FAQs within their category; ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "order" }), " is the global absolute position. ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "isPinned: true" }), " floats the FAQ above non-pinned items in its category."] })] })),
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
Icon: Layout, title: "Carousel Slides",
|
|
21
|
+
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "MAX_ACTIVE_SLIDES = 5" }), ": You cannot activate a 6th slide. Deactivate an existing slide first."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "background.type" }), ": ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "image" }), ", ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "video" }), ", ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "color" }), ", ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "gradient" }), ". Do not have 2 active video slides simultaneously \u2014 auto-playing two videos degrades mobile performance."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Reordering" }), ": Drag slides in AdminCarouselView to reorder. The ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "order" }), " field is updated automatically."] }), _jsxs("li", { children: [_jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "cards[]" }), ": Each slide contains an array of content cards displayed as an overlay. Max 3 cards per slide recommended for readability."] })] })),
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
Icon: Grid, title: "Homepage Sections",
|
|
25
|
+
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "21 section types" }), ": product-grid, brand-grid, category-grid, event-highlight, blog-preview, etc. The full list and config shapes are in ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "appkit/src/features/homepage/schemas/firestore.ts" }), "."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "order" }), ": Controls render sequence on the public homepage. Lower numbers appear higher on the page."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "enabled: false" }), ": Hides the section without deleting it \u2014 useful for temporary removal."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Never edit Firestore directly" }), ": Always use AdminSectionsView. The config shapes are validated on write \u2014 direct Firestore edits can produce invalid shapes that crash the home page render."] })] })),
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
Icon: Megaphone, title: "Ads",
|
|
29
|
+
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: ["Ad slot keys correspond to specific locations on the page (e.g. ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "homepage-banner-top" }), ", ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "sidebar-right" }), ")."] }), _jsxs("li", { children: [_jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "isActive" }), " controls immediate visibility. ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "startDate" }), "/", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "endDate" }), " schedule automatic activation/deactivation."] }), _jsxs("li", { children: [_jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "impressions" }), " and ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "clicks" }), " are read-only counters \u2014 incremented by the ad-serving system. Do not edit manually."] })] })),
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
Icon: Mail, title: "Newsletter",
|
|
33
|
+
content: (_jsxs(_Fragment, { children: [_jsxs(Text, { className: "text-sm text-[var(--appkit-color-text-muted)] mb-3", children: ["Subscribers are stored in the ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "newsletter" }), " collection. The admin panel supports subscriber export and campaign send."] }), _jsx(Alert, { variant: "warning", children: "Legal obligation: every email sent must include a working unsubscribe link. Under the Indian IT Act and CAN-SPAM, unsubscribe requests must be honoured within 10 business days. Never send to unsubscribed addresses. All newsletter exports are de-identified by default." })] })),
|
|
34
|
+
},
|
|
35
|
+
].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)))] }));
|
|
36
|
+
}
|
|
@@ -55,7 +55,7 @@ export function AdminCouponEditorView({ couponId, onSaved, onDeleted, embedded,
|
|
|
55
55
|
// validity
|
|
56
56
|
const [startDate, setStartDate] = React.useState(new Date().toISOString().split("T")[0]);
|
|
57
57
|
const [endDate, setEndDate] = React.useState("");
|
|
58
|
-
const [isActive, setIsActive] = React.useState(
|
|
58
|
+
const [isActive, setIsActive] = React.useState(true);
|
|
59
59
|
// restrictions
|
|
60
60
|
const [firstTimeOnly, setFirstTimeOnly] = React.useState(false);
|
|
61
61
|
const [combinable, setCombinable] = React.useState(false);
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Users, Package, Store, ShoppingBag, FileText, Settings, Shield, BarChart2, AlertTriangle, BookOpen, } from "lucide-react";
|
|
3
|
+
import { Div, Heading, Text, Section, Alert } from "../../../ui";
|
|
4
|
+
import { ROUTES } from "../../../next/routing/route-map";
|
|
5
|
+
const GUIDE_CARDS = [
|
|
6
|
+
{
|
|
7
|
+
Icon: Users,
|
|
8
|
+
title: "Users & Accounts",
|
|
9
|
+
description: "Roles, search, user editor, sessions management, employee accounts, and PII handling.",
|
|
10
|
+
href: String(ROUTES.ADMIN.GUIDE_USERS),
|
|
11
|
+
permission: "admin:users:read",
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
Icon: Package,
|
|
15
|
+
title: "Catalog",
|
|
16
|
+
description: "Products, listing types, categories (3-tier taxonomy), brands, and reviews.",
|
|
17
|
+
href: String(ROUTES.ADMIN.GUIDE_CATALOG),
|
|
18
|
+
permission: "admin:products:read",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
Icon: Store,
|
|
22
|
+
title: "Stores & Sellers",
|
|
23
|
+
description: "Store lifecycle, identity architecture, capabilities, addresses, and suspension.",
|
|
24
|
+
href: String(ROUTES.ADMIN.GUIDE_STORES),
|
|
25
|
+
permission: "admin:stores:read",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
Icon: ShoppingBag,
|
|
29
|
+
title: "Orders & Finance",
|
|
30
|
+
description: "Order statuses, ID format, dispute intervention, payouts, returns, and commission math.",
|
|
31
|
+
href: String(ROUTES.ADMIN.GUIDE_ORDERS),
|
|
32
|
+
permission: "admin:orders:read",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
Icon: FileText,
|
|
36
|
+
title: "Content & Marketing",
|
|
37
|
+
description: "Blog posts, events, FAQs, carousel slides, homepage sections, ads, and newsletter.",
|
|
38
|
+
href: String(ROUTES.ADMIN.GUIDE_CONTENT),
|
|
39
|
+
permission: "admin:blog:read",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
Icon: Settings,
|
|
43
|
+
title: "Site Configuration",
|
|
44
|
+
description: "Site settings groups, feature flags, integrations, auction config, and legal pages.",
|
|
45
|
+
href: String(ROUTES.ADMIN.GUIDE_SITE),
|
|
46
|
+
permission: "admin:site:read",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
Icon: Users,
|
|
50
|
+
title: "Team & Permissions",
|
|
51
|
+
description: "Permission groups, employee invite flow, role vs permission difference, and RBAC model.",
|
|
52
|
+
href: String(ROUTES.ADMIN.GUIDE_TEAM),
|
|
53
|
+
permission: "admin:team:read",
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
Icon: BarChart2,
|
|
57
|
+
title: "Analytics",
|
|
58
|
+
description: "Revenue dashboard, order funnel, product performance, and store-level metrics.",
|
|
59
|
+
href: String(ROUTES.ADMIN.GUIDE_ANALYTICS),
|
|
60
|
+
permission: "admin:analytics:view",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
Icon: Shield,
|
|
64
|
+
title: "Trust & Safety",
|
|
65
|
+
description: "Bans (soft/hard), scam registry, support tickets, moderation queue, and reports.",
|
|
66
|
+
href: String(ROUTES.ADMIN.GUIDE_TRUST),
|
|
67
|
+
permission: "admin:moderation:read",
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
export function AdminGuideHubView({ permissions = [], isFullAdmin = false }) {
|
|
71
|
+
const hasPermission = (perm) => isFullAdmin || permissions.includes(perm);
|
|
72
|
+
const visibleCards = GUIDE_CARDS.filter((c) => hasPermission(c.permission));
|
|
73
|
+
const hasAny = visibleCards.length > 0;
|
|
74
|
+
return (_jsxs(Div, { className: "space-y-8 pb-10", children: [_jsx(Section, { className: "rounded-2xl overflow-hidden border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] shadow-sm", children: _jsxs(Div, { className: "px-6 py-8 md:px-10", style: {
|
|
75
|
+
background: "linear-gradient(135deg,var(--appkit-color-primary-700,#4f46e5) 0%,var(--appkit-color-cobalt,#2563eb) 60%,var(--appkit-color-secondary-400,#7c3aed) 100%)",
|
|
76
|
+
}, children: [_jsxs(Div, { className: "flex items-center gap-3 mb-3", children: [_jsx(Div, { className: "flex-shrink-0 w-10 h-10 rounded-xl bg-white/20 flex items-center justify-center", children: _jsx(BookOpen, { className: "w-5 h-5 text-white" }) }), _jsx(Text, { className: "text-sm font-semibold text-white/80 uppercase tracking-widest", children: "Admin & Employee Guide" })] }), _jsx(Heading, { level: 1, className: "text-2xl md:text-3xl font-bold text-white mb-2", children: "LetItRip Internal Guide" }), _jsx(Text, { className: "text-white/80 text-base", children: "Everything you need to operate and manage LetItRip \u2014 India's largest collectibles marketplace." })] }) }), _jsx(Alert, { variant: "info", compact: true, children: "These guides describe expected platform behaviour. Real access to each section is enforced independently by the permission system \u2014 this page is reference only." }), !hasAny && (_jsx(Alert, { variant: "info", title: "Limited access", children: "Your permission set doesn't include read access to any admin sections yet. Contact your admin to request the permissions you need." })), hasAny && (_jsxs(Section, { children: [_jsx(Heading, { level: 2, className: "text-lg font-semibold text-[var(--appkit-color-text)] mb-4", children: "Guides" }), _jsx(Div, { className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3", children: visibleCards.map(({ Icon, title, description, href }) => (_jsxs("a", { href: href, className: "group flex flex-col gap-3 rounded-xl border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] p-5 hover:border-[var(--appkit-color-primary)] hover:shadow-md transition-all", children: [_jsxs(Div, { className: "flex items-center gap-3", children: [_jsx(Div, { className: "flex-shrink-0 w-9 h-9 rounded-lg flex items-center justify-center", style: {
|
|
77
|
+
background: "linear-gradient(135deg,var(--appkit-color-primary-700,#4f46e5) 0%,var(--appkit-color-cobalt,#2563eb) 100%)",
|
|
78
|
+
}, children: _jsx(Icon, { className: "w-4 h-4 text-white" }) }), _jsx(Text, { className: "font-semibold text-[var(--appkit-color-text)] group-hover:text-[var(--appkit-color-primary)] transition-colors", children: title })] }), _jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)] leading-relaxed", children: description }), _jsx(Text, { className: "text-xs font-medium text-[var(--appkit-color-primary)] mt-auto", children: "Read guide \u2192" })] }, title))) })] })), _jsx(Section, { className: "rounded-2xl border border-amber-200 bg-amber-50 dark:bg-amber-950/20 dark:border-amber-800 p-6", children: _jsxs(Div, { className: "flex items-start gap-3", children: [_jsx(AlertTriangle, { className: "w-5 h-5 text-amber-600 dark:text-amber-400 flex-shrink-0 mt-0.5" }), _jsxs(Div, { children: [_jsx(Text, { className: "font-semibold text-amber-800 dark:text-amber-300 mb-1", children: "Remember: with great access comes responsibility" }), _jsx(Text, { className: "text-sm text-amber-700 dark:text-amber-400", children: "Admin actions are logged. Never access user PII (email, phone) without a legitimate support reason. Never grant capabilities or permissions without documented approval. When in doubt, escalate to a senior admin." })] })] }) })] }));
|
|
79
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function AdminOrdersGuideView(): import("react/jsx-runtime").JSX.Element;
|