@mohasinac/appkit 2.7.57 → 2.7.59
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/DashboardLayoutClient.d.ts +7 -1
- package/dist/_internal/client/features/layout/DashboardLayoutClient.js +8 -2
- package/dist/_internal/server/features/scams/og.js +1 -1
- package/dist/_internal/shared/actions/bulk-helpers.d.ts +3 -0
- package/dist/_internal/shared/actions/bulk-helpers.js +19 -0
- package/dist/_internal/shared/config/schema.d.ts +4 -4
- package/dist/client.d.ts +4 -1
- package/dist/client.js +3 -1
- package/dist/configs/next.js +11 -0
- package/dist/configs/tailwind.d.ts +16 -3
- package/dist/configs/tailwind.js +18 -5
- package/dist/features/account/components/AddressBook.js +1 -1
- package/dist/features/account/components/AddressesIndexListing.js +3 -4
- package/dist/features/account/components/BuyerAccountGuideView.js +7 -7
- package/dist/features/account/components/BuyerAuctionsGuideView.js +7 -7
- package/dist/features/account/components/BuyerOrdersGuideView.js +6 -6
- package/dist/features/account/components/BuyerShoppingGuideView.js +7 -7
- package/dist/features/account/components/UserOffersPanel.js +1 -1
- package/dist/features/account/components/UserSidebar.js +14 -10
- package/dist/features/account/components/UserSupportView.js +4 -4
- package/dist/features/admin/components/AdminAdsView.js +1 -1
- package/dist/features/admin/components/AdminAllEventEntriesView.js +4 -10
- package/dist/features/admin/components/AdminAnalyticsGuideView.js +3 -3
- package/dist/features/admin/components/AdminBidsView.js +3 -9
- package/dist/features/admin/components/AdminBlogView.js +4 -9
- package/dist/features/admin/components/AdminBrandEditorView.js +14 -17
- package/dist/features/admin/components/AdminBrandsView.js +4 -9
- package/dist/features/admin/components/AdminBundlesView.js +40 -59
- package/dist/features/admin/components/AdminCarouselView.js +8 -21
- package/dist/features/admin/components/AdminCartsView.js +3 -4
- package/dist/features/admin/components/AdminCatalogGuideView.js +5 -5
- package/dist/features/admin/components/AdminCategoriesView.js +4 -9
- package/dist/features/admin/components/AdminCategoryEditorView.js +1 -5
- package/dist/features/admin/components/AdminContactEditorView.js +2 -2
- package/dist/features/admin/components/AdminContactView.js +7 -25
- package/dist/features/admin/components/AdminContentGuideView.js +6 -6
- package/dist/features/admin/components/AdminCouponEditorView.js +14 -17
- package/dist/features/admin/components/AdminCouponsView.js +8 -16
- package/dist/features/admin/components/AdminFaqEditorView.js +14 -17
- package/dist/features/admin/components/AdminFaqsView.js +18 -69
- package/dist/features/admin/components/AdminFeatureEditorView.js +7 -9
- package/dist/features/admin/components/AdminFeatureFlagsView.js +1 -1
- package/dist/features/admin/components/AdminFeaturesView.js +1 -1
- package/dist/features/admin/components/AdminGuideHubView.js +1 -1
- package/dist/features/admin/components/AdminHistoryView.js +1 -1
- package/dist/features/admin/components/AdminListingScaffold.js +1 -1
- package/dist/features/admin/components/AdminNewsletterView.js +5 -15
- package/dist/features/admin/components/AdminNotificationsView.js +3 -9
- package/dist/features/admin/components/AdminOrdersGuideView.js +5 -5
- package/dist/features/admin/components/AdminOrdersView.js +3 -9
- package/dist/features/admin/components/AdminPayoutsView.js +3 -9
- package/dist/features/admin/components/AdminPrizeDrawsView.js +4 -9
- package/dist/features/admin/components/AdminProductEditorView.js +1 -5
- package/dist/features/admin/components/AdminProductsView.js +5 -10
- package/dist/features/admin/components/AdminReturnRequestsView.js +1 -1
- package/dist/features/admin/components/AdminReviewsView.js +3 -9
- package/dist/features/admin/components/AdminScammerEditorView.js +3 -3
- package/dist/features/admin/components/AdminScammersView.js +6 -12
- package/dist/features/admin/components/AdminSectionsView.js +1 -1
- package/dist/features/admin/components/AdminSessionsView.js +3 -9
- package/dist/features/admin/components/AdminSiteSettingsView.js +1 -1
- package/dist/features/admin/components/AdminStoreAddressesView.js +1 -1
- package/dist/features/admin/components/AdminStoresGuideView.js +6 -6
- package/dist/features/admin/components/AdminStoresView.js +3 -9
- package/dist/features/admin/components/AdminSublistingCategoriesView.js +3 -7
- package/dist/features/admin/components/AdminSublistingCategoryEditorView.js +17 -20
- package/dist/features/admin/components/AdminSupportTicketDetailView.js +4 -4
- package/dist/features/admin/components/AdminSupportTicketsView.js +9 -15
- package/dist/features/admin/components/AdminTeamGuideView.js +4 -4
- package/dist/features/admin/components/AdminTeamView.js +6 -15
- package/dist/features/admin/components/AdminTrustGuideView.js +5 -5
- package/dist/features/admin/components/AdminUserEditorView.js +1 -1
- package/dist/features/admin/components/AdminUsersGuideView.js +5 -5
- package/dist/features/admin/components/AdminUsersView.js +3 -9
- package/dist/features/admin/components/AdminViewCards.js +1 -1
- package/dist/features/admin/components/AdminWishlistsView.js +1 -1
- package/dist/features/admin/components/DataTable.js +1 -1
- package/dist/features/admin/hooks/useAdminListing.d.ts +58 -0
- package/dist/features/admin/hooks/useAdminListing.js +71 -0
- package/dist/features/auctions/components/AuctionBidsTable.js +1 -1
- package/dist/features/auctions/components/AuctionDetailPageView.js +2 -2
- package/dist/features/auctions/components/MarketplaceAuctionCard.js +2 -2
- package/dist/features/auctions/components/PlaceBidFormClient.js +1 -1
- package/dist/features/auth/components/VerifyEmailView.js +1 -1
- package/dist/features/auth/schemas/firestore.d.ts +1 -0
- package/dist/features/blog/components/BlogIndexListing.js +3 -6
- package/dist/features/blog/components/BlogListView.js +1 -1
- package/dist/features/blog/components/BlogPostView.js +1 -1
- package/dist/features/cart/components/CartDrawer.js +1 -1
- package/dist/features/categories/components/CategoriesIndexListing.js +3 -6
- package/dist/features/categories/components/CategoryDetailPageView.js +1 -1
- package/dist/features/checkout/components/CheckoutStepper.js +1 -1
- package/dist/features/collections/components/CollectionCard.js +1 -1
- package/dist/features/copilot/components/AdminCopilotView.js +2 -2
- package/dist/features/events/components/AdminEventsView.js +4 -4
- package/dist/features/events/components/EventPollWidget.js +2 -2
- package/dist/features/events/components/EventStatusBadge.js +4 -4
- package/dist/features/events/components/EventsIndexListing.js +3 -4
- package/dist/features/events/components/SpinWheelView.js +1 -1
- package/dist/features/faq/components/FAQAccordion.js +2 -2
- package/dist/features/forms/Input.js +3 -3
- package/dist/features/forms/Select.js +2 -2
- package/dist/features/forms/Textarea.js +2 -2
- package/dist/features/forms/utils.d.ts +1 -1
- package/dist/features/forms/utils.js +1 -1
- package/dist/features/homepage/components/AnnouncementBar.d.ts +4 -1
- package/dist/features/homepage/components/AnnouncementBar.js +51 -4
- package/dist/features/homepage/components/CharacterHotspotForm.js +2 -2
- package/dist/features/homepage/components/MarketplaceHomepageView.d.ts +3 -1
- package/dist/features/homepage/components/MarketplaceHomepageView.js +2 -2
- package/dist/features/homepage/components/index.d.ts +1 -1
- package/dist/features/homepage/components/index.js +1 -1
- package/dist/features/homepage/lib/section-renderer.d.ts +2 -2
- package/dist/features/homepage/lib/section-renderer.js +2 -2
- package/dist/features/layout/BottomActions.js +2 -2
- package/dist/features/layout/ListingLayout.js +3 -3
- package/dist/features/layout/TitleBarLayout.js +1 -1
- package/dist/features/media/index.d.ts +2 -0
- package/dist/features/media/index.js +1 -0
- package/dist/features/media/modals/ImageCropModal.js +1 -1
- package/dist/features/media/modals/ImageEditor.d.ts +12 -0
- package/dist/features/media/modals/ImageEditor.js +73 -0
- package/dist/features/media/upload/ImageUpload.d.ts +5 -1
- package/dist/features/media/upload/ImageUpload.js +33 -8
- package/dist/features/media/upload/MediaUploadField.d.ts +5 -1
- package/dist/features/media/upload/MediaUploadField.js +26 -12
- package/dist/features/orders/components/OrdersList.js +7 -7
- package/dist/features/orders/components/RefundHistoryTable.js +2 -2
- package/dist/features/orders/components/RefundRequestView.js +3 -3
- package/dist/features/orders/repository/orders.repository.d.ts +0 -1
- package/dist/features/orders/repository/orders.repository.js +0 -1
- package/dist/features/orders/server.d.ts +1 -1
- package/dist/features/orders/server.js +1 -1
- package/dist/features/pre-orders/components/PreOrderActionsClient.js +1 -1
- package/dist/features/pre-orders/components/PreOrderBadge.js +4 -4
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +1 -1
- package/dist/features/pre-orders/components/PreorderCard.js +2 -2
- package/dist/features/products/components/CustomSectionsEditor.js +1 -1
- package/dist/features/products/components/GroupSettingsPanel.js +64 -48
- package/dist/features/products/components/MakeOfferButton.js +2 -2
- package/dist/features/products/components/PrizeDrawDetailPageView.js +2 -2
- package/dist/features/products/components/PrizeDrawItemsEditor.js +3 -3
- package/dist/features/products/components/PrizeRevealModal.js +3 -3
- package/dist/features/products/components/ProductDetailPageView.js +4 -4
- package/dist/features/products/components/ProductFeaturesSelector.js +1 -1
- package/dist/features/products/components/ProductGrid.js +3 -3
- package/dist/features/products/repository/products.repository.d.ts +0 -2
- package/dist/features/products/repository/products.repository.js +0 -2
- package/dist/features/products/server.d.ts +1 -1
- package/dist/features/products/server.js +1 -1
- package/dist/features/promotions/components/CouponCard.js +7 -7
- package/dist/features/promotions/components/CouponsIndexListing.js +3 -2
- package/dist/features/promotions/components/PromotionsView.js +1 -1
- package/dist/features/reviews/components/ReviewDetailShell.js +2 -2
- package/dist/features/reviews/components/ReviewsIndexListing.js +3 -4
- package/dist/features/reviews/components/ReviewsList.js +1 -1
- package/dist/features/reviews/repository/reviews.repository.d.ts +0 -1
- package/dist/features/reviews/repository/reviews.repository.js +0 -1
- package/dist/features/reviews/server.d.ts +1 -1
- package/dist/features/reviews/server.js +1 -1
- package/dist/features/seller/components/SellerAddressesView.js +11 -21
- package/dist/features/seller/components/SellerAnalyticsAlertsView.js +2 -2
- package/dist/features/seller/components/SellerAuctionsView.js +14 -23
- package/dist/features/seller/components/SellerBidsView.js +6 -6
- package/dist/features/seller/components/SellerBundlesView.js +13 -21
- package/dist/features/seller/components/SellerClassifiedView.js +14 -22
- package/dist/features/seller/components/SellerCouponEditorView.js +2 -2
- package/dist/features/seller/components/SellerCouponsView.js +14 -20
- package/dist/features/seller/components/SellerDigitalCodesView.js +13 -21
- package/dist/features/seller/components/SellerFeaturesView.js +1 -1
- package/dist/features/seller/components/SellerGroupedListingsView.js +6 -10
- package/dist/features/seller/components/SellerLiveView.js +15 -23
- package/dist/features/seller/components/SellerOffersPanel.js +2 -2
- package/dist/features/seller/components/SellerOffersView.js +4 -5
- package/dist/features/seller/components/SellerOrdersView.js +9 -23
- package/dist/features/seller/components/SellerPayoutMethodsView.js +13 -25
- package/dist/features/seller/components/SellerPayoutsView.js +4 -5
- package/dist/features/seller/components/SellerPreOrdersView.js +13 -22
- package/dist/features/seller/components/SellerPrizeDrawsView.js +13 -22
- package/dist/features/seller/components/SellerProductsCards.js +1 -1
- package/dist/features/seller/components/SellerProductsFilterDrawer.d.ts +1 -1
- package/dist/features/seller/components/SellerProductsFilterDrawer.js +3 -6
- package/dist/features/seller/components/SellerProductsView.js +10 -23
- package/dist/features/seller/components/SellerReviewsView.js +1 -1
- package/dist/features/seller/components/SellerShippingConfigsView.js +14 -26
- package/dist/features/seller/components/SellerStoreCategoriesView.js +16 -35
- package/dist/features/seller/components/SellerTemplatesView.js +27 -43
- package/dist/features/seller/components/analytics/SellerTopProducts.js +1 -1
- package/dist/features/stores/components/StoreFinanceGuideView.js +2 -2
- package/dist/features/stores/components/StoreHeader.js +1 -1
- package/dist/features/stores/components/StoreListingsGuideView.js +3 -3
- package/dist/features/stores/components/StoreOrdersGuideView.js +4 -4
- package/dist/features/stores/components/StoreReviewsListing.js +3 -4
- package/dist/features/stores/components/StoreSettingsGuideView.js +2 -2
- package/dist/features/stores/components/StoresIndexListing.js +3 -4
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +4 -4
- package/dist/features/wishlist/components/WishlistPage.js +1 -1
- package/dist/index.d.ts +4 -6
- package/dist/index.js +6 -12
- package/dist/react/contexts/SessionContext.d.ts +2 -0
- package/dist/react/contexts/SessionContext.js +2 -0
- package/dist/react/hooks/useEntityDelete.d.ts +15 -0
- package/dist/react/hooks/useEntityDelete.js +39 -0
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.js +2 -0
- package/dist/repositories/index.d.ts +3 -3
- package/dist/repositories/index.js +3 -3
- package/dist/styles.css +286 -3
- package/dist/tailwind-utilities.css +1 -1
- package/dist/tokens/motion.d.ts +162 -0
- package/dist/tokens/motion.js +89 -0
- package/dist/ui/DataTable.js +3 -2
- package/dist/ui/components/Accordion.js +2 -1
- package/dist/ui/components/Card.d.ts +5 -2
- package/dist/ui/components/Card.js +29 -11
- package/dist/ui/components/Div.d.ts +2 -16
- package/dist/ui/components/Div.js +2 -16
- package/dist/ui/components/Layout.d.ts +9 -19
- package/dist/ui/components/Layout.js +9 -7
- package/dist/ui/components/ListingFilterDrawer.d.ts +10 -0
- package/dist/ui/components/ListingFilterDrawer.js +8 -0
- package/dist/ui/components/Modal.js +7 -4
- package/dist/ui/components/Motion.d.ts +75 -0
- package/dist/ui/components/Motion.js +122 -0
- package/dist/ui/components/Semantic.d.ts +81 -14
- package/dist/ui/components/Semantic.js +75 -10
- package/dist/ui/components/Semantic.style.css +261 -2
- package/dist/ui/components/SideDrawer.d.ts +1 -1
- package/dist/ui/components/SideDrawer.js +8 -6
- package/dist/ui/components/SideModal.d.ts +1 -1
- package/dist/ui/components/SideModal.js +11 -9
- package/dist/ui/components/StackedViewShell.js +6 -1
- package/dist/ui/components/Toast.js +5 -2
- package/dist/ui/components/Typography.d.ts +44 -3
- package/dist/ui/components/Typography.js +15 -30
- package/dist/ui/components/Typography.style.css +24 -0
- package/dist/ui/components/VacationBanner.js +2 -1
- package/dist/ui/components/surface-tokens.d.ts +70 -0
- package/dist/ui/components/surface-tokens.js +68 -0
- package/dist/ui/forms/FormShell.d.ts +6 -1
- package/dist/ui/forms/FormShell.js +32 -4
- package/dist/ui/index.d.ts +11 -2
- package/dist/ui/index.js +5 -2
- package/dist/validation/schemas.d.ts +28 -28
- package/package.json +3 -1
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Store, GitBranch, Edit, Shield, MapPin, Ban } from "lucide-react";
|
|
3
|
-
import { Div, Heading, Text, Section, Alert } from "../../../ui";
|
|
3
|
+
import { Div, Heading, Span, Text, Section, Alert } from "../../../ui";
|
|
4
4
|
import { GC } from "../../_guide-cls";
|
|
5
5
|
export function AdminStoresGuideView() {
|
|
6
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
7
|
{
|
|
8
8
|
Icon: Store, title: "Store Lifecycle",
|
|
9
|
-
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx("
|
|
9
|
+
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx(Span, { weight: "bold", 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(Span, { weight: "bold", 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(Span, { weight: "bold", 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
10
|
},
|
|
11
11
|
{
|
|
12
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("
|
|
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(Span, { weight: "bold", className: "text-[var(--appkit-color-text)]", children: "Why separate?" }), " Allows future store ownership transfer without rewriting all product/order documents."] }), _jsxs("li", { children: [_jsx(Span, { weight: "bold", 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(Span, { weight: "bold", 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
14
|
},
|
|
15
15
|
{
|
|
16
16
|
Icon: Edit, title: "Store Editor Walkthrough",
|
|
17
|
-
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx("
|
|
17
|
+
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx(Span, { weight: "bold", className: "text-[var(--appkit-color-text)]", children: "storeName / storeDescription" }), ": Public-facing. Shown on the store profile page."] }), _jsxs("li", { children: [_jsx(Span, { weight: "bold", className: "text-[var(--appkit-color-text)]", children: "ownerId" }), ": Read-only after store creation. Changing ownership requires engineering involvement."] }), _jsxs("li", { children: [_jsx(Span, { weight: "bold", 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(Span, { weight: "bold", 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
18
|
},
|
|
19
19
|
{
|
|
20
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("
|
|
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(Span, { weight: "bold", children: "lower_commission_rate" }), " (commission rate change agreement), ", _jsx(Span, { weight: "bold", children: "extended_return_window" }), " (operational review), ", _jsx(Span, { weight: "bold", children: "api_access" }), " (technical integration agreement). Do not toggle these without a senior admin sign-off on file."] })] })),
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
24
|
Icon: MapPin, title: "Store Addresses (Pickup Locations)",
|
|
@@ -26,7 +26,7 @@ export function AdminStoresGuideView() {
|
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
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("
|
|
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(Span, { weight: "bold", 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
30
|
},
|
|
31
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
32
|
}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import React, { useState, useCallback } from "react";
|
|
4
|
-
import { X } from "lucide-react";
|
|
5
4
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
6
5
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
7
6
|
import { usePanelUrlSync } from "../../../react/hooks/use-panel-url-sync";
|
|
8
7
|
import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
|
|
9
|
-
import { BulkActionBar, FilterChipGroup, ListingToolbar, Pagination, ListingViewShell, RowActionMenu, useToast } from "../../../ui";
|
|
8
|
+
import { BulkActionBar, FilterChipGroup, ListingToolbar, Pagination, ListingViewShell, RowActionMenu, useToast, ListingFilterDrawer } from "../../../ui";
|
|
10
9
|
import { AdminViewCards } from "./AdminViewCards";
|
|
11
10
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
12
11
|
import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
|
|
@@ -25,11 +24,6 @@ const SORT_OPTIONS = [
|
|
|
25
24
|
{ value: "storeName", label: "Name A–Z" },
|
|
26
25
|
];
|
|
27
26
|
const STATUS_OPTIONS = ADMIN_STORE_STATUS_TABS;
|
|
28
|
-
function StoresFilterDrawer({ filterOpen, setFilterOpen, activeFilterCount, clearFilters, pendingFilters, setPendingFilters, applyFilters, }) {
|
|
29
|
-
if (!filterOpen)
|
|
30
|
-
return null;
|
|
31
|
-
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 dark:text-zinc-400 hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4 space-y-5", children: _jsx(FilterChipGroup, { label: "Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }) }), _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})` : ""] }) })] })] }));
|
|
32
|
-
}
|
|
33
27
|
export function AdminStoresView({ children, ...props }) {
|
|
34
28
|
const hasChildren = React.Children.count(children) > 0;
|
|
35
29
|
const [view, setView] = useState("table");
|
|
@@ -115,7 +109,7 @@ export function AdminStoresView({ children, ...props }) {
|
|
|
115
109
|
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search stores, slugs, or owner 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 }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
|
|
116
110
|
{ id: "manage", label: ACTIONS.ADMIN["manage-store"].label, variant: "primary", onClick: () => { const id = selection.selectedIds[0]; if (id)
|
|
117
111
|
openEditPanel(id); selection.clearSelection(); } },
|
|
118
|
-
] }), 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-
|
|
112
|
+
] }), 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-error-surface px-4 py-3 text-sm text-error dark:border-red-900/60", children: errorMessage })), view === "table" ? (_jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No stores found", selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle, onToggleSelectAll: (next) => next ? selection.setSelectedIds(rows.map(r => r.id)) : selection.clearSelection(), renderRowActions: (row) => {
|
|
119
113
|
const sr = row;
|
|
120
114
|
const isSuspended = sr.status?.toLowerCase() === "suspended";
|
|
121
115
|
const isVerified = Boolean(sr._raw?.isVerified);
|
|
@@ -139,7 +133,7 @@ export function AdminStoresView({ children, ...props }) {
|
|
|
139
133
|
disabled: isSuspended || suspendStore.isPending,
|
|
140
134
|
},
|
|
141
135
|
] }));
|
|
142
|
-
} })) : (_jsx(AdminViewCards, { rows: rows, view: view, isLoading: isLoading, emptyLabel: "No stores found", onRowClick: (row) => openEditPanel(row.id), selectedIdSet: selection.selectedIdSet, onToggleSelect: selection.toggle }))] }), _jsx(
|
|
136
|
+
} })) : (_jsx(AdminViewCards, { rows: rows, view: view, isLoading: isLoading, emptyLabel: "No stores found", onRowClick: (row) => openEditPanel(row.id), selectedIdSet: selection.selectedIdSet, onToggleSelect: selection.toggle }))] }), _jsx(ListingFilterDrawer, { open: filterOpen, onClose: () => setFilterOpen(false), onApply: applyFilters, onClear: clearFilters, activeCount: activeFilterCount, children: _jsx(FilterChipGroup, { label: "Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }) })] }), _jsx(AdminStoreEditorView, { open: isEditOpen, onClose: closePanel, storeId: editId ?? undefined, storeName: panelRow?.primary, currentStatus: panelRow?.status?.toLowerCase(), currentIsVerified: Boolean(panelRow?._raw?.isVerified), currentCapabilities: Array.isArray(panelRow?._raw?.capabilities)
|
|
143
137
|
? panelRow._raw.capabilities
|
|
144
138
|
: undefined })] }));
|
|
145
139
|
}
|
|
@@ -7,6 +7,7 @@ import { BulkActionBar, ListingToolbar, Pagination } from "../../../ui";
|
|
|
7
7
|
import { useBottomActions } from "../../layout";
|
|
8
8
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
9
9
|
import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
|
|
10
|
+
import { buildBulkAction } from "../../../_internal/shared/actions/bulk-helpers";
|
|
10
11
|
import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
|
|
11
12
|
import { DataTable } from "./DataTable";
|
|
12
13
|
const PAGE_SIZE = 25;
|
|
@@ -54,15 +55,10 @@ export function AdminSublistingCategoriesView({ onBulkDelete }) {
|
|
|
54
55
|
const totalPages = Math.ceil(total / PAGE_SIZE);
|
|
55
56
|
const selection = useBulkSelection({ items: rows ?? [], keyExtractor: (r) => r.id });
|
|
56
57
|
const bulkActions = [
|
|
57
|
-
...(onBulkDelete ? [{
|
|
58
|
-
id: "bulk-delete",
|
|
59
|
-
label: ACTIONS.ADMIN["delete-sublisting-category"].label,
|
|
60
|
-
variant: "danger",
|
|
61
|
-
onClick: async () => { await onBulkDelete(selection.selectedIds); selection.clearSelection(); },
|
|
62
|
-
}] : []),
|
|
58
|
+
...(onBulkDelete ? [buildBulkAction(ACTIONS.ADMIN["delete-sublisting-category"], async () => { await onBulkDelete(selection.selectedIds); selection.clearSelection(); })] : []),
|
|
63
59
|
];
|
|
64
60
|
useBottomActions(selection.selectedCount > 0 ? { bulk: { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: bulkActions } } : {});
|
|
65
61
|
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { searchValue: searchInput, searchPlaceholder: "Search sub-listing categories\u2026", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => {
|
|
66
62
|
table.set("sort", v);
|
|
67
|
-
}, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState }), selection.selectedCount > 0 && bulkActions.length > 0 && (_jsx(BulkActionBar, { selectedCount: selection.selectedCount, actions: bulkActions, onClearSelection: selection.clearSelection })), 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-
|
|
63
|
+
}, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState }), selection.selectedCount > 0 && bulkActions.length > 0 && (_jsx(BulkActionBar, { selectedCount: selection.selectedCount, actions: bulkActions, onClearSelection: selection.clearSelection })), 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-error-surface px-4 py-3 text-sm text-error dark:border-red-900/60", children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No sub-listing categories found" })] })] }));
|
|
68
64
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import React from "react";
|
|
4
4
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
5
|
-
import { Button, Form, Input, StackedViewShell, useToast, } from "../../../ui";
|
|
5
|
+
import { Button, ConfirmDeleteModal, Form, Input, StackedViewShell, useToast, } from "../../../ui";
|
|
6
6
|
import { FieldInput, FormShellContext, useFormShellState } from "../../../ui/forms";
|
|
7
7
|
import { ImageUpload } from "../../media/upload/ImageUpload";
|
|
8
8
|
import { useMediaUpload } from "../../media";
|
|
@@ -15,6 +15,7 @@ export function AdminSublistingCategoryEditorView({ categoryId, onSaved, onDelet
|
|
|
15
15
|
const [itemCode, setItemCode] = React.useState("");
|
|
16
16
|
const [description, setDescription] = React.useState("");
|
|
17
17
|
const [coverImage, setCoverImage] = React.useState("");
|
|
18
|
+
const [deleteConfirmOpen, setDeleteConfirmOpen] = React.useState(false);
|
|
18
19
|
const categoryQuery = useQuery({
|
|
19
20
|
queryKey: ["admin", "sublisting-category", categoryId],
|
|
20
21
|
queryFn: async () => {
|
|
@@ -69,22 +70,18 @@ export function AdminSublistingCategoryEditorView({ categoryId, onSaved, onDelet
|
|
|
69
70
|
const { upload } = useMediaUpload();
|
|
70
71
|
const isSubmitting = saveMutation.isPending || categoryQuery.isLoading;
|
|
71
72
|
const { shellCtx, setFieldError, clearErrors } = useFormShellState();
|
|
72
|
-
return (_jsx(StackedViewShell, { portal: "admin", ...rest, title: isEdit ? "Edit Sub-listing Category" : "New Sub-listing Category", sections: [
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
deleteMutation.mutate();
|
|
87
|
-
}
|
|
88
|
-
}, children: "Delete" }))] })] }, "sc-editor-form") }, "sc-ctx"),
|
|
89
|
-
] }));
|
|
73
|
+
return (_jsxs(_Fragment, { children: [_jsx(StackedViewShell, { portal: "admin", ...rest, title: isEdit ? "Edit Sub-listing Category" : "New Sub-listing Category", sections: [
|
|
74
|
+
_jsx(FormShellContext.Provider, { value: shellCtx, children: _jsxs(Form, { onSubmit: (e) => {
|
|
75
|
+
e.preventDefault();
|
|
76
|
+
clearErrors();
|
|
77
|
+
if (!name.trim()) {
|
|
78
|
+
setFieldError("name", "Category name is required");
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
saveMutation.mutate();
|
|
82
|
+
}, className: "space-y-4", children: [_jsxs("div", { className: "grid sm:grid-cols-2 gap-4", children: [_jsx(FieldInput, { name: "name", label: "Category name", value: name, onChange: (v) => setName(v), required: true, placeholder: "e.g. Base Set Charizard 108/120" }), _jsx(Input, { label: "Item code", value: itemCode, onChange: (e) => setItemCode(e.target.value), placeholder: "e.g. PSA 10, 108/120, STH", helperText: "Grade, card number, or series code. Optional." })] }), _jsx(Input, { label: "Description", value: description, onChange: (e) => setDescription(e.target.value), placeholder: "Brief description shown on the public category page" }), _jsx(ImageUpload, { label: "Cover image", currentImage: coverImage, onUpload: (file) => upload(file, "sublisting-categories", true, {
|
|
83
|
+
type: "category-image",
|
|
84
|
+
name: name || "sublisting",
|
|
85
|
+
}), onChange: setCoverImage }), _jsxs("div", { className: "flex gap-3 pt-2", children: [_jsx(Button, { type: "submit", isLoading: isSubmitting, disabled: !name || isSubmitting, children: isEdit ? "Save changes" : "Create category" }), isEdit && (_jsx(Button, { type: "button", variant: "danger", isLoading: deleteMutation.isPending, onClick: () => setDeleteConfirmOpen(true), children: "Delete" }))] })] }, "sc-editor-form") }, "sc-ctx"),
|
|
86
|
+
] }), deleteConfirmOpen && (_jsx(ConfirmDeleteModal, { isOpen: true, title: "Delete Sublisting Category", message: "Delete this category? All linked listings will be unlinked. This cannot be undone.", onConfirm: () => { deleteMutation.mutate(); setDeleteConfirmOpen(false); }, onClose: () => setDeleteConfirmOpen(false), isDeleting: deleteMutation.isPending }))] }));
|
|
90
87
|
}
|
|
@@ -19,10 +19,10 @@ const PRIORITY_OPTIONS = [
|
|
|
19
19
|
{ label: "Urgent", value: "urgent" },
|
|
20
20
|
];
|
|
21
21
|
const STATUS_COLOR = {
|
|
22
|
-
open: "bg-
|
|
23
|
-
in_progress: "bg-
|
|
22
|
+
open: "bg-info-surface text-info",
|
|
23
|
+
in_progress: "bg-warning-surface text-warning",
|
|
24
24
|
waiting_on_user: "bg-purple-100 text-purple-700 dark:bg-purple-900/40 dark:text-purple-300",
|
|
25
|
-
resolved: "bg-
|
|
25
|
+
resolved: "bg-success-surface text-success",
|
|
26
26
|
closed: "bg-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-400",
|
|
27
27
|
};
|
|
28
28
|
const ROLE_LABEL = {
|
|
@@ -79,5 +79,5 @@ export function AdminSupportTicketDetailView({ open, onClose, ticketId, subject,
|
|
|
79
79
|
});
|
|
80
80
|
return (_jsx(SideDrawer, { isOpen: open, onClose: onClose, title: subject ?? "Support Ticket", children: _jsxs(Div, { className: "flex flex-col gap-4 p-4", children: [_jsxs(Div, { className: "flex flex-wrap items-center gap-2", children: [_jsx("span", { className: `inline-flex rounded-full px-2.5 py-1 text-xs font-medium ${STATUS_COLOR[currentStatus ?? "open"] ?? STATUS_COLOR.open}`, children: (currentStatus ?? "open").replace(/_/g, " ") }), _jsx("span", { className: "rounded-full bg-zinc-100 px-2.5 py-1 text-xs text-zinc-600 dark:bg-zinc-800 dark:text-zinc-300", children: category }), orderId && (_jsxs("span", { className: "rounded-full bg-zinc-100 px-2.5 py-1 text-xs text-zinc-600 dark:bg-zinc-800 dark:text-zinc-300", children: ["Order: ", orderId] }))] }), _jsxs(Div, { className: "text-sm text-zinc-500 dark:text-zinc-400", children: ["From: ", _jsx("span", { className: "font-medium text-zinc-700 dark:text-zinc-200", children: userDisplayName })] }), description && (_jsxs(Div, { className: "rounded-lg border border-zinc-200 bg-zinc-50 p-3 dark:border-zinc-700 dark:bg-zinc-900/40", children: [_jsx(Text, { className: "mb-1 text-xs font-semibold text-zinc-500 dark:text-zinc-400 uppercase tracking-wide", children: "Description" }), _jsx(Text, { className: "whitespace-pre-wrap text-sm text-zinc-700 dark:text-zinc-200", children: description })] })), messages.length > 0 && (_jsxs(Div, { className: "space-y-2", children: [_jsxs(Text, { className: "text-xs font-semibold text-zinc-500 dark:text-zinc-400 uppercase tracking-wide", children: ["Messages (", messages.length, ")"] }), _jsx("div", { className: "space-y-2 max-h-64 overflow-y-auto", children: messages.map((msg, i) => (_jsxs("div", { className: `rounded-lg p-3 text-sm ${msg.authorRole === "user"
|
|
81
81
|
? "bg-zinc-50 border border-zinc-200 dark:bg-zinc-900/40 dark:border-zinc-700"
|
|
82
|
-
: "bg-
|
|
82
|
+
: "bg-info-surface border border-blue-200 dark:border-blue-800"}`, children: [_jsxs("div", { className: "mb-1 flex items-center gap-2 text-xs text-zinc-400 dark:text-zinc-400", children: [_jsx("span", { className: "font-medium text-zinc-600 dark:text-zinc-300", children: ROLE_LABEL[msg.authorRole ?? "user"] ?? msg.authorRole }), msg.createdAt && (_jsx("span", { children: new Date(msg.createdAt).toLocaleString() }))] }), _jsx(Text, { className: "whitespace-pre-wrap text-zinc-700 dark:text-zinc-200", children: msg.body })] }, msg.id ?? i))) })] })), _jsxs(Div, { className: "flex flex-col gap-1", children: [_jsx("label", { className: "text-xs font-semibold text-zinc-500 dark:text-zinc-400 uppercase tracking-wide", children: "Reply to user" }), _jsx("textarea", { value: replyBody, onChange: (e) => setReplyBody(e.target.value), rows: 3, placeholder: "Type a reply\u2026", className: "w-full rounded-lg border border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-900 dark:text-zinc-100 placeholder-zinc-400 focus:outline-none focus:ring-2 focus:ring-primary-500" }), _jsx(Button, { type: "button", variant: "primary", size: "sm", isLoading: replyMutation.isPending, disabled: !replyBody.trim() || !ticketId || replyMutation.isPending, onClick: () => replyMutation.mutate(), children: "Send reply" })] }), _jsx("hr", { className: "border-zinc-200 dark:border-zinc-700" }), _jsx(Select, { label: "Status", options: STATUS_OPTIONS, value: status, onValueChange: setStatus }), _jsx(Select, { label: "Priority", options: PRIORITY_OPTIONS, value: priority, onValueChange: setPriority }), _jsxs(Div, { className: "flex flex-col gap-1", children: [_jsx("label", { className: "text-xs font-semibold text-zinc-500 dark:text-zinc-400 uppercase tracking-wide", children: "Internal notes (staff only)" }), _jsx("textarea", { value: notes, onChange: (e) => setNotes(e.target.value), rows: 3, placeholder: "Notes visible only to admins and employees\u2026", className: "w-full rounded-lg border border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-900 dark:text-zinc-100 placeholder-zinc-400 focus:outline-none focus:ring-2 focus:ring-primary-500" })] }), _jsxs(FormActions, { align: "right", children: [_jsx(Button, { type: "button", variant: "secondary", onClick: onClose, children: "Cancel" }), _jsx(Button, { type: "button", isLoading: updateMutation.isPending, disabled: !ticketId || updateMutation.isPending, onClick: () => updateMutation.mutate(), children: "Save changes" })] })] }) }));
|
|
83
83
|
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import React, { useState, useCallback } from "react";
|
|
4
|
-
import { X } from "lucide-react";
|
|
5
4
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
6
5
|
import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
|
|
7
|
-
import { FilterChipGroup, ListingToolbar, ListingViewShell, Pagination, RowActionMenu, Text } from "../../../ui";
|
|
6
|
+
import { FilterChipGroup, ListingToolbar, ListingViewShell, Pagination, RowActionMenu, Text, ListingFilterDrawer } from "../../../ui";
|
|
8
7
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
9
8
|
import { ROW_ACTION_META, ROW_ACTION_ID } from "../../../features/products/constants/action-defs";
|
|
10
9
|
import { ADMIN_SUPPORT_TICKET_STATUS_TABS, ADMIN_SUPPORT_TICKET_PRIORITY_TABS, } from "../constants/filter-tabs";
|
|
@@ -20,23 +19,18 @@ const SORT_OPTIONS = [
|
|
|
20
19
|
{ value: "-updatedAt", label: "Recently updated" },
|
|
21
20
|
];
|
|
22
21
|
const PRIORITY_BADGE = {
|
|
23
|
-
urgent: "bg-
|
|
24
|
-
high: "bg-
|
|
25
|
-
normal: "bg-
|
|
22
|
+
urgent: "bg-error-surface text-error",
|
|
23
|
+
high: "bg-warning-surface text-warning",
|
|
24
|
+
normal: "bg-info-surface text-info",
|
|
26
25
|
low: "bg-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-400",
|
|
27
26
|
};
|
|
28
27
|
const STATUS_BADGE = {
|
|
29
|
-
open: "bg-
|
|
30
|
-
in_progress: "bg-
|
|
28
|
+
open: "bg-info-surface text-info",
|
|
29
|
+
in_progress: "bg-warning-surface text-warning",
|
|
31
30
|
waiting_on_user: "bg-purple-100 text-purple-700 dark:bg-purple-900/40 dark:text-purple-300",
|
|
32
|
-
resolved: "bg-
|
|
31
|
+
resolved: "bg-success-surface text-success",
|
|
33
32
|
closed: "bg-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-400",
|
|
34
33
|
};
|
|
35
|
-
function SupportTicketsFilterDrawer({ filterOpen, setFilterOpen, activeFilterCount, clearFilters, pendingFilters, setPendingFilters, applyFilters, }) {
|
|
36
|
-
if (!filterOpen)
|
|
37
|
-
return null;
|
|
38
|
-
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 dark:text-zinc-400 hover:bg-zinc-100 dark:hover:bg-zinc-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: [_jsx(FilterChipGroup, { label: "Status", tabs: ADMIN_SUPPORT_TICKET_STATUS_TABS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }), _jsx(FilterChipGroup, { label: "Priority", tabs: ADMIN_SUPPORT_TICKET_PRIORITY_TABS, value: pendingFilters.priority ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, priority: id })) })] }), _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})` : ""] }) })] })] }));
|
|
39
|
-
}
|
|
40
34
|
function buildTicketColumns(setSelectedRow, setDrawerOpen) {
|
|
41
35
|
return [
|
|
42
36
|
{
|
|
@@ -144,9 +138,9 @@ export function AdminSupportTicketsView({ children, ...props }) {
|
|
|
144
138
|
}
|
|
145
139
|
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search by subject", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => {
|
|
146
140
|
table.set("sort", v);
|
|
147
|
-
}, showTableView: true, view: view, onViewChange: (v) => setView(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: 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-
|
|
141
|
+
}, showTableView: true, view: view, onViewChange: (v) => setView(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: 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-error-surface px-4 py-3 text-sm text-error dark:border-red-900/60", children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No support tickets found", renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
|
|
148
142
|
{ label: ROW_ACTION_META[ROW_ACTION_ID.VIEW].label, onClick: () => { setSelectedRow(row); setDrawerOpen(true); } },
|
|
149
|
-
] })), columns: buildTicketColumns(setSelectedRow, setDrawerOpen) })] }),
|
|
143
|
+
] })), columns: buildTicketColumns(setSelectedRow, setDrawerOpen) })] }), _jsxs(ListingFilterDrawer, { open: filterOpen, onClose: () => setFilterOpen(false), onApply: applyFilters, onClear: clearFilters, activeCount: activeFilterCount, children: [_jsx(FilterChipGroup, { label: "Status", tabs: ADMIN_SUPPORT_TICKET_STATUS_TABS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }), _jsx(FilterChipGroup, { label: "Priority", tabs: ADMIN_SUPPORT_TICKET_PRIORITY_TABS, value: pendingFilters.priority ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, priority: id })) })] })] }), _jsx(AdminSupportTicketDetailView, { open: drawerOpen, onClose: () => setDrawerOpen(false), ticketId: selectedRow?.id, subject: selectedRow?.primary, userDisplayName: toStringValue(selectedRow?._raw?.userDisplayName, ""), category: toStringValue(selectedRow?._raw?.category, "general"), currentStatus: selectedRow?.status, currentPriority: toStringValue(selectedRow?._raw?.priority, "normal"), description: toStringValue(selectedRow?._raw?.description, ""), messages: Array.isArray(selectedRow?._raw?.messages)
|
|
150
144
|
? selectedRow._raw.messages
|
|
151
145
|
: [], internalNotes: toStringValue(selectedRow?._raw?.internalNotes, "") || undefined, orderId: toStringValue(selectedRow?._raw?.orderId, "") || undefined })] }));
|
|
152
146
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Users, Shield } from "lucide-react";
|
|
3
|
-
import { Div, Heading, Text, Section, Alert } from "../../../ui";
|
|
3
|
+
import { Div, Heading, Span, Text, Section, Alert } from "../../../ui";
|
|
4
4
|
export function AdminTeamGuideView() {
|
|
5
5
|
const PERMISSION_GROUPS = [
|
|
6
6
|
{ name: "content_manager", desc: "Blog, FAQs, Events, Carousel, Sections, Ads, Newsletter read + write." },
|
|
@@ -15,7 +15,7 @@ export function AdminTeamGuideView() {
|
|
|
15
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
16
|
{
|
|
17
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("
|
|
18
|
+
content: (_jsxs(_Fragment, { children: [_jsxs(Text, { className: "text-sm text-[var(--appkit-color-text-muted)] mb-3", children: [_jsx(Span, { weight: "bold", 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(Span, { weight: "bold", 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
19
|
},
|
|
20
20
|
{
|
|
21
21
|
Icon: Users, title: "Permission Groups",
|
|
@@ -23,11 +23,11 @@ export function AdminTeamGuideView() {
|
|
|
23
23
|
},
|
|
24
24
|
{
|
|
25
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("
|
|
26
|
+
content: (_jsxs("ul", { className: "space-y-2 text-sm text-[var(--appkit-color-text-muted)]", children: [_jsxs("li", { children: ["Go to ", _jsx(Span, { weight: "bold", 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(Span, { weight: "bold", 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
27
|
},
|
|
28
28
|
{
|
|
29
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("
|
|
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(Span, { weight: "bold", children: "Admin \u2192 Custom Roles" }), " \u2014 use these when a predefined group is too broad or too narrow for a hire."] })] })),
|
|
31
31
|
},
|
|
32
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
33
|
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import React, { useState, useCallback } from "react";
|
|
4
|
-
import {
|
|
4
|
+
import { UserPlus } from "lucide-react";
|
|
5
5
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
6
6
|
import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
|
|
7
|
-
import { BulkActionBar, Button, FilterChipGroup, ListingToolbar, Pagination, ListingViewShell, RowActionMenu, } from "../../../ui";
|
|
7
|
+
import { BulkActionBar, Button, FilterChipGroup, ListingToolbar, Pagination, ListingViewShell, RowActionMenu, ListingFilterDrawer } from "../../../ui";
|
|
8
8
|
import { useBottomActions } from "../../layout";
|
|
9
9
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
10
10
|
import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
|
|
11
|
+
import { buildBulkAction } from "../../../_internal/shared/actions/bulk-helpers";
|
|
11
12
|
import { ALL_TAB } from "../constants/filter-tabs";
|
|
12
13
|
import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
|
|
13
14
|
import { DataTable } from "./DataTable";
|
|
@@ -45,11 +46,6 @@ const GROUP_TABS = [
|
|
|
45
46
|
function formatGroup(group) {
|
|
46
47
|
return group.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
47
48
|
}
|
|
48
|
-
function TeamFilterDrawer({ filterOpen, setFilterOpen, activeFilterCount, clearFilters, pendingGroup, setPendingGroup, applyFilters, }) {
|
|
49
|
-
if (!filterOpen)
|
|
50
|
-
return null;
|
|
51
|
-
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 dark:text-zinc-400 hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4", children: _jsx(FilterChipGroup, { label: "Permission Group", tabs: GROUP_TABS, value: pendingGroup, onChange: (id) => setPendingGroup(id) }) }), _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})` : ""] }) })] })] }));
|
|
52
|
-
}
|
|
53
49
|
export function AdminTeamView({ children, onBulkRemove, ...props }) {
|
|
54
50
|
const hasChildren = React.Children.count(children) > 0;
|
|
55
51
|
const [view, setView] = useState("table");
|
|
@@ -133,12 +129,7 @@ export function AdminTeamView({ children, onBulkRemove, ...props }) {
|
|
|
133
129
|
const totalPages = Math.ceil(total / PAGE_SIZE);
|
|
134
130
|
const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
|
|
135
131
|
const bulkActions = [
|
|
136
|
-
...(onBulkRemove ? [{
|
|
137
|
-
id: "bulk-remove",
|
|
138
|
-
label: ACTIONS.ADMIN["remove-team-member"].label,
|
|
139
|
-
variant: "danger",
|
|
140
|
-
onClick: async () => { await onBulkRemove(selection.selectedIds); selection.clearSelection(); },
|
|
141
|
-
}] : []),
|
|
132
|
+
...(onBulkRemove ? [buildBulkAction(ACTIONS.ADMIN["remove-team-member"], async () => { await onBulkRemove(selection.selectedIds); selection.clearSelection(); })] : []),
|
|
142
133
|
];
|
|
143
134
|
useBottomActions(selection.selectedCount > 0 ? { bulk: { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: bulkActions } } : {});
|
|
144
135
|
if (hasChildren) {
|
|
@@ -146,12 +137,12 @@ export function AdminTeamView({ children, onBulkRemove, ...props }) {
|
|
|
146
137
|
}
|
|
147
138
|
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search by name or email", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => {
|
|
148
139
|
table.set("sort", v);
|
|
149
|
-
}, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs(Button, { type: "button", variant: "primary", onClick: openInvite, className: "flex items-center gap-1.5 whitespace-nowrap", children: [_jsx(UserPlus, { className: "h-4 w-4" }), "Invite Employee"] }) }), selection.selectedCount > 0 && bulkActions.length > 0 && (_jsx(BulkActionBar, { selectedCount: selection.selectedCount, actions: bulkActions, onClearSelection: selection.clearSelection })), 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-
|
|
140
|
+
}, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs(Button, { type: "button", variant: "primary", onClick: openInvite, className: "flex items-center gap-1.5 whitespace-nowrap", children: [_jsx(UserPlus, { className: "h-4 w-4" }), "Invite Employee"] }) }), selection.selectedCount > 0 && bulkActions.length > 0 && (_jsx(BulkActionBar, { selectedCount: selection.selectedCount, actions: bulkActions, onClearSelection: selection.clearSelection })), 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-error-surface px-4 py-3 text-sm text-error dark:border-red-900/60", children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No employees found", renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
|
|
150
141
|
{
|
|
151
142
|
label: ACTIONS.ADMIN["edit-team-member"].label,
|
|
152
143
|
onClick: () => openEdit(row),
|
|
153
144
|
},
|
|
154
|
-
] })) })] }), _jsx(
|
|
145
|
+
] })) })] }), _jsx(ListingFilterDrawer, { open: filterOpen, onClose: () => setFilterOpen(false), onApply: applyFilters, onClear: clearFilters, activeCount: activeFilterCount, children: _jsx(FilterChipGroup, { label: "Permission Group", tabs: GROUP_TABS, value: pendingGroup, onChange: (id) => setPendingGroup(id) }) })] }), _jsx(AdminEmployeeEditorView, { open: drawerOpen, onClose: () => setDrawerOpen(false), mode: inviteMode ? "invite" : "edit", userId: selectedRow?.id, displayName: selectedRow?.primary, currentPermissionGroup: toStringValue(selectedRow?._raw?.permissionGroup, "custom"), currentPermissions: Array.isArray(selectedRow?._raw?.permissions)
|
|
155
146
|
? selectedRow._raw.permissions
|
|
156
147
|
: [] })] }));
|
|
157
148
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Shield, Ban, AlertTriangle, Headphones, Flag, ClipboardList } from "lucide-react";
|
|
3
|
-
import { Div, Heading, Text, Section, Alert } from "../../../ui";
|
|
3
|
+
import { Div, Heading, Span, Text, Section, Alert } from "../../../ui";
|
|
4
4
|
import { GC } from "../../_guide-cls";
|
|
5
5
|
const STILL_ALLOWED = "✓ Still allowed";
|
|
6
6
|
export function AdminTrustGuideView() {
|
|
@@ -14,19 +14,19 @@ export function AdminTrustGuideView() {
|
|
|
14
14
|
["Sell", "Depends on scope", "✗ Blocked — store suspended"],
|
|
15
15
|
["Bid", "Can restrict with place_bids scope", "✗ Blocked"],
|
|
16
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("
|
|
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(Span, { weight: "bold", 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
18
|
},
|
|
19
19
|
{
|
|
20
20
|
Icon: AlertTriangle, title: "Scam Registry",
|
|
21
|
-
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx("
|
|
21
|
+
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx(Span, { weight: "bold", 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(Span, { weight: "bold", 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(Span, { weight: "bold", 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(Span, { weight: "bold", className: "text-[var(--appkit-color-text)]", children: "Takedown requests" }), ": Route to senior admin with legal involvement. Preserve evidence before any deletion."] })] })),
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
24
|
Icon: Headphones, title: "Support Tickets",
|
|
25
|
-
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx("
|
|
25
|
+
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx(Span, { weight: "bold", 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(Span, { weight: "bold", className: "text-[var(--appkit-color-text)]", children: "Categories" }), ": order_issue, billing_payment, account_access, seller_dispute, technical, other."] }), _jsxs("li", { children: [_jsx(Span, { weight: "bold", 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(Span, { weight: "bold", 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
26
|
},
|
|
27
27
|
{
|
|
28
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("
|
|
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(Span, { weight: "bold", 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(Span, { weight: "bold", 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(Span, { weight: "bold", 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
30
|
},
|
|
31
31
|
{
|
|
32
32
|
Icon: ClipboardList, title: "Item Requests",
|
|
@@ -34,7 +34,7 @@ function formatExpiry(expiresAt) {
|
|
|
34
34
|
return `Until ${d.toLocaleDateString()}`;
|
|
35
35
|
}
|
|
36
36
|
function HardBanPanel({ userId, isHardBanned, currentHardBanReason, showHardBanForm, setShowHardBanForm, hardBanReasonInput, setHardBanReasonInput, hardBanPending, unbanPending, onHardBan, onUnban, }) {
|
|
37
|
-
return (_jsxs("div", { className: "mb-4 rounded-lg border border-zinc-200 bg-zinc-50 p-3 dark:border-zinc-700 dark:bg-zinc-900/40", children: [_jsxs("div", { className: "mb-2 flex items-center justify-between", children: [_jsx("span", { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300", children: "Hard ban" }), isHardBanned ? (_jsx("span", { className: "rounded-full bg-
|
|
37
|
+
return (_jsxs("div", { className: "mb-4 rounded-lg border border-zinc-200 bg-zinc-50 p-3 dark:border-zinc-700 dark:bg-zinc-900/40", children: [_jsxs("div", { className: "mb-2 flex items-center justify-between", children: [_jsx("span", { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300", children: "Hard ban" }), isHardBanned ? (_jsx("span", { className: "rounded-full bg-error-surface px-2 py-0.5 text-xs font-semibold text-error", children: "Banned" })) : (_jsx("span", { className: "rounded-full bg-success-surface px-2 py-0.5 text-xs font-semibold text-success", children: "Active" }))] }), isHardBanned ? (_jsxs("div", { className: "space-y-2", children: [currentHardBanReason && _jsxs(Text, { className: "text-xs text-zinc-600 dark:text-zinc-400", children: ["Reason: ", currentHardBanReason] }), _jsx(Button, { type: "button", variant: "secondary", size: "sm", isLoading: unbanPending, disabled: unbanPending, onClick: onUnban, children: "Lift hard ban" })] })) : showHardBanForm ? (_jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("label", { className: "text-xs font-medium text-zinc-700 dark:text-zinc-300", children: "Ban reason (required)" }), _jsx("textarea", { value: hardBanReasonInput, onChange: (e) => setHardBanReasonInput(e.target.value), rows: 2, placeholder: "e.g. Repeated fraud, scam activity\u2026", className: "w-full rounded-lg border border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-900 dark:text-zinc-100 placeholder-zinc-400 focus:outline-none focus:ring-2 focus:ring-red-500" })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { type: "button", variant: "danger", size: "sm", isLoading: hardBanPending, disabled: !hardBanReasonInput.trim() || hardBanPending, onClick: () => onHardBan(hardBanReasonInput.trim()), children: "Confirm hard ban" }), _jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: () => { setShowHardBanForm(false); setHardBanReasonInput(""); }, children: "Cancel" })] })] })) : (_jsx(Button, { type: "button", variant: "danger", size: "sm", disabled: !userId, onClick: () => setShowHardBanForm(true), children: "Impose hard ban" }))] }));
|
|
38
38
|
}
|
|
39
39
|
function SoftBanPanel({ userId, softBans, showAddSoftBan, setShowAddSoftBan, softBanAction, setSoftBanAction, softBanReason, setSoftBanReason, softBanExpiry, setSoftBanExpiry, softBanPending, liftPending, onAddSoftBan, onLiftSoftBan, }) {
|
|
40
40
|
return (_jsxs("div", { className: "rounded-lg border border-zinc-200 bg-zinc-50 p-3 dark:border-zinc-700 dark:bg-zinc-900/40", children: [_jsxs("div", { className: "mb-2 flex items-center justify-between", children: [_jsxs("span", { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300", children: ["Soft bans", softBans.length > 0 ? ` (${softBans.length})` : ""] }), !showAddSoftBan && (_jsx(Button, { type: "button", variant: "secondary", size: "sm", disabled: !userId, onClick: () => setShowAddSoftBan(true), children: "Add soft ban" }))] }), softBans.length > 0 && (_jsx("ul", { className: "mb-3 space-y-2", children: softBans.map((ban) => (_jsxs("li", { className: "flex items-start justify-between gap-2 rounded-md border border-zinc-200 bg-white px-3 py-2 text-xs dark:border-zinc-700 dark:bg-zinc-800", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "font-semibold text-zinc-800 dark:text-zinc-200", children: formatBanAction(ban.action) }), _jsx("div", { className: "text-zinc-500 dark:text-zinc-400", children: ban.reason }), _jsx("div", { className: "text-zinc-400 dark:text-zinc-400", children: formatExpiry(ban.expiresAt) })] }), _jsx(Button, { type: "button", variant: "secondary", size: "sm", isLoading: liftPending, disabled: liftPending, onClick: () => onLiftSoftBan(ban.action), children: "Lift" })] }, ban.action))) })), showAddSoftBan && (_jsxs("div", { className: "space-y-2", children: [_jsx(Select, { label: "Action to restrict", options: BANNED_ACTION_OPTIONS, value: softBanAction, onValueChange: setSoftBanAction }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("label", { className: "text-xs font-medium text-zinc-700 dark:text-zinc-300", children: "Reason (required)" }), _jsx("textarea", { value: softBanReason, onChange: (e) => setSoftBanReason(e.target.value), rows: 2, placeholder: "e.g. Suspicious bid activity\u2026", className: "w-full rounded-lg border border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-900 dark:text-zinc-100 placeholder-zinc-400 focus:outline-none focus:ring-2 focus:ring-primary-500" })] }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("label", { className: "text-xs font-medium text-zinc-700 dark:text-zinc-300", children: "Expires at (optional \u2014 leave blank for permanent)" }), _jsx("input", { type: "datetime-local", value: softBanExpiry, onChange: (e) => setSoftBanExpiry(e.target.value), className: "w-full rounded-lg border border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-900 dark:text-zinc-100 focus:outline-none focus:ring-2 focus:ring-primary-500" })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { type: "button", variant: "primary", size: "sm", isLoading: softBanPending, disabled: !softBanReason.trim() || softBanPending, onClick: () => onAddSoftBan({ action: softBanAction, reason: softBanReason.trim(), ...(softBanExpiry ? { expiresAt: new Date(softBanExpiry).toISOString() } : {}) }), children: "Apply soft ban" }), _jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: () => { setShowAddSoftBan(false); setSoftBanReason(""); setSoftBanExpiry(""); }, children: "Cancel" })] })] })), softBans.length === 0 && !showAddSoftBan && (_jsx(Text, { className: "text-xs text-zinc-400 dark:text-zinc-400", children: "No active soft bans." }))] }));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Users, Search, Edit, Monitor, UserCog, AlertTriangle } from "lucide-react";
|
|
3
|
-
import { Div, Heading, Text, Section, Alert } from "../../../ui";
|
|
3
|
+
import { Div, Heading, Span, Text, Section, Alert } from "../../../ui";
|
|
4
4
|
import { GC } from "../../_guide-cls";
|
|
5
5
|
export function AdminUsersGuideView() {
|
|
6
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." })] }), [
|
|
@@ -16,19 +16,19 @@ export function AdminUsersGuideView() {
|
|
|
16
16
|
},
|
|
17
17
|
{
|
|
18
18
|
Icon: Search, title: "User List & Search",
|
|
19
|
-
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx("
|
|
19
|
+
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx(Span, { weight: "bold", className: "text-[var(--appkit-color-text)]", children: "Columns" }), ": display name, email, role badge, emailVerified, createdAt, last login."] }), _jsxs("li", { children: [_jsx(Span, { weight: "bold", className: "text-[var(--appkit-color-text)]", children: "Filter chips" }), ": All, by role, verified only, banned."] }), _jsxs("li", { children: [_jsx(Span, { weight: "bold", 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
20
|
},
|
|
21
21
|
{
|
|
22
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("
|
|
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(Span, { weight: "bold", 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(Span, { weight: "bold", 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(Span, { weight: "bold", 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(Span, { weight: "bold", 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
24
|
},
|
|
25
25
|
{
|
|
26
26
|
Icon: Monitor, title: "Sessions Management",
|
|
27
|
-
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx("
|
|
27
|
+
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx(Span, { weight: "bold", 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(Span, { weight: "bold", 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(Span, { weight: "bold", className: "text-[var(--appkit-color-text)]", children: "Hard ban cascade" }), " \u2014 issuing a hard ban automatically expires all active sessions for that user."] })] })),
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
30
|
Icon: UserCog, title: "Employee Accounts",
|
|
31
|
-
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: ["Create employee accounts via ", _jsx("
|
|
31
|
+
content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: ["Create employee accounts via ", _jsx(Span, { weight: "bold", 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
32
|
},
|
|
33
33
|
{
|
|
34
34
|
Icon: AlertTriangle, title: "PII Handling Notice",
|