@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.
- package/dist/_internal/server/jobs/runtime/adapters/firebase.js +5 -14
- package/dist/_internal/shared/actions/action-registry.js +827 -4
- package/dist/_internal/shared/features/events/schema.d.ts +4 -4
- package/dist/constants/api-endpoints.d.ts +3 -0
- package/dist/constants/api-endpoints.js +1 -0
- package/dist/features/account/components/UserOffersPanel.js +2 -1
- package/dist/features/admin/components/AdminAllEventEntriesView.js +4 -3
- package/dist/features/admin/components/AdminBidsView.js +5 -3
- package/dist/features/admin/components/AdminBlogView.js +3 -2
- package/dist/features/admin/components/AdminBrandsView.js +2 -1
- package/dist/features/admin/components/AdminBundleEditorView.js +4 -5
- package/dist/features/admin/components/AdminBundlesView.js +3 -3
- package/dist/features/admin/components/AdminCarouselView.d.ts +2 -1
- package/dist/features/admin/components/AdminCarouselView.js +14 -5
- package/dist/features/admin/components/AdminCategoriesView.js +2 -1
- package/dist/features/admin/components/AdminContactView.d.ts +4 -1
- package/dist/features/admin/components/AdminContactView.js +27 -7
- package/dist/features/admin/components/AdminCouponsView.d.ts +3 -1
- package/dist/features/admin/components/AdminCouponsView.js +17 -3
- package/dist/features/admin/components/AdminFaqsView.d.ts +3 -1
- package/dist/features/admin/components/AdminFaqsView.js +18 -4
- package/dist/features/admin/components/AdminFeaturesView.js +2 -1
- package/dist/features/admin/components/AdminMediaView.js +3 -2
- package/dist/features/admin/components/AdminNavigationView.js +3 -2
- package/dist/features/admin/components/AdminNewsletterView.d.ts +2 -1
- package/dist/features/admin/components/AdminNewsletterView.js +13 -4
- package/dist/features/admin/components/AdminNotificationsView.js +6 -4
- package/dist/features/admin/components/AdminOrdersView.js +4 -3
- package/dist/features/admin/components/AdminPayoutsView.js +2 -2
- package/dist/features/admin/components/AdminPrizeDrawsView.js +2 -1
- package/dist/features/admin/components/AdminProductsView.js +3 -3
- package/dist/features/admin/components/AdminReviewsView.js +4 -3
- package/dist/features/admin/components/AdminScammersView.js +2 -1
- package/dist/features/admin/components/AdminSessionsView.js +5 -3
- package/dist/features/admin/components/AdminStoresView.js +2 -2
- package/dist/features/admin/components/AdminSublistingCategoriesView.d.ts +5 -1
- package/dist/features/admin/components/AdminSublistingCategoriesView.js +12 -3
- package/dist/features/admin/components/AdminSupportTicketsView.js +2 -1
- package/dist/features/admin/components/AdminTeamView.d.ts +2 -1
- package/dist/features/admin/components/AdminTeamView.js +13 -4
- package/dist/features/admin/components/AdminUsersView.js +2 -2
- package/dist/features/products/constants/action-defs.d.ts +86 -7
- package/dist/features/products/constants/action-defs.js +99 -7
- package/dist/features/seller/components/QuickProductForm.d.ts +13 -0
- package/dist/features/seller/components/QuickProductForm.js +50 -0
- package/dist/features/seller/components/SellerAuctionsView.d.ts +4 -1
- package/dist/features/seller/components/SellerAuctionsView.js +37 -3
- package/dist/features/seller/components/SellerBidsView.js +2 -1
- package/dist/features/seller/components/SellerBundlesView.d.ts +3 -1
- package/dist/features/seller/components/SellerBundlesView.js +38 -5
- package/dist/features/seller/components/SellerClassifiedView.d.ts +3 -1
- package/dist/features/seller/components/SellerClassifiedView.js +37 -4
- package/dist/features/seller/components/SellerDigitalCodesView.d.ts +3 -1
- package/dist/features/seller/components/SellerDigitalCodesView.js +37 -4
- package/dist/features/seller/components/SellerGoogleReviewsView.js +2 -1
- package/dist/features/seller/components/SellerGroupedListingsView.js +3 -2
- package/dist/features/seller/components/SellerLiveView.d.ts +3 -1
- package/dist/features/seller/components/SellerLiveView.js +37 -4
- package/dist/features/seller/components/SellerOffersPanel.js +2 -1
- package/dist/features/seller/components/SellerOffersView.d.ts +4 -1
- package/dist/features/seller/components/SellerOffersView.js +8 -3
- package/dist/features/seller/components/SellerPayoutsView.d.ts +3 -1
- package/dist/features/seller/components/SellerPayoutsView.js +7 -3
- package/dist/features/seller/components/SellerPreOrdersView.d.ts +2 -1
- package/dist/features/seller/components/SellerPreOrdersView.js +30 -7
- package/dist/features/seller/components/SellerPrizeDrawsView.d.ts +2 -1
- package/dist/features/seller/components/SellerPrizeDrawsView.js +30 -7
- package/dist/features/seller/components/SellerProductShell.js +12 -0
- package/dist/features/seller/components/SellerReviewsView.js +2 -1
- package/dist/features/seller/components/SellerShippingConfigsView.js +3 -2
- package/dist/features/seller/components/SellerStoreCategoriesView.js +6 -5
- package/dist/features/seller/components/SellerTemplatesView.js +4 -3
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +30 -16
- package/dist/styles.css +1 -1
- package/dist/tailwind-utilities.css +1 -1
- 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 {
|
|
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(
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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" : "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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
|
|
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(
|
|
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
|
|
50
|
-
success: "
|
|
51
|
-
partial: "
|
|
52
|
-
failed: "
|
|
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(
|
|
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("
|
|
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(
|
|
141
|
-
? "bg-
|
|
142
|
-
: "bg-zinc-200 dark:bg-zinc-700 text-zinc-600 dark:text-zinc-300"}`, children: done ? "✓" : step.n }), _jsxs(
|
|
143
|
-
}) })] }), _jsxs(Section, { className: CLS_SECTION_CARD, children: [_jsxs(
|
|
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…" : "
|
|
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(
|
|
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
|
}
|