@mohasinac/appkit 2.7.54 → 2.7.55

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.
Files changed (76) hide show
  1. package/dist/_internal/server/jobs/runtime/adapters/firebase.js +5 -14
  2. package/dist/_internal/shared/actions/action-registry.js +827 -4
  3. package/dist/_internal/shared/features/events/schema.d.ts +4 -4
  4. package/dist/constants/api-endpoints.d.ts +3 -0
  5. package/dist/constants/api-endpoints.js +1 -0
  6. package/dist/features/account/components/UserOffersPanel.js +2 -1
  7. package/dist/features/admin/components/AdminAllEventEntriesView.js +4 -3
  8. package/dist/features/admin/components/AdminBidsView.js +5 -3
  9. package/dist/features/admin/components/AdminBlogView.js +3 -2
  10. package/dist/features/admin/components/AdminBrandsView.js +2 -1
  11. package/dist/features/admin/components/AdminBundleEditorView.js +4 -5
  12. package/dist/features/admin/components/AdminBundlesView.js +3 -3
  13. package/dist/features/admin/components/AdminCarouselView.d.ts +2 -1
  14. package/dist/features/admin/components/AdminCarouselView.js +14 -5
  15. package/dist/features/admin/components/AdminCategoriesView.js +2 -1
  16. package/dist/features/admin/components/AdminContactView.d.ts +4 -1
  17. package/dist/features/admin/components/AdminContactView.js +27 -7
  18. package/dist/features/admin/components/AdminCouponsView.d.ts +3 -1
  19. package/dist/features/admin/components/AdminCouponsView.js +17 -3
  20. package/dist/features/admin/components/AdminFaqsView.d.ts +3 -1
  21. package/dist/features/admin/components/AdminFaqsView.js +18 -4
  22. package/dist/features/admin/components/AdminFeaturesView.js +2 -1
  23. package/dist/features/admin/components/AdminMediaView.js +3 -2
  24. package/dist/features/admin/components/AdminNavigationView.js +3 -2
  25. package/dist/features/admin/components/AdminNewsletterView.d.ts +2 -1
  26. package/dist/features/admin/components/AdminNewsletterView.js +13 -4
  27. package/dist/features/admin/components/AdminNotificationsView.js +6 -4
  28. package/dist/features/admin/components/AdminOrdersView.js +4 -3
  29. package/dist/features/admin/components/AdminPayoutsView.js +2 -2
  30. package/dist/features/admin/components/AdminPrizeDrawsView.js +2 -1
  31. package/dist/features/admin/components/AdminProductsView.js +3 -3
  32. package/dist/features/admin/components/AdminReviewsView.js +4 -3
  33. package/dist/features/admin/components/AdminScammersView.js +2 -1
  34. package/dist/features/admin/components/AdminSessionsView.js +5 -3
  35. package/dist/features/admin/components/AdminStoresView.js +2 -2
  36. package/dist/features/admin/components/AdminSublistingCategoriesView.d.ts +5 -1
  37. package/dist/features/admin/components/AdminSublistingCategoriesView.js +12 -3
  38. package/dist/features/admin/components/AdminSupportTicketsView.js +2 -1
  39. package/dist/features/admin/components/AdminTeamView.d.ts +2 -1
  40. package/dist/features/admin/components/AdminTeamView.js +13 -4
  41. package/dist/features/admin/components/AdminUsersView.js +2 -2
  42. package/dist/features/products/constants/action-defs.d.ts +86 -7
  43. package/dist/features/products/constants/action-defs.js +99 -7
  44. package/dist/features/seller/components/QuickProductForm.d.ts +13 -0
  45. package/dist/features/seller/components/QuickProductForm.js +50 -0
  46. package/dist/features/seller/components/SellerAuctionsView.d.ts +4 -1
  47. package/dist/features/seller/components/SellerAuctionsView.js +37 -3
  48. package/dist/features/seller/components/SellerBidsView.js +2 -1
  49. package/dist/features/seller/components/SellerBundlesView.d.ts +3 -1
  50. package/dist/features/seller/components/SellerBundlesView.js +38 -5
  51. package/dist/features/seller/components/SellerClassifiedView.d.ts +3 -1
  52. package/dist/features/seller/components/SellerClassifiedView.js +37 -4
  53. package/dist/features/seller/components/SellerDigitalCodesView.d.ts +3 -1
  54. package/dist/features/seller/components/SellerDigitalCodesView.js +37 -4
  55. package/dist/features/seller/components/SellerGoogleReviewsView.js +2 -1
  56. package/dist/features/seller/components/SellerGroupedListingsView.js +3 -2
  57. package/dist/features/seller/components/SellerLiveView.d.ts +3 -1
  58. package/dist/features/seller/components/SellerLiveView.js +37 -4
  59. package/dist/features/seller/components/SellerOffersPanel.js +2 -1
  60. package/dist/features/seller/components/SellerOffersView.d.ts +4 -1
  61. package/dist/features/seller/components/SellerOffersView.js +8 -3
  62. package/dist/features/seller/components/SellerPayoutsView.d.ts +3 -1
  63. package/dist/features/seller/components/SellerPayoutsView.js +7 -3
  64. package/dist/features/seller/components/SellerPreOrdersView.d.ts +2 -1
  65. package/dist/features/seller/components/SellerPreOrdersView.js +30 -7
  66. package/dist/features/seller/components/SellerPrizeDrawsView.d.ts +2 -1
  67. package/dist/features/seller/components/SellerPrizeDrawsView.js +30 -7
  68. package/dist/features/seller/components/SellerProductShell.js +12 -0
  69. package/dist/features/seller/components/SellerReviewsView.js +2 -1
  70. package/dist/features/seller/components/SellerShippingConfigsView.js +3 -2
  71. package/dist/features/seller/components/SellerStoreCategoriesView.js +6 -5
  72. package/dist/features/seller/components/SellerTemplatesView.js +4 -3
  73. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +30 -16
  74. package/dist/styles.css +1 -1
  75. package/dist/tailwind-utilities.css +1 -1
  76. package/package.json +1 -1
@@ -1,10 +1,10 @@
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 { Pencil, X } from "lucide-react";
4
+ import { X } from "lucide-react";
5
5
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
6
  import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
7
- import { Alert, Badge, Button, Div, FilterChipGroup, ListingToolbar, ListingViewShell, Pagination, Row, Text } from "../../../ui";
7
+ import { Alert, Badge, Button, ConfirmDeleteModal, Div, FilterChipGroup, ListingToolbar, ListingViewShell, Pagination, Row, RowActionMenu, Text } from "../../../ui";
8
8
  import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
9
9
  import { SELLER_PRIZE_DRAW_STATUS_TABS } from "../../admin/constants/filter-tabs";
10
10
  import { ROUTES } from "../../../constants";
@@ -67,10 +67,12 @@ const PRIZE_DRAW_COLUMNS = [
67
67
  render: (row) => (_jsx("span", { className: "text-xs text-[var(--appkit-color-text-muted)]", children: row.updatedAt })),
68
68
  },
69
69
  ];
70
- export function SellerPrizeDrawsView({ children, ...props }) {
70
+ export function SellerPrizeDrawsView({ children, onDelete, ...props }) {
71
71
  const hasChildren = React.Children.count(children) > 0;
72
72
  const [view, setView] = useState("table");
73
73
  const dispatch = useActionDispatch();
74
+ const [deleteTargetId, setDeleteTargetId] = useState(null);
75
+ const [deletingId, setDeletingId] = useState(null);
74
76
  const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
75
77
  const [searchInput, setSearchInput] = useState(table.get("q") || "");
76
78
  const [filterOpen, setFilterOpen] = useState(false);
@@ -132,11 +134,32 @@ export function SellerPrizeDrawsView({ children, ...props }) {
132
134
  const currentPage = table.getNumber("page", 1);
133
135
  const totalPages = Math.ceil(total / PAGE_SIZE);
134
136
  const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
137
+ const handleDelete = useCallback(async (id) => {
138
+ setDeletingId(id);
139
+ try {
140
+ if (onDelete)
141
+ await onDelete(id);
142
+ else
143
+ await fetch(`/api/store/products/${id}`, { method: "DELETE", credentials: "include" });
144
+ }
145
+ finally {
146
+ setDeletingId(null);
147
+ setDeleteTargetId(null);
148
+ }
149
+ }, [onDelete]);
135
150
  if (hasChildren) {
136
151
  return (_jsx(ListingViewShell, { portal: "seller", ...props, children: children }));
137
152
  }
138
- return (_jsxs(Div, { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search prize draws by name\u2026", 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 }), totalPages > 1 && (_jsx("div", { className: "sticky z-10 flex justify-center bg-[var(--appkit-color-surface)]/95 backdrop-blur-sm border-b border-[var(--appkit-color-border)] px-3 py-1.5", style: { top: "calc(var(--header-height, 0px) + 44px)" }, 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(Alert, { variant: "error", className: "mb-4", children: errorMessage })), _jsx(DataTable, { columns: PRIZE_DRAW_COLUMNS, rows: rows, isLoading: isLoading, emptyLabel: "No prize draws listed yet", getRowHref: (row) => String(ROUTES.STORE.PRIZE_DRAWS_EDIT(row.id)), renderRowActions: (row) => (_jsx(Button, { variant: "ghost", size: "sm", onClick: (e) => {
139
- e.stopPropagation();
140
- void dispatch({ type: "NAVIGATE", href: String(ROUTES.STORE.PRIZE_DRAWS_EDIT(row.id)) });
141
- }, "aria-label": ACTIONS.STORE["edit-listing"].ariaLabel, children: _jsx(Pencil, { className: "w-4 h-4" }) })) })] }), filterOpen && (_jsxs(_Fragment, { children: [_jsx(Div, { role: "presentation", className: "fixed inset-0 z-40 bg-black/40", onClick: () => setFilterOpen(false) }), _jsxs(Div, { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-[var(--appkit-color-surface)] shadow-2xl", children: [_jsxs(Row, { justify: "between", className: "border-b border-[var(--appkit-color-border)] px-4 py-3.5", children: [_jsx(Text, { className: "text-base font-semibold text-[var(--appkit-color-text)]", children: "Filters" }), _jsxs(Row, { className: "gap-2", children: [activeFilterCount > 0 && (_jsx(Button, { variant: "ghost", size: "sm", onClick: clearFilters, className: "text-xs text-[var(--appkit-color-text-muted)] hover:text-[var(--appkit-color-error)]", children: "Clear all" })), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => setFilterOpen(false), "aria-label": "Close filters", 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-[var(--appkit-color-border)] px-4 py-3.5", children: _jsxs(Button, { variant: "primary", onClick: applyFilters, className: "w-full rounded-lg py-2.5 active:scale-[0.98]", children: ["Apply Filters", activeFilterCount > 0 ? ` (${activeFilterCount})` : ""] }) })] })] }))] }));
153
+ return (_jsxs(Div, { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search prize draws by name\u2026", 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 }), totalPages > 1 && (_jsx("div", { className: "sticky z-10 flex justify-center bg-[var(--appkit-color-surface)]/95 backdrop-blur-sm border-b border-[var(--appkit-color-border)] px-3 py-1.5", style: { top: "calc(var(--header-height, 0px) + 44px)" }, 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(Alert, { variant: "error", className: "mb-4", children: errorMessage })), _jsx(DataTable, { columns: PRIZE_DRAW_COLUMNS, rows: rows, isLoading: isLoading, emptyLabel: "No prize draws listed yet", getRowHref: (row) => String(ROUTES.STORE.PRIZE_DRAWS_EDIT(row.id)), renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
154
+ {
155
+ label: ACTIONS.STORE["edit-listing"].label,
156
+ onClick: () => void dispatch({ type: "NAVIGATE", href: String(ROUTES.STORE.PRIZE_DRAWS_EDIT(row.id)) }),
157
+ },
158
+ ...(onDelete ? [{
159
+ label: ACTIONS.STORE["delete-listing"].label,
160
+ destructive: true,
161
+ onClick: () => setDeleteTargetId(row.id),
162
+ disabled: deletingId === row.id,
163
+ }] : []),
164
+ ] })) })] }), filterOpen && (_jsxs(_Fragment, { children: [_jsx(Div, { role: "presentation", className: "fixed inset-0 z-40 bg-black/40", onClick: () => setFilterOpen(false) }), _jsxs(Div, { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-[var(--appkit-color-surface)] shadow-2xl", children: [_jsxs(Row, { justify: "between", className: "border-b border-[var(--appkit-color-border)] px-4 py-3.5", children: [_jsx(Text, { className: "text-base font-semibold text-[var(--appkit-color-text)]", children: "Filters" }), _jsxs(Row, { className: "gap-2", children: [activeFilterCount > 0 && (_jsx(Button, { variant: "ghost", size: "sm", onClick: clearFilters, className: "text-xs text-[var(--appkit-color-text-muted)] hover:text-[var(--appkit-color-error)]", children: "Clear all" })), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => setFilterOpen(false), "aria-label": "Close filters", 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-[var(--appkit-color-border)] px-4 py-3.5", children: _jsxs(Button, { variant: "primary", onClick: applyFilters, className: "w-full rounded-lg py-2.5 active:scale-[0.98]", children: ["Apply Filters", activeFilterCount > 0 ? ` (${activeFilterCount})` : ""] }) })] })] })), deleteTargetId && (_jsx(ConfirmDeleteModal, { isOpen: true, title: "Delete Prize Draw", message: "Are you sure you want to delete this prize draw listing? This cannot be undone.", onConfirm: () => handleDelete(deleteTargetId), onClose: () => setDeleteTargetId(null), isDeleting: deletingId === deleteTargetId }))] }));
142
165
  }
@@ -7,6 +7,7 @@ import { FormShellProvider } from "../../../ui/forms";
7
7
  import { Alert, Button, Div, FormField, FormGroup, Heading, Section, Stack, Text, Toggle } from "../../../ui";
8
8
  import { ImageUpload, MediaUploadField, MediaUploadList, useMediaUpload } from "../../media";
9
9
  import { StoreAddressSelectorCreate } from "../../stores/components/StoreAddressSelectorCreate";
10
+ import { QuickProductForm } from "./QuickProductForm";
10
11
  const CONDITION_OPTIONS = [
11
12
  { value: "new", label: "New" },
12
13
  { value: "like_new", label: "Like New" },
@@ -143,10 +144,12 @@ const EDIT_SECTIONS = [
143
144
  // ── Main SellerProductShell ───────────────────────────────────────────────
144
145
  export function SellerProductShell({ mode, listingType = "standard", initialValues, onSave, onPublish, onDiscard, isLoading = false, storeSlug, renderCategorySelector, renderBrandSelector, renderAddressSelector, renderTemplateSelector, onSaveAsTemplate, previewSlot, }) {
145
146
  const [draft, setDraft] = useState(initialValues ?? { status: "draft", condition: "new" });
147
+ const [formMode, setFormMode] = useState(mode === "create" && listingType === "standard" ? "quick" : "full");
146
148
  const [currentStep, setCurrentStep] = useState(0);
147
149
  const [stepError, setStepError] = useState(null);
148
150
  const { isDirty, markDirty, markClean } = useFormShell();
149
151
  const router = useRouter();
152
+ const { upload: shellUpload } = useMediaUpload();
150
153
  // Auto-save in create mode — debounce 2s on any draft change
151
154
  const autoSaveTimer = useRef(null);
152
155
  const draftRef = useRef(draft);
@@ -299,6 +302,15 @@ export function SellerProductShell({ mode, listingType = "standard", initialValu
299
302
  const title = mode === "create"
300
303
  ? `New ${listingTypeLabel}`
301
304
  : draft.title ?? `Edit ${listingTypeLabel}`;
305
+ if (mode === "create" && formMode === "quick") {
306
+ return (_jsx(FormShellProvider, { isDirty: isDirty, values: draft, children: _jsx(FormShell, { isOpen: true, onClose: handleDiscard, title: title, breadcrumb: breadcrumb, isDirty: isDirty, isLoading: isLoading, children: _jsx(QuickProductForm, { values: draft, onChange: update, onPublish: () => void handlePublish(), onSave: () => void handleSave(), onSwitchToFull: () => setFormMode("full"), isLoading: isLoading, renderCategorySelector: renderCategorySelector, storeSlug: storeSlug, onUploadImage: (file) => shellUpload(file, "products", true, {
307
+ type: "product-image",
308
+ index: 1,
309
+ name: draft.title ?? "product",
310
+ store: storeSlug ?? "store",
311
+ category: draft.category ?? "uncategorized",
312
+ }) }) }) }));
313
+ }
302
314
  if (mode === "create") {
303
315
  return (_jsx(FormShell, { isOpen: true, onClose: handleDiscard, title: title, breadcrumb: breadcrumb, isDirty: isDirty, isLoading: isLoading, previewSlot: previewSlot, splitPreview: !!previewSlot, renderBottomBar: () => (_jsxs("div", { className: "flex-shrink-0 border-t border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)]", children: [_jsx(StepFormActions, { currentStep: currentStep, totalSteps: steps.length, onNext: () => void handleNext(), onPrev: currentStep > 0 ? () => setCurrentStep((c) => c - 1) : undefined, completeLabel: `Publish ${listingTypeLabel}`, isLoading: isLoading && currentStep === steps.length - 1, disabled: isLoading }), currentStep === 0 && (_jsx("div", { className: "px-5 pb-3", children: _jsx(Button, { variant: "ghost", size: "sm", onClick: () => void handleSave(), disabled: isLoading || !draft.title?.trim(), children: "Save as draft & finish later" }) })), stepError && (_jsx(Text, { className: "px-5 pb-3 text-sm text-[var(--appkit-color-error)]", children: stepError }))] })), children: _jsx(FormShellProvider, { isDirty: isDirty, values: draft, children: _jsx(StepForm, { steps: steps, values: draft, onChange: update, onComplete: handlePublish, completeLabel: `Publish ${listingTypeLabel}`, currentStep: currentStep, onStepChange: setCurrentStep, isLoading: isLoading, hideActions: true, stepErrors: stepValidationErrors }) }) }));
304
316
  }
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import { useCallback, useEffect, useMemo, useState } from "react";
4
4
  import { Alert, Badge, BulkActionBar, Button, Checkbox, Div, Modal, Row, Select, SideDrawer, Stack, Text, Textarea, } from "../../../ui";
5
5
  import { StackedViewShell } from "../../../ui";
6
+ import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
6
7
  const STAR = "★";
7
8
  const EMPTY_STAR = "☆";
8
9
  function Stars({ rating }) {
@@ -158,6 +159,6 @@ export function SellerReviewsView({ reviewsApiBase = "/api/store/reviews", reply
158
159
  label: "Reply to selected",
159
160
  onClick: () => setBulkReplyOpen(true),
160
161
  },
161
- ] })), loading ? (_jsx(Div, { className: "py-8 text-center", children: _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", children: "Loading reviews\u2026" }) })) : reviews.length === 0 ? (_jsx(Div, { className: "py-12 text-center", children: _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", children: "No reviews found." }) })) : (_jsx(Stack, { gap: "md", children: reviews.map((review) => (_jsx(Div, { className: "p-4 rounded-lg border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)]", children: _jsxs(Div, { className: "flex items-start justify-between gap-3 flex-wrap", children: [_jsxs(Row, { className: "gap-3 items-start flex-1 min-w-0", children: [_jsx(Checkbox, { checked: selectedIds.has(review.id), onChange: () => toggleSelected(review.id), "aria-label": "Select review" }), _jsxs(Div, { className: "flex-1 min-w-0", children: [_jsx(Text, { className: "font-medium truncate", children: review.productTitle }), _jsxs(Div, { className: "flex items-center gap-2 mt-1 flex-wrap", children: [_jsx(Stars, { rating: review.rating }), _jsxs(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: ["by ", review.userName] }), review.verified && _jsx(Badge, { variant: "success", children: "Verified" }), statusBadge(review.status), _jsx(Badge, { variant: review.sellerReply ? "success" : "warning", children: review.sellerReply ? "Store replied" : "Awaiting store reply" })] }), review.title && _jsx(Text, { className: "mt-2 font-medium", children: review.title }), _jsx(Text, { className: "mt-1 text-sm text-[var(--appkit-color-text-secondary)] line-clamp-3", children: review.comment }), review.sellerReply && (_jsxs(Div, { className: "mt-2 pl-3 border-l-2 border-[var(--appkit-color-primary)]", children: [_jsx(Text, { className: "text-xs text-[var(--appkit-color-text-muted)]", children: "Store reply:" }), _jsx(Text, { className: "text-sm", children: review.sellerReply })] }))] })] }), _jsxs(Row, { className: "gap-2 flex-shrink-0", children: [_jsx(Button, { variant: "outline", size: "sm", onClick: () => openReply(review), children: review.sellerReply ? "Edit Reply" : "Reply" }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => setContestTarget(review), children: "Contest" }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => setFeedbackTarget(review), children: "Feedback" })] })] }) }, review.id))) })), meta && meta.totalPages > 1 && (_jsxs(Div, { className: "flex items-center justify-center gap-2", children: [_jsx(Button, { variant: "outline", size: "sm", onClick: () => setPage((p) => Math.max(1, p - 1)), disabled: page <= 1 || loading, children: "Previous" }), _jsxs(Text, { className: "text-sm", children: ["Page ", meta.page, " of ", meta.totalPages] }), _jsx(Button, { variant: "outline", size: "sm", onClick: () => setPage((p) => p + 1), disabled: !meta.hasMore || loading, children: "Next" })] }))] }, "reviews"),
162
+ ] })), loading ? (_jsx(Div, { className: "py-8 text-center", children: _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", children: "Loading reviews\u2026" }) })) : reviews.length === 0 ? (_jsx(Div, { className: "py-12 text-center", children: _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", children: "No reviews found." }) })) : (_jsx(Stack, { gap: "md", children: reviews.map((review) => (_jsx(Div, { className: "p-4 rounded-lg border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)]", children: _jsxs(Div, { className: "flex items-start justify-between gap-3 flex-wrap", children: [_jsxs(Row, { className: "gap-3 items-start flex-1 min-w-0", children: [_jsx(Checkbox, { checked: selectedIds.has(review.id), onChange: () => toggleSelected(review.id), "aria-label": "Select review" }), _jsxs(Div, { className: "flex-1 min-w-0", children: [_jsx(Text, { className: "font-medium truncate", children: review.productTitle }), _jsxs(Div, { className: "flex items-center gap-2 mt-1 flex-wrap", children: [_jsx(Stars, { rating: review.rating }), _jsxs(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: ["by ", review.userName] }), review.verified && _jsx(Badge, { variant: "success", children: "Verified" }), statusBadge(review.status), _jsx(Badge, { variant: review.sellerReply ? "success" : "warning", children: review.sellerReply ? "Store replied" : "Awaiting store reply" })] }), review.title && _jsx(Text, { className: "mt-2 font-medium", children: review.title }), _jsx(Text, { className: "mt-1 text-sm text-[var(--appkit-color-text-secondary)] line-clamp-3", children: review.comment }), review.sellerReply && (_jsxs(Div, { className: "mt-2 pl-3 border-l-2 border-[var(--appkit-color-primary)]", children: [_jsx(Text, { className: "text-xs text-[var(--appkit-color-text-muted)]", children: "Store reply:" }), _jsx(Text, { className: "text-sm", children: review.sellerReply })] }))] })] }), _jsxs(Row, { className: "gap-2 flex-shrink-0", children: [_jsx(Button, { variant: "outline", size: "sm", onClick: () => openReply(review), children: review.sellerReply ? "Edit Reply" : ACTIONS.STORE["reply-review"].label }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => setContestTarget(review), children: ACTIONS.STORE["contest-review"].label }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => setFeedbackTarget(review), children: ACTIONS.STORE["buyer-feedback"].label })] })] }) }, review.id))) })), meta && meta.totalPages > 1 && (_jsxs(Div, { className: "flex items-center justify-center gap-2", children: [_jsx(Button, { variant: "outline", size: "sm", onClick: () => setPage((p) => Math.max(1, p - 1)), disabled: page <= 1 || loading, children: "Previous" }), _jsxs(Text, { className: "text-sm", children: ["Page ", meta.page, " of ", meta.totalPages] }), _jsx(Button, { variant: "outline", size: "sm", onClick: () => setPage((p) => p + 1), disabled: !meta.hasMore || loading, children: "Next" })] }))] }, "reviews"),
162
163
  ] }), _jsx(SideDrawer, { isOpen: !!replyTarget, onClose: () => setReplyTarget(null), title: replyTarget?.sellerReply ? "Edit Reply" : "Reply to Review", mode: "create", children: _jsxs(Stack, { gap: "md", className: "p-4", children: [replyTarget && (_jsxs(Div, { className: "p-3 rounded bg-[var(--appkit-color-surface-muted)]", children: [_jsx(Stars, { rating: replyTarget.rating }), _jsx(Text, { className: "text-sm mt-1", children: replyTarget.comment })] })), replyError && _jsx(Alert, { variant: "error", children: replyError }), _jsxs(Div, { children: [_jsx(Text, { className: "text-sm font-medium mb-1.5", children: "Store reply" }), _jsx("textarea", { className: "w-full rounded-md border border-[var(--appkit-color-border)] bg-[var(--appkit-color-input-bg,var(--appkit-color-surface))] p-2.5 text-sm resize-none focus:outline-none focus:ring-2 focus:ring-[var(--appkit-color-primary)] dark:bg-[var(--appkit-color-surface-dark,#1e2330)] dark:border-[var(--appkit-color-border-dark)]", rows: 5, value: replyText, onChange: (e) => setReplyText(e.target.value), maxLength: 1000, placeholder: "Write your response to this review\u2026" }), _jsxs(Text, { className: "text-xs text-[var(--appkit-color-text-muted)] text-right mt-1", children: [replyText.length, "/1000"] })] }), _jsxs(Div, { className: "flex gap-2 justify-end", children: [_jsx(Button, { variant: "outline", onClick: () => setReplyTarget(null), disabled: replySaving, children: "Cancel" }), _jsx(Button, { variant: "primary", onClick: handleReplySave, disabled: replySaving || !replyText.trim(), isLoading: replySaving, children: replyTarget?.sellerReply ? "Update Reply" : "Post Reply" })] })] }) }), _jsx(Modal, { isOpen: bulkReplyOpen, onClose: () => setBulkReplyOpen(false), title: `Bulk reply to ${selectedIds.size} review${selectedIds.size === 1 ? "" : "s"}`, actions: _jsxs(Row, { className: "gap-2", children: [_jsx(Button, { variant: "ghost", onClick: () => setBulkReplyOpen(false), disabled: bulkSaving, children: "Cancel" }), _jsx(Button, { variant: "primary", onClick: () => void submitBulkReply(), disabled: !bulkReplyText.trim() || bulkSaving, isLoading: bulkSaving, children: "Send reply" })] }), children: _jsxs(Stack, { gap: "md", className: "p-1", children: [_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: "The same reply will be posted on all selected reviews." }), _jsx(Textarea, { value: bulkReplyText, onChange: (e) => setBulkReplyText(e.target.value), rows: 5, placeholder: "Thanks for your review\u2026", label: "Reply" })] }) }), _jsx(Modal, { isOpen: !!contestTarget, onClose: () => setContestTarget(null), title: "Contest this review", actions: _jsxs(Row, { className: "gap-2", children: [_jsx(Button, { variant: "ghost", onClick: () => setContestTarget(null), children: "Cancel" }), _jsx(Button, { variant: "primary", onClick: () => void submitContest(), disabled: !contestReason.trim(), children: "Submit" })] }), children: _jsxs(Stack, { gap: "md", className: "p-1", children: [_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: "Flag this review for admin investigation. Provide a clear reason \u2014 fake, abusive, off-topic, etc." }), _jsx(Textarea, { value: contestReason, onChange: (e) => setContestReason(e.target.value), rows: 4, label: "Reason" })] }) }), _jsx(Modal, { isOpen: !!feedbackTarget, onClose: () => setFeedbackTarget(null), title: "Send feedback to buyer", actions: _jsxs(Row, { className: "gap-2", children: [_jsx(Button, { variant: "ghost", onClick: () => setFeedbackTarget(null), children: "Cancel" }), _jsx(Button, { variant: "primary", onClick: () => void submitFeedback(), disabled: !feedbackText.trim(), children: "Send" })] }), children: _jsxs(Stack, { gap: "md", className: "p-1", children: [_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: "Private message sent to the buyer's notification inbox. Does not appear on the public review." }), _jsx(Textarea, { value: feedbackText, onChange: (e) => setFeedbackText(e.target.value), rows: 4, label: "Feedback" })] }) })] }));
163
164
  }
@@ -5,6 +5,7 @@ import { Plus } from "lucide-react";
5
5
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
6
  import { Button, ConfirmDeleteModal, DataTable, Div, ListingToolbar, RowActionMenu, Text, } from "../../../ui";
7
7
  import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
8
+ import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
8
9
  import { ROUTES } from "../../..";
9
10
  import { toRecordArray, toStringValue, useSellerListingData, } from "../hooks/useSellerListingData";
10
11
  import { TABLE_KEYS } from "../../../constants/table-keys";
@@ -127,7 +128,7 @@ export function SellerShippingConfigsView({ onCreateClick, onEditClick, onDelete
127
128
  }, [onEditClick]);
128
129
  return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: 0, searchValue: searchInput, searchPlaceholder: "Search shipping configs...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs(Button, { size: "sm", onClick: handleCreate, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), _jsx("span", { children: "New Config" })] }) }), _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 dark:bg-red-950/40 dark:border-red-900/60 px-4 py-3 text-sm text-red-700 dark:text-red-200", children: errorMessage })), isLoading ? (_jsx(Div, { className: "space-y-2", children: Array.from({ length: 3 }).map((_, i) => (_jsx(Div, { className: "h-14 animate-pulse rounded-xl border border-zinc-100 dark:border-slate-700 bg-zinc-50 dark:bg-slate-800" }, i))) })) : rows.length === 0 ? (_jsxs(Div, { className: "py-16 text-center", children: [_jsx(Text, { className: "text-zinc-400 dark:text-zinc-500", children: "No shipping configs yet \u2014 define your first shipping rule" }), _jsx("div", { className: "mt-4", children: _jsx(Button, { size: "sm", onClick: handleCreate, children: "Add shipping config" }) })] })) : (_jsx(DataTable, { columns: COLUMNS, data: rows, keyExtractor: (r) => r.id, actions: (row) => (_jsx(RowActionMenu, { actions: [
129
130
  {
130
- label: "Edit",
131
+ label: ACTIONS.STORE["edit-listing"].label,
131
132
  onClick: () => handleEdit(row.id),
132
133
  },
133
134
  ...(!row.isDefault
@@ -140,7 +141,7 @@ export function SellerShippingConfigsView({ onCreateClick, onEditClick, onDelete
140
141
  ]
141
142
  : []),
142
143
  {
143
- label: "Delete",
144
+ label: ACTIONS.STORE["delete-listing"].label,
144
145
  destructive: true,
145
146
  onClick: () => setDeleteTargetId(row.id),
146
147
  disabled: deletingId === row.id,
@@ -6,6 +6,7 @@ import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
6
  import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
7
7
  import { BulkActionBar, Button, ConfirmDeleteModal, DataTable, Div, ListingToolbar, Pagination, RowActionMenu, Text, } from "../../../ui";
8
8
  import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
9
+ import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
9
10
  import { ROUTES } from "../../../next";
10
11
  import { toRecordArray, toStringValue, useSellerListingData, } from "../hooks/useSellerListingData";
11
12
  import { TABLE_KEYS } from "../../../constants/table-keys";
@@ -83,18 +84,18 @@ export function SellerStoreCategoriesView({ onCreateClick, onEditClick, onDelete
83
84
  const bulkActions = [
84
85
  ...(onBulkDelete ? [{
85
86
  id: "bulk-delete",
86
- label: "Delete selected",
87
+ label: ACTIONS.STORE["delete-listing"].label,
87
88
  variant: "danger",
88
89
  onClick: async () => { await onBulkDelete(selection.selectedIds); selection.clearSelection(); refetch?.(); },
89
90
  }] : []),
90
91
  ...(onBulkActivate ? [{
91
92
  id: "bulk-activate",
92
- label: "Activate",
93
+ label: ACTIONS.ADMIN["activate-bundle"].label,
93
94
  onClick: async () => { await onBulkActivate(selection.selectedIds); selection.clearSelection(); refetch?.(); },
94
95
  }] : []),
95
96
  ...(onBulkDeactivate ? [{
96
97
  id: "bulk-deactivate",
97
- label: "Deactivate",
98
+ label: ACTIONS.ADMIN["deactivate-bundle"].label,
98
99
  onClick: async () => { await onBulkDeactivate(selection.selectedIds); selection.clearSelection(); refetch?.(); },
99
100
  }] : []),
100
101
  ];
@@ -107,13 +108,13 @@ export function SellerStoreCategoriesView({ onCreateClick, onEditClick, onDelete
107
108
  };
108
109
  return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: 0, searchValue: searchInput, searchPlaceholder: "Search categories by label...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, showTableView: true, view: "table", onViewChange: () => { }, onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs(Button, { size: "sm", onClick: handleNavigateNew, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), _jsx("span", { children: "New Category" })] }) }), 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) }) })), selection.selectedCount > 0 && (_jsx(BulkActionBar, { selectedCount: selection.selectedCount, actions: bulkActions, onClearSelection: selection.clearSelection })), _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 dark:bg-red-950/40 dark:border-red-900/60 px-4 py-3 text-sm text-red-700 dark:text-red-200", children: errorMessage })), isLoading ? (_jsx(Div, { className: "space-y-2", children: Array.from({ length: 5 }).map((_, i) => (_jsx(Div, { className: "h-14 animate-pulse rounded-xl border border-zinc-100 dark:border-slate-700 bg-zinc-50 dark:bg-slate-800" }, i))) })) : rows.length === 0 ? (_jsx(Div, { className: "py-16 text-center", children: _jsx(Text, { className: "text-zinc-400 dark:text-zinc-500", children: "No categories yet \u2014 add your first storefront category" }) })) : (_jsx(DataTable, { columns: COLUMNS, data: rows, keyExtractor: (r) => r.id, selectable: bulkActions.length > 0, selectedIds: selection.selectedIds, onSelectionChange: (ids) => selection.setSelectedIds(ids), actions: (row) => (_jsx(RowActionMenu, { actions: [
109
110
  {
110
- label: "Edit",
111
+ label: ACTIONS.STORE["edit-listing"].label,
111
112
  onClick: () => onEditClick
112
113
  ? onEditClick(row.id)
113
114
  : (window.location.href = String(ROUTES.STORE.STORE_CATEGORIES_EDIT(row.id))),
114
115
  },
115
116
  ...(onDelete ? [{
116
- label: "Delete",
117
+ label: ACTIONS.STORE["delete-listing"].label,
117
118
  destructive: true,
118
119
  onClick: () => setDeleteTargetId(row.id),
119
120
  disabled: deletingId === row.id,
@@ -6,6 +6,7 @@ import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
6
  import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
7
7
  import { BulkActionBar, Button, ConfirmDeleteModal, DataTable, Div, FilterDrawer, Input, ListingToolbar, Pagination, RowActionMenu, Select, SideDrawer, Stack, Text, } from "../../../ui";
8
8
  import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
9
+ import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
9
10
  import { toRecordArray, toStringValue, useSellerListingData, } from "../hooks/useSellerListingData";
10
11
  import { TABLE_KEYS } from "../../../constants/table-keys";
11
12
  const PAGE_SIZE = 25;
@@ -226,7 +227,7 @@ export function SellerTemplatesView({ onDelete, onBulkDelete, onDuplicate, }) {
226
227
  const bulkActions = [
227
228
  {
228
229
  id: "bulk-delete",
229
- label: "Delete selected",
230
+ label: ACTIONS.STORE["delete-template"].label,
230
231
  variant: "danger",
231
232
  onClick: async () => {
232
233
  if (onBulkDelete) {
@@ -247,7 +248,7 @@ export function SellerTemplatesView({ onDelete, onBulkDelete, onDuplicate, }) {
247
248
  ? "No templates match your search or filters"
248
249
  : "No templates yet — add your first product template" }) })) : (_jsx(DataTable, { columns: COLUMNS, data: pageRows, keyExtractor: (r) => r.id, selectable: bulkActions.length > 0, selectedIds: selection.selectedIds, onSelectionChange: (ids) => selection.setSelectedIds(ids), actions: (row) => (_jsx(RowActionMenu, { actions: [
249
250
  {
250
- label: "Edit",
251
+ label: ACTIONS.STORE["edit-template"].label,
251
252
  onClick: () => openEdit(row),
252
253
  },
253
254
  {
@@ -256,7 +257,7 @@ export function SellerTemplatesView({ onDelete, onBulkDelete, onDuplicate, }) {
256
257
  onClick: () => handleDuplicate(row),
257
258
  },
258
259
  {
259
- label: "Delete",
260
+ label: ACTIONS.STORE["delete-template"].label,
260
261
  destructive: true,
261
262
  onClick: () => setDeleteTargetId(row.id),
262
263
  disabled: deletingId === row.id,
@@ -1,18 +1,19 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import React from "react";
4
4
  const CLS_SECTION_CARD = "border border-zinc-200 dark:border-zinc-700 rounded-xl p-5";
5
5
  import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
6
- import { Alert, Button, Div, Form, FormActions, Heading, Input, Section, Text, Toggle, useToast } from "../../../ui";
6
+ import { Alert, Badge, Button, Div, Form, FormActions, Heading, Input, Row, Section, Stack, Text, Toggle, useToast } from "../../../ui";
7
7
  import { apiClient } from "../../../http";
8
8
  import { WHATSAPP_SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
9
+ import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
9
10
  import { buildPurchaseAnnouncementMessage } from "../helpers/whatsapp";
10
11
  // ---------------------------------------------------------------------------
11
12
  // Sub-components
12
13
  // ---------------------------------------------------------------------------
13
14
  function MaskedTokenInput({ label, value, onChange, placeholder, helperText, }) {
14
15
  const [revealed, setRevealed] = React.useState(false);
15
- return (_jsxs("div", { className: "relative", children: [_jsx(Input, { label: label, value: value, onChange: (e) => onChange(e.target.value), type: revealed ? "text" : "password", placeholder: placeholder, helperText: helperText }), _jsx("button", { type: "button", onClick: () => setRevealed((r) => !r), className: "absolute right-3 top-8 text-xs text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-200", children: revealed ? "Hide" : "Reveal" })] }));
16
+ return (_jsxs(Div, { className: "relative", children: [_jsx(Input, { label: label, value: value, onChange: (e) => onChange(e.target.value), type: revealed ? "text" : "password", placeholder: placeholder, helperText: helperText }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setRevealed((r) => !r), className: "absolute right-3 top-8 text-xs", children: revealed ? "Hide" : "Reveal" })] }));
16
17
  }
17
18
  const STEPS = [
18
19
  {
@@ -46,10 +47,10 @@ const STEPS = [
46
47
  checkKey: "connected",
47
48
  },
48
49
  ];
49
- const STATUS_COLOR = {
50
- success: "bg-green-100 text-green-800 dark:bg-green-900/40 dark:text-green-300",
51
- partial: "bg-amber-100 text-amber-800 dark:bg-amber-900/40 dark:text-amber-300",
52
- failed: "bg-red-100 text-red-800 dark:bg-red-900/40 dark:text-red-300",
50
+ const STATUS_VARIANT = {
51
+ success: "success",
52
+ partial: "warning",
53
+ failed: "danger",
53
54
  };
54
55
  // ---------------------------------------------------------------------------
55
56
  // Sample catalog items for preview
@@ -115,7 +116,7 @@ export function SellerWhatsAppSettingsView({ hasCapability }) {
115
116
  showToast(msg, "error");
116
117
  },
117
118
  });
118
- // Catalog sync mutation
119
+ // Catalog sync mutation (push: site → WhatsApp)
119
120
  const syncMutation = useMutation({
120
121
  mutationFn: async () => apiClient.post(WHATSAPP_SELLER_ENDPOINTS.CATALOG_SYNC, {}),
121
122
  onSuccess: (res) => {
@@ -128,28 +129,41 @@ export function SellerWhatsAppSettingsView({ hasCapability }) {
128
129
  showToast(msg, "error");
129
130
  },
130
131
  });
132
+ // Catalog import mutation (pull: WhatsApp → site)
133
+ const importMutation = useMutation({
134
+ mutationFn: async () => apiClient.post(WHATSAPP_SELLER_ENDPOINTS.CATALOG_IMPORT, {}),
135
+ onSuccess: (res) => {
136
+ const r = res ?? {};
137
+ showToast(`Imported ${r.imported ?? 0} product${(r.imported ?? 0) !== 1 ? "s" : ""} from WhatsApp (${r.skipped ?? 0} already synced)`, "success");
138
+ void queryClient.invalidateQueries({ queryKey: ["store", "whatsapp-settings"] });
139
+ },
140
+ onError: (err) => {
141
+ const msg = err instanceof Error ? err.message : "Import failed";
142
+ showToast(msg, "error");
143
+ },
144
+ });
131
145
  if (!hasCapability) {
132
- return (_jsx("div", { className: "max-w-xl mx-auto py-8 px-4", children: _jsxs(Alert, { variant: "warning", children: [_jsx(Text, { className: "font-medium", children: "WhatsApp catalog sync is not enabled for your store." }), _jsx(Text, { className: "text-sm mt-1", children: "Contact LetItRip support to request access to the WhatsApp Business integration." })] }) }));
146
+ return (_jsx(Div, { className: "max-w-xl mx-auto py-8 px-4", children: _jsxs(Alert, { variant: "warning", children: [_jsx(Text, { className: "font-medium", children: "WhatsApp catalog sync is not enabled for your store." }), _jsx(Text, { className: "text-sm mt-1", children: "Contact LetItRip support to request access to the WhatsApp Business integration." })] }) }));
133
147
  }
134
- return (_jsxs("div", { className: "max-w-2xl mx-auto space-y-8 py-6 px-4", children: [_jsxs(Section, { children: [_jsx(Heading, { level: 2, className: "text-base font-semibold text-zinc-900 dark:text-zinc-100 mb-4", children: "How to connect your WhatsApp Business account" }), _jsx("ol", { className: "space-y-3", children: STEPS.map((step) => {
148
+ return (_jsxs(Stack, { gap: "xl", className: "max-w-2xl mx-auto py-6 px-4", children: [_jsxs(Section, { children: [_jsx(Heading, { level: 2, className: "text-base font-semibold text-zinc-900 dark:text-zinc-100 mb-4", children: "How to connect your WhatsApp Business account" }), _jsx(Stack, { gap: "sm", children: STEPS.map((step) => {
135
149
  const done = step.checkKey === undefined
136
150
  ? false
137
151
  : step.checkKey === "connected"
138
152
  ? cfg?.connected === true
139
153
  : Boolean(cfg?.[step.checkKey]);
140
- return (_jsxs("li", { className: "flex gap-3", children: [_jsx("span", { className: `flex-shrink-0 w-6 h-6 rounded-full text-xs font-bold flex items-center justify-center mt-0.5 ${done
141
- ? "bg-green-500 text-white"
142
- : "bg-zinc-200 dark:bg-zinc-700 text-zinc-600 dark:text-zinc-300"}`, children: done ? "✓" : step.n }), _jsxs(_Fragment, { children: [_jsx(Text, { className: "text-sm font-medium text-zinc-900 dark:text-zinc-100", children: step.title }), _jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400 mt-0.5", children: step.body })] })] }, step.n));
143
- }) })] }), _jsxs(Section, { className: CLS_SECTION_CARD, children: [_jsxs("div", { className: "flex items-center justify-between mb-4", children: [_jsx(Heading, { level: 2, className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: "Connection" }), isLoading ? null : cfg?.connected ? (_jsx("span", { className: "text-xs font-medium px-2 py-0.5 rounded-full bg-green-100 text-green-800 dark:bg-green-900/40 dark:text-green-300", children: "Connected" })) : (_jsx("span", { className: "text-xs font-medium px-2 py-0.5 rounded-full bg-zinc-100 text-zinc-500 dark:bg-zinc-800 dark:text-zinc-400", children: "Not configured" }))] }), _jsxs(Form, { onSubmit: (e) => {
154
+ return (_jsxs(Row, { gap: "sm", align: "start", children: [_jsx(Div, { className: `flex-shrink-0 w-6 h-6 rounded-full text-xs font-bold flex items-center justify-center mt-0.5 ${done
155
+ ? "bg-[var(--appkit-color-success)] text-white"
156
+ : "bg-zinc-200 dark:bg-zinc-700 text-zinc-600 dark:text-zinc-300"}`, children: done ? "✓" : step.n }), _jsxs(Div, { children: [_jsx(Text, { className: "text-sm font-medium text-zinc-900 dark:text-zinc-100", children: step.title }), _jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400 mt-0.5", children: step.body })] })] }, step.n));
157
+ }) })] }), _jsxs(Section, { className: CLS_SECTION_CARD, children: [_jsxs(Row, { justify: "between", align: "center", className: "mb-4", children: [_jsx(Heading, { level: 2, className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: "Connection" }), isLoading ? null : cfg?.connected ? (_jsx(Badge, { variant: "success", children: "Connected" })) : (_jsx(Badge, { variant: "default", children: "Not configured" }))] }), _jsxs(Form, { onSubmit: (e) => {
144
158
  e.preventDefault();
145
159
  saveMutation.mutate();
146
- }, className: "space-y-4", children: [_jsx(Input, { label: "WhatsApp Business Phone Number", value: phoneNumber, onChange: (e) => setPhoneNumber(e.target.value), placeholder: "919876543210", helperText: "Digits only, include country code (e.g. 91 for India)" }), _jsx(Input, { label: "WABA ID (WhatsApp Business Account ID)", value: wabaId, onChange: (e) => setWabaId(e.target.value), placeholder: "123456789012345", helperText: "From Meta Business Manager \u2192 WhatsApp Accounts" }), _jsx(Input, { label: "Catalog ID", value: catalogId, onChange: (e) => setCatalogId(e.target.value), placeholder: "987654321098765", helperText: "From Meta Commerce Manager \u2192 Catalogs" }), _jsx(MaskedTokenInput, { label: "System User Access Token", value: accessToken, onChange: setAccessToken, placeholder: "EAAxxxxxxxx\u2026", helperText: "Long-lived token with WhatsApp Business permissions" }), _jsx(FormActions, { align: "right", children: _jsx(Button, { type: "submit", isLoading: saveMutation.isPending, disabled: saveMutation.isPending, children: saveMutation.isPending ? "Saving…" : "Save & Connect" }) })] })] }), _jsxs(Section, { className: CLS_SECTION_CARD, children: [_jsx(Heading, { level: 2, className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100 mb-4", children: "Catalog Sync" }), _jsxs("div", { className: "flex items-center justify-between mb-4", children: [_jsxs(_Fragment, { children: [_jsx(Text, { className: "text-sm text-zinc-700 dark:text-zinc-300", children: "Enable catalog sync" }), _jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: "When enabled, your published standard products can be synced to WhatsApp." })] }), _jsx(Toggle, { checked: syncEnabled, onChange: setSyncEnabled, disabled: !cfg?.connected })] }), cfg?.lastCatalogSyncAt && (_jsxs("div", { className: "flex items-center gap-2 text-xs text-zinc-500 dark:text-zinc-400 mb-3", children: [_jsxs("span", { children: ["Last sync: ", new Date(cfg.lastCatalogSyncAt).toLocaleString("en-IN")] }), cfg.lastSyncCount !== undefined && _jsxs("span", { children: ["\u00B7 ", cfg.lastSyncCount, " products"] }), cfg.lastSyncStatus && (_jsx("span", { className: `px-1.5 py-0.5 rounded ${STATUS_COLOR[cfg.lastSyncStatus] ?? ""}`, children: cfg.lastSyncStatus }))] })), _jsx(Text, { className: "text-xs text-zinc-400 dark:text-zinc-500 mb-3", children: "Only published standard products are synced. Auctions and pre-orders are excluded. Batches of up to 50 products per call." }), _jsx(Button, { onClick: () => syncMutation.mutate(), isLoading: syncMutation.isPending, disabled: !cfg?.connected || !syncEnabled || syncMutation.isPending, variant: "secondary", children: syncMutation.isPending ? "Syncing…" : "Sync Now" })] }), _jsxs(Section, { className: CLS_SECTION_CARD, children: [_jsx(Heading, { level: 2, className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100 mb-2", children: "Purchase Announcement Preview" }), _jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400 mb-3", children: "This message is sent automatically to your phone and the platform admin when a new order is placed." }), _jsx("div", { className: "bg-zinc-50 dark:bg-zinc-800/60 rounded-lg px-4 py-3 text-sm text-zinc-700 dark:text-zinc-200 font-mono", children: buildPurchaseAnnouncementMessage({
160
+ }, className: "space-y-4", children: [_jsx(Input, { label: "WhatsApp Business Phone Number", value: phoneNumber, onChange: (e) => setPhoneNumber(e.target.value), placeholder: "919876543210", helperText: "Digits only, include country code (e.g. 91 for India)" }), _jsx(Input, { label: "WABA ID (WhatsApp Business Account ID)", value: wabaId, onChange: (e) => setWabaId(e.target.value), placeholder: "123456789012345", helperText: "From Meta Business Manager \u2192 WhatsApp Accounts" }), _jsx(Input, { label: "Catalog ID", value: catalogId, onChange: (e) => setCatalogId(e.target.value), placeholder: "987654321098765", helperText: "From Meta Commerce Manager \u2192 Catalogs" }), _jsx(MaskedTokenInput, { label: "System User Access Token", value: accessToken, onChange: setAccessToken, placeholder: "EAAxxxxxxxx\u2026", helperText: "Long-lived token with WhatsApp Business permissions" }), _jsx(FormActions, { align: "right", children: _jsx(Button, { type: "submit", isLoading: saveMutation.isPending, disabled: saveMutation.isPending, children: saveMutation.isPending ? "Saving…" : ACTIONS.STORE["whatsapp-connect"].label }) })] })] }), _jsxs(Section, { className: CLS_SECTION_CARD, children: [_jsx(Heading, { level: 2, className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100 mb-4", children: "Catalog Sync" }), _jsxs(Row, { justify: "between", align: "center", className: "mb-4", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-sm text-zinc-700 dark:text-zinc-300", children: "Enable catalog sync" }), _jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: "When enabled, your published standard products can be synced to WhatsApp." })] }), _jsx(Toggle, { checked: syncEnabled, onChange: setSyncEnabled, disabled: !cfg?.connected })] }), cfg?.lastCatalogSyncAt && (_jsxs(Row, { gap: "sm", align: "center", className: "text-xs text-zinc-500 dark:text-zinc-400 mb-3", children: [_jsxs(Text, { children: ["Last sync: ", new Date(cfg.lastCatalogSyncAt).toLocaleString("en-IN")] }), cfg.lastSyncCount !== undefined && _jsxs(Text, { children: ["\u00B7 ", cfg.lastSyncCount, " products"] }), cfg.lastSyncStatus && (_jsx(Badge, { variant: STATUS_VARIANT[cfg.lastSyncStatus] ?? "default", children: cfg.lastSyncStatus }))] })), _jsx(Text, { className: "text-xs text-zinc-400 dark:text-zinc-500 mb-3", children: "Only published standard products are synced. Auctions and pre-orders are excluded. Batches of up to 50 products per call." }), _jsxs(Div, { className: "flex gap-3", children: [_jsx(Button, { onClick: () => syncMutation.mutate(), isLoading: syncMutation.isPending, disabled: !cfg?.connected || !syncEnabled || syncMutation.isPending, variant: "secondary", children: syncMutation.isPending ? "Syncing…" : ACTIONS.STORE["whatsapp-catalog-sync"].label }), _jsx(Button, { onClick: () => importMutation.mutate(), isLoading: importMutation.isPending, disabled: !cfg?.connected || !syncEnabled || importMutation.isPending, variant: "secondary", children: importMutation.isPending ? "Importing…" : ACTIONS.STORE["whatsapp-catalog-import"].label })] }), _jsxs(Text, { className: "text-xs text-zinc-400 dark:text-zinc-500 mt-3", children: [_jsx("strong", { children: "Push" }), " sends your published standard products to WhatsApp.", " ", _jsx("strong", { children: "Import" }), " creates draft products from your WhatsApp catalog. Products are matched by slug in the description field."] })] }), _jsxs(Section, { className: CLS_SECTION_CARD, children: [_jsx(Heading, { level: 2, className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100 mb-2", children: "Purchase Announcement Preview" }), _jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400 mb-3", children: "This message is sent automatically to your phone and the platform admin when a new order is placed." }), _jsx(Div, { className: "bg-zinc-50 dark:bg-zinc-800/60 rounded-lg px-4 py-3 text-sm text-zinc-700 dark:text-zinc-200 font-mono", children: buildPurchaseAnnouncementMessage({
147
161
  buyerName: "Ravi K.",
148
162
  firstItemName: "Charizard PSA 9",
149
163
  additionalItemCount: 2,
150
164
  totalAmount: 450000,
151
165
  orderId: "order-3-20260510-a1b2c3",
152
- }) })] }), _jsxs(Section, { className: CLS_SECTION_CARD, children: [_jsx(Heading, { level: 2, className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100 mb-2", children: "Catalog Preview" }), _jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400 mb-4", children: "This is how your products appear in the WhatsApp Catalog when a buyer taps \"View Catalog\" in the chat. Only published standard products are included." }), _jsxs("div", { className: "border border-zinc-200 dark:border-zinc-700 rounded-xl overflow-hidden bg-[#ECE5DD] dark:bg-zinc-800 p-3", children: [_jsxs("div", { className: "flex items-center gap-2 mb-3", children: [_jsx("div", { className: "w-8 h-8 rounded-full bg-[#25D366] flex items-center justify-center", children: _jsx("span", { className: "text-white text-xs font-bold", children: "W" }) }), _jsxs(Div, { children: [_jsx(Text, { className: "text-xs font-semibold text-zinc-900 dark:text-zinc-100", children: cfg?.connected ? "Your Store" : "Store Name" }), _jsx(Text, { className: "text-[10px] text-zinc-500 dark:text-zinc-400", children: "WhatsApp Business" })] })] }), _jsx("div", { className: "grid grid-cols-2 gap-2", children: SAMPLE_CATALOG_ITEMS.map((item) => (_jsxs("div", { className: "bg-white dark:bg-zinc-900 rounded-lg overflow-hidden shadow-sm", children: [_jsx("div", { className: "aspect-square bg-zinc-100 dark:bg-zinc-800 flex items-center justify-center", children: _jsx("span", { className: "text-2xl", children: item.emoji }) }), _jsxs("div", { className: "p-2", children: [_jsx(Text, { className: "text-xs font-medium text-zinc-900 dark:text-zinc-100 line-clamp-2 leading-tight", children: item.name }), _jsx(Text, { className: "text-xs text-[#25D366] font-semibold mt-0.5", children: item.price })] })] }, item.id))) }), _jsx("div", { className: "mt-3 text-center", children: _jsx(Text, { className: "text-[10px] text-zinc-500 dark:text-zinc-400", children: cfg?.lastSyncCount
166
+ }) })] }), _jsxs(Section, { className: CLS_SECTION_CARD, children: [_jsx(Heading, { level: 2, className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100 mb-2", children: "Catalog Preview" }), _jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400 mb-4", children: "This is how your products appear in the WhatsApp Catalog when a buyer taps \"View Catalog\" in the chat. Only published standard products are included." }), _jsxs(Div, { className: "border border-zinc-200 dark:border-zinc-700 rounded-xl overflow-hidden bg-[#ECE5DD] dark:bg-zinc-800 p-3", children: [_jsxs(Row, { gap: "sm", align: "center", className: "mb-3", children: [_jsx(Div, { className: "w-8 h-8 rounded-full bg-[#25D366] flex items-center justify-center", children: _jsx(Text, { className: "text-white text-xs font-bold", children: "W" }) }), _jsxs(Div, { children: [_jsx(Text, { className: "text-xs font-semibold text-zinc-900 dark:text-zinc-100", children: cfg?.connected ? "Your Store" : "Store Name" }), _jsx(Text, { className: "text-[10px] text-zinc-500 dark:text-zinc-400", children: "WhatsApp Business" })] })] }), _jsx(Div, { className: "grid grid-cols-2 gap-2", children: SAMPLE_CATALOG_ITEMS.map((item) => (_jsxs(Div, { className: "bg-white dark:bg-zinc-900 rounded-lg overflow-hidden shadow-sm", children: [_jsx(Div, { className: "aspect-square bg-zinc-100 dark:bg-zinc-800 flex items-center justify-center", children: _jsx(Text, { className: "text-2xl", children: item.emoji }) }), _jsxs(Div, { className: "p-2", children: [_jsx(Text, { className: "text-xs font-medium text-zinc-900 dark:text-zinc-100 line-clamp-2 leading-tight", children: item.name }), _jsx(Text, { className: "text-xs text-[#25D366] font-semibold mt-0.5", children: item.price })] })] }, item.id))) }), _jsx(Div, { className: "mt-3 text-center", children: _jsx(Text, { className: "text-[10px] text-zinc-500 dark:text-zinc-400", children: cfg?.lastSyncCount
153
167
  ? `${cfg.lastSyncCount} products synced to catalog`
154
168
  : "Sync your products to populate the catalog" }) })] }), !cfg?.connected && (_jsx(Text, { className: "text-xs text-amber-600 dark:text-amber-400 mt-2", children: "Connect your WhatsApp Business account above to enable the catalog." }))] })] }));
155
169
  }