@mohasinac/appkit 2.7.50 → 2.7.52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_internal/server/jobs/core/__tests__/couponExpiry.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/couponExpiry.test.js +65 -0
- package/dist/_internal/server/jobs/core/__tests__/listingProcessor.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/listingProcessor.test.js +163 -0
- package/dist/_internal/server/jobs/core/__tests__/mediaTmpCleanup.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/mediaTmpCleanup.test.js +115 -0
- package/dist/_internal/server/jobs/core/__tests__/pendingOrderTimeout.test.d.ts +1 -0
- package/dist/_internal/server/jobs/core/__tests__/pendingOrderTimeout.test.js +96 -0
- package/dist/client.d.ts +29 -6
- package/dist/client.js +16 -4
- package/dist/constants/api-endpoints.d.ts +42 -0
- package/dist/constants/api-endpoints.js +14 -0
- package/dist/features/_guide-cls.d.ts +13 -0
- package/dist/features/_guide-cls.js +14 -0
- package/dist/features/about/components/HelpPageView.js +29 -26
- package/dist/features/account/components/BuyerAccountGuideView.d.ts +1 -0
- package/dist/features/account/components/BuyerAccountGuideView.js +39 -0
- package/dist/features/account/components/BuyerAuctionsGuideView.d.ts +1 -0
- package/dist/features/account/components/BuyerAuctionsGuideView.js +38 -0
- package/dist/features/account/components/BuyerOrdersGuideView.d.ts +1 -0
- package/dist/features/account/components/BuyerOrdersGuideView.js +48 -0
- package/dist/features/account/components/BuyerShoppingGuideView.d.ts +1 -0
- package/dist/features/account/components/BuyerShoppingGuideView.js +38 -0
- package/dist/features/admin/components/AdminAnalyticsGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminAnalyticsGuideView.js +24 -0
- package/dist/features/admin/components/AdminBlogEditorView.js +100 -77
- package/dist/features/admin/components/AdminBundleEditorView.js +20 -14
- package/dist/features/admin/components/AdminBundlesView.d.ts +1 -3
- package/dist/features/admin/components/AdminBundlesView.js +173 -52
- package/dist/features/admin/components/AdminCatalogGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminCatalogGuideView.js +28 -0
- package/dist/features/admin/components/AdminContentGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminContentGuideView.js +36 -0
- package/dist/features/admin/components/AdminCouponEditorView.js +1 -1
- package/dist/features/admin/components/AdminGuideHubView.d.ts +5 -0
- package/dist/features/admin/components/AdminGuideHubView.js +79 -0
- package/dist/features/admin/components/AdminOrdersGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminOrdersGuideView.js +44 -0
- package/dist/features/admin/components/AdminProductsView.js +8 -3
- package/dist/features/admin/components/AdminSiteConfigGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminSiteConfigGuideView.js +21 -0
- package/dist/features/admin/components/AdminStoresGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminStoresGuideView.js +32 -0
- package/dist/features/admin/components/AdminTeamGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminTeamGuideView.js +33 -0
- package/dist/features/admin/components/AdminTrustGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminTrustGuideView.js +40 -0
- package/dist/features/admin/components/AdminUsersGuideView.d.ts +1 -0
- package/dist/features/admin/components/AdminUsersGuideView.js +38 -0
- package/dist/features/categories/components/CategoryBundlesListing.js +3 -33
- package/dist/features/categories/components/index.d.ts +0 -2
- package/dist/features/categories/components/index.js +0 -1
- package/dist/features/events/components/AdminEventEditorView.js +223 -269
- package/dist/features/grouped/repository/grouped-listings.repository.d.ts +9 -0
- package/dist/features/grouped/repository/grouped-listings.repository.js +12 -0
- package/dist/features/media/upload/MediaUploadField.js +1 -1
- package/dist/features/pre-orders/components/PreOrdersIndexListing.js +3 -1
- package/dist/features/products/components/AuctionsIndexListing.js +3 -1
- package/dist/features/products/components/MarketplaceBundleCard.d.ts +21 -0
- package/dist/features/products/components/MarketplaceBundleCard.js +56 -0
- package/dist/features/products/components/ProductsIndexListing.js +3 -1
- package/dist/features/products/components/index.d.ts +2 -4
- package/dist/features/products/components/index.js +1 -2
- package/dist/features/promotions/schemas/firestore.js +1 -1
- package/dist/features/reviews/components/index.d.ts +0 -2
- package/dist/features/reviews/components/index.js +0 -1
- package/dist/features/seller/components/BrandInlineSelect.js +23 -3
- package/dist/features/seller/components/CategoryInlineSelect.js +20 -3
- package/dist/features/seller/components/SellerAnalyticsAlertsView.d.ts +6 -0
- package/dist/features/seller/components/SellerAnalyticsAlertsView.js +124 -0
- package/dist/features/seller/components/SellerAnalyticsView.d.ts +2 -0
- package/dist/features/seller/components/SellerAnalyticsView.js +17 -9
- package/dist/features/seller/components/SellerBundlesView.d.ts +5 -0
- package/dist/features/seller/components/SellerBundlesView.js +104 -0
- package/dist/features/seller/components/SellerClassifiedView.d.ts +5 -0
- package/dist/features/seller/components/SellerClassifiedView.js +113 -0
- package/dist/features/seller/components/SellerDashboardView.js +19 -7
- package/dist/features/seller/components/SellerDigitalCodesView.d.ts +5 -0
- package/dist/features/seller/components/SellerDigitalCodesView.js +115 -0
- package/dist/features/seller/components/SellerGoogleReviewsView.d.ts +14 -0
- package/dist/features/seller/components/SellerGoogleReviewsView.js +95 -0
- package/dist/features/seller/components/SellerGroupedListingsView.d.ts +6 -0
- package/dist/features/seller/components/SellerGroupedListingsView.js +112 -0
- package/dist/features/seller/components/SellerLiveView.d.ts +5 -0
- package/dist/features/seller/components/SellerLiveView.js +113 -0
- package/dist/features/seller/components/SellerOrdersView.js +18 -2
- package/dist/features/seller/components/SellerPayoutMethodsView.d.ts +7 -0
- package/dist/features/seller/components/SellerPayoutMethodsView.js +120 -0
- package/dist/features/seller/components/SellerPayoutSettingsView.js +50 -12
- package/dist/features/seller/components/SellerProductShell.d.ts +1 -1
- package/dist/features/seller/components/SellerProductShell.js +9 -7
- package/dist/features/seller/components/SellerProductsFilterDrawer.js +3 -1
- package/dist/features/seller/components/SellerProductsView.js +48 -54
- package/dist/features/seller/components/SellerShippingConfigsView.d.ts +7 -0
- package/dist/features/seller/components/SellerShippingConfigsView.js +149 -0
- package/dist/features/seller/components/SellerShippingView.js +37 -10
- package/dist/features/seller/components/SellerStoreCategoriesView.d.ts +9 -0
- package/dist/features/seller/components/SellerStoreCategoriesView.js +122 -0
- package/dist/features/seller/components/SellerStorefrontView.d.ts +0 -7
- package/dist/features/seller/components/SellerStorefrontView.js +26 -29
- package/dist/features/seller/components/SellerTemplatesView.d.ts +10 -0
- package/dist/features/seller/components/SellerTemplatesView.js +265 -0
- package/dist/features/seller/components/index.d.ts +26 -0
- package/dist/features/seller/components/index.js +13 -0
- package/dist/features/stores/api/[storeSlug]/reviews/route.js +49 -7
- package/dist/features/stores/components/StoreCapabilitiesGuideView.d.ts +3 -0
- package/dist/features/stores/components/StoreCapabilitiesGuideView.js +101 -0
- package/dist/features/stores/components/StoreFinanceGuideView.d.ts +3 -0
- package/dist/features/stores/components/StoreFinanceGuideView.js +79 -0
- package/dist/features/stores/components/StoreGuideHubView.d.ts +5 -0
- package/dist/features/stores/components/StoreGuideHubView.js +89 -0
- package/dist/features/stores/components/StoreListingsGuideView.d.ts +3 -0
- package/dist/features/stores/components/StoreListingsGuideView.js +151 -0
- package/dist/features/stores/components/StoreOrdersGuideView.d.ts +3 -0
- package/dist/features/stores/components/StoreOrdersGuideView.js +122 -0
- package/dist/features/stores/components/StoreReviewsListing.js +100 -23
- package/dist/features/stores/components/StoreSettingsGuideView.d.ts +3 -0
- package/dist/features/stores/components/StoreSettingsGuideView.js +56 -0
- package/dist/features/stores/components/index.d.ts +12 -1
- package/dist/features/stores/components/index.js +6 -1
- package/dist/features/stores/hooks/useStores.d.ts +5 -0
- package/dist/features/stores/hooks/useStores.js +10 -0
- package/dist/features/stores/manifest.js +1 -1
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +13 -2
- package/dist/index.d.ts +54 -9
- package/dist/index.js +58 -15
- package/dist/next/routing/route-map.d.ts +55 -0
- package/dist/next/routing/route-map.js +25 -0
- package/dist/providers/db-firebase/__tests__/filter-aliases.test.d.ts +1 -0
- package/dist/providers/db-firebase/__tests__/filter-aliases.test.js +93 -0
- package/dist/providers/db-firebase/sieve.d.ts +49 -0
- package/dist/providers/db-firebase/sieve.js +61 -8
- package/dist/repositories/index.d.ts +1 -0
- package/dist/repositories/index.js +2 -0
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/ListingToolbar.d.ts +8 -1
- package/dist/ui/components/ListingToolbar.js +4 -2
- package/dist/ui/components/__tests__/BulkActionBar.test.d.ts +1 -0
- package/dist/ui/components/__tests__/BulkActionBar.test.js +96 -0
- package/dist/ui/components/__tests__/ListingToolbar.test.d.ts +1 -0
- package/dist/ui/components/__tests__/ListingToolbar.test.js +125 -0
- package/dist/ui/index.d.ts +1 -1
- package/package.json +8 -2
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import React from "react";
|
|
4
4
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
5
|
-
import { Button,
|
|
6
|
-
import {
|
|
7
|
-
import { ImageUpload } from "../../media/upload/ImageUpload";
|
|
8
|
-
import { useMediaUpload } from "../../media";
|
|
5
|
+
import { Button, ConfirmDeleteModal, Div, Heading, Input, RichTextEditor, RichTextRenderer, Select, StackedViewShell, TagInput, Text, Toggle, useToast, } from "../../../ui";
|
|
6
|
+
import { ImageUpload, useMediaUpload } from "../../media";
|
|
9
7
|
import { apiClient } from "../../../http";
|
|
10
8
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
9
|
+
import { StepForm } from "../../shell";
|
|
11
10
|
// --- Helpers -----------------------------------------------------------------
|
|
12
11
|
const STATUS_OPTIONS = [
|
|
13
12
|
{ label: "Draft", value: "draft" },
|
|
@@ -21,6 +20,22 @@ const CATEGORY_OPTIONS = [
|
|
|
21
20
|
{ label: "Updates", value: "updates" },
|
|
22
21
|
{ label: "Community", value: "community" },
|
|
23
22
|
];
|
|
23
|
+
const DEFAULT_DRAFT = {
|
|
24
|
+
title: "",
|
|
25
|
+
slug: "",
|
|
26
|
+
excerpt: "",
|
|
27
|
+
content: "",
|
|
28
|
+
coverImage: "",
|
|
29
|
+
youtubeId: "",
|
|
30
|
+
category: "news",
|
|
31
|
+
tags: [],
|
|
32
|
+
status: "published",
|
|
33
|
+
isFeatured: true,
|
|
34
|
+
authorName: "Admin",
|
|
35
|
+
publishedAt: "",
|
|
36
|
+
metaTitle: "",
|
|
37
|
+
metaDescription: "",
|
|
38
|
+
};
|
|
24
39
|
function toSlug(str) {
|
|
25
40
|
const base = str
|
|
26
41
|
.toLowerCase()
|
|
@@ -42,23 +57,15 @@ function toDateInputValue(val) {
|
|
|
42
57
|
// --- Component ---------------------------------------------------------------
|
|
43
58
|
export function AdminBlogEditorView({ postId, onSaved, onDeleted, embedded, ...rest }) {
|
|
44
59
|
const isEdit = Boolean(postId);
|
|
45
|
-
const [
|
|
46
|
-
const [slug, setSlug] = React.useState("");
|
|
60
|
+
const [draft, setDraft] = React.useState(DEFAULT_DRAFT);
|
|
47
61
|
const [slugManual, setSlugManual] = React.useState(false);
|
|
48
|
-
const [
|
|
49
|
-
const [
|
|
50
|
-
const [coverImage, setCoverImage] = React.useState("");
|
|
51
|
-
const [category, setCategory] = React.useState("news");
|
|
52
|
-
const [tags, setTags] = React.useState([]);
|
|
53
|
-
const [status, setStatus] = React.useState("draft");
|
|
54
|
-
const [isFeatured, setIsFeatured] = React.useState(false);
|
|
55
|
-
const [authorName, setAuthorName] = React.useState("Admin");
|
|
56
|
-
const [publishedAt, setPublishedAt] = React.useState("");
|
|
57
|
-
const [metaTitle, setMetaTitle] = React.useState("");
|
|
58
|
-
const [metaDescription, setMetaDescription] = React.useState("");
|
|
62
|
+
const [currentStep, setCurrentStep] = React.useState(0);
|
|
63
|
+
const [showDeleteConfirm, setShowDeleteConfirm] = React.useState(false);
|
|
59
64
|
const { showToast } = useToast();
|
|
60
65
|
const { upload } = useMediaUpload();
|
|
61
|
-
const
|
|
66
|
+
const update = React.useCallback((partial) => {
|
|
67
|
+
setDraft((prev) => ({ ...prev, ...partial }));
|
|
68
|
+
}, []);
|
|
62
69
|
// --- load existing post (edit mode) ---
|
|
63
70
|
const postQuery = useQuery({
|
|
64
71
|
queryKey: ["admin", "blog", postId],
|
|
@@ -72,49 +79,46 @@ export function AdminBlogEditorView({ postId, onSaved, onDeleted, embedded, ...r
|
|
|
72
79
|
const p = postQuery.data;
|
|
73
80
|
if (!p)
|
|
74
81
|
return;
|
|
75
|
-
|
|
76
|
-
|
|
82
|
+
setDraft({
|
|
83
|
+
title: p.title ?? "",
|
|
84
|
+
slug: p.slug ?? "",
|
|
85
|
+
excerpt: p.excerpt ?? "",
|
|
86
|
+
content: p.content ?? "",
|
|
87
|
+
coverImage: typeof p.coverImage === "string" ? p.coverImage : p.coverImage?.url ?? "",
|
|
88
|
+
youtubeId: p.youtubeId ?? "",
|
|
89
|
+
category: p.category ?? "news",
|
|
90
|
+
tags: Array.isArray(p.tags) ? p.tags : [],
|
|
91
|
+
status: p.status ?? "published",
|
|
92
|
+
isFeatured: p.isFeatured ?? true,
|
|
93
|
+
authorName: p.authorName ?? "Admin",
|
|
94
|
+
publishedAt: toDateInputValue(p.publishedAt),
|
|
95
|
+
metaTitle: p.metaTitle ?? "",
|
|
96
|
+
metaDescription: p.metaDescription ?? "",
|
|
97
|
+
});
|
|
77
98
|
setSlugManual(true);
|
|
78
|
-
setExcerpt(p.excerpt ?? "");
|
|
79
|
-
setContent(p.content ?? "");
|
|
80
|
-
setCoverImage(typeof p.coverImage === "string"
|
|
81
|
-
? p.coverImage
|
|
82
|
-
: p.coverImage?.url ?? "");
|
|
83
|
-
setCategory(p.category ?? "news");
|
|
84
|
-
setTags(Array.isArray(p.tags) ? p.tags : []);
|
|
85
|
-
setStatus(p.status ?? "draft");
|
|
86
|
-
setIsFeatured(p.isFeatured ?? false);
|
|
87
|
-
setAuthorName(p.authorName ?? "Admin");
|
|
88
|
-
setPublishedAt(toDateInputValue(p.publishedAt));
|
|
89
|
-
setMetaTitle(p.metaTitle ?? "");
|
|
90
|
-
setMetaDescription(p.metaDescription ?? "");
|
|
91
99
|
}, [postQuery.data]);
|
|
92
|
-
const handleTitleChange = (value) => {
|
|
93
|
-
setTitle(value);
|
|
94
|
-
if (!slugManual)
|
|
95
|
-
setSlug(toSlug(value));
|
|
96
|
-
};
|
|
97
100
|
// --- save ---
|
|
98
101
|
const saveMutation = useMutation({
|
|
99
102
|
mutationFn: async () => {
|
|
100
103
|
const payload = {
|
|
101
|
-
title,
|
|
102
|
-
slug: slug || toSlug(title),
|
|
103
|
-
excerpt,
|
|
104
|
-
content,
|
|
105
|
-
coverImage: coverImage || undefined,
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
104
|
+
title: draft.title,
|
|
105
|
+
slug: draft.slug || toSlug(draft.title),
|
|
106
|
+
excerpt: draft.excerpt,
|
|
107
|
+
content: draft.content,
|
|
108
|
+
coverImage: draft.coverImage || undefined,
|
|
109
|
+
youtubeId: draft.youtubeId || undefined,
|
|
110
|
+
category: draft.category,
|
|
111
|
+
tags: draft.tags,
|
|
112
|
+
status: draft.status,
|
|
113
|
+
isFeatured: draft.isFeatured,
|
|
114
|
+
authorName: draft.authorName,
|
|
115
|
+
publishedAt: draft.status === "published" && !draft.publishedAt
|
|
112
116
|
? new Date().toISOString()
|
|
113
|
-
: publishedAt
|
|
114
|
-
? new Date(publishedAt).toISOString()
|
|
117
|
+
: draft.publishedAt
|
|
118
|
+
? new Date(draft.publishedAt).toISOString()
|
|
115
119
|
: undefined,
|
|
116
|
-
metaTitle: metaTitle || undefined,
|
|
117
|
-
metaDescription: metaDescription || undefined,
|
|
120
|
+
metaTitle: draft.metaTitle || undefined,
|
|
121
|
+
metaDescription: draft.metaDescription || undefined,
|
|
118
122
|
};
|
|
119
123
|
if (isEdit) {
|
|
120
124
|
return apiClient.patch(ADMIN_ENDPOINTS.BLOG_BY_ID(postId), payload);
|
|
@@ -122,7 +126,7 @@ export function AdminBlogEditorView({ postId, onSaved, onDeleted, embedded, ...r
|
|
|
122
126
|
return apiClient.post(ADMIN_ENDPOINTS.BLOG, {
|
|
123
127
|
...payload,
|
|
124
128
|
authorId: "admin",
|
|
125
|
-
readTimeMinutes: Math.max(1, Math.round(content.replace(/<[^>]*>/g, "").split(/\s+/).length / 200)),
|
|
129
|
+
readTimeMinutes: Math.max(1, Math.round(draft.content.replace(/<[^>]*>/g, "").split(/\s+/).filter(Boolean).length / 200)),
|
|
126
130
|
views: 0,
|
|
127
131
|
});
|
|
128
132
|
},
|
|
@@ -146,30 +150,49 @@ export function AdminBlogEditorView({ postId, onSaved, onDeleted, embedded, ...r
|
|
|
146
150
|
},
|
|
147
151
|
onError: (err) => showToast(err?.message ?? "Failed to delete post.", "error"),
|
|
148
152
|
});
|
|
149
|
-
const
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
153
|
+
const isLoading = saveMutation.isPending || postQuery.isLoading;
|
|
154
|
+
const steps = [
|
|
155
|
+
{
|
|
156
|
+
label: "Content",
|
|
157
|
+
validate: (values) => !values.title.trim() ? "Title is required" : null,
|
|
158
|
+
render: ({ values, onChange }) => (_jsxs("div", { className: "space-y-5", children: [_jsx(Heading, { level: 3, className: "mb-2", children: "Content" }), _jsx(Input, { label: "Title", value: values.title, onChange: (e) => {
|
|
159
|
+
const v = e.target.value;
|
|
160
|
+
onChange({ title: v, ...(!slugManual && { slug: toSlug(v) }) });
|
|
161
|
+
}, placeholder: "e.g. How to Grade Pok\u00E9mon Cards" }), _jsx(Input, { label: "Slug", value: values.slug, onChange: (e) => {
|
|
162
|
+
setSlugManual(true);
|
|
163
|
+
onChange({ slug: e.target.value });
|
|
164
|
+
}, placeholder: "blog-how-to-grade-pokemon-cards", helperText: "Auto-generated from title. Must start with 'blog-'." }), _jsx(Input, { label: "Excerpt", value: values.excerpt, onChange: (e) => onChange({ excerpt: e.target.value }), placeholder: "Short summary shown in listings and cards" }), _jsxs("div", { className: "space-y-1", children: [_jsx(Text, { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300", children: "Content" }), _jsx(RichTextEditor, { value: values.content, onChange: (v) => onChange({ content: v }), placeholder: "Write your article here...", minHeightClassName: "min-h-[320px]" })] })] })),
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
label: "Media",
|
|
168
|
+
render: ({ values, onChange }) => (_jsxs("div", { className: "space-y-5", children: [_jsx(Heading, { level: 3, className: "mb-2", children: "Media" }), _jsx(ImageUpload, { label: "Cover Image", currentImage: values.coverImage, onUpload: (file) => upload(file, "blog", true, {
|
|
169
|
+
type: "blog-cover",
|
|
170
|
+
title: values.title || values.slug,
|
|
171
|
+
category: values.category,
|
|
172
|
+
}), onChange: (url) => onChange({ coverImage: url }) }), _jsx(Input, { label: "YouTube Video ID (optional)", value: values.youtubeId, onChange: (e) => onChange({ youtubeId: e.target.value }), placeholder: "e.g. dQw4w9WgXcQ", helperText: "The 11-character video ID from the YouTube URL." })] })),
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
label: "SEO & Tags",
|
|
176
|
+
render: ({ values, onChange }) => {
|
|
177
|
+
const readTime = Math.max(1, Math.round(values.content.replace(/<[^>]*>/g, "").split(/\s+/).filter(Boolean).length / 200));
|
|
178
|
+
return (_jsxs("div", { className: "space-y-5", children: [_jsx(Heading, { level: 3, className: "mb-2", children: "SEO & Tags" }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(Select, { label: "Category", options: CATEGORY_OPTIONS, value: values.category, onValueChange: (v) => onChange({ category: v }) }), _jsxs(Div, { children: [_jsx(Text, { className: "text-sm font-medium mb-1.5", children: "Estimated Read Time" }), _jsxs(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: ["~", readTime, " min (auto-calculated from content)"] })] })] }), _jsx(TagInput, { label: "Tags", value: values.tags, onChange: (t) => onChange({ tags: t }), placeholder: "e.g. pokemon, grading, tcg" }), _jsx(Input, { label: "Meta Title (optional)", value: values.metaTitle, onChange: (e) => onChange({ metaTitle: e.target.value }), placeholder: "Defaults to post title" }), _jsx(Input, { label: "Meta Description (optional)", value: values.metaDescription, onChange: (e) => onChange({ metaDescription: e.target.value }), placeholder: "SEO description \u2014 max 160 chars", maxLength: 160 })] }));
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
label: "Publish",
|
|
183
|
+
render: ({ values, onChange }) => (_jsxs("div", { className: "space-y-5", children: [_jsx(Heading, { level: 3, className: "mb-2", children: "Publish Settings" }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(Select, { label: "Status", options: STATUS_OPTIONS, value: values.status, onValueChange: (v) => onChange({ status: v }) }), _jsx(Input, { label: "Publish date (optional)", value: values.publishedAt, onChange: (e) => onChange({ publishedAt: e.target.value }), type: "date", helperText: "Auto-set to now when publishing." })] }), _jsx(Input, { label: "Author Name", value: values.authorName, onChange: (e) => onChange({ authorName: e.target.value }), placeholder: "Author display name" }), _jsx(Toggle, { label: "Featured post", checked: values.isFeatured, onChange: (checked) => onChange({ isFeatured: checked }) }), isEdit && (_jsx("div", { className: "pt-4 border-t border-[var(--appkit-color-border)]", children: _jsx(Button, { type: "button", variant: "danger", size: "sm", onClick: () => setShowDeleteConfirm(true), isLoading: deleteMutation.isPending, children: "Delete post" }) }))] })),
|
|
184
|
+
},
|
|
185
|
+
];
|
|
186
|
+
const deleteModal = (_jsx(ConfirmDeleteModal, { isOpen: showDeleteConfirm, onConfirm: () => {
|
|
187
|
+
setShowDeleteConfirm(false);
|
|
188
|
+
deleteMutation.mutate();
|
|
189
|
+
}, onClose: () => setShowDeleteConfirm(false), title: "Delete post", message: "This will permanently delete the blog post. This cannot be undone." }));
|
|
190
|
+
const formContent = (_jsxs(_Fragment, { children: [_jsx(StepForm, { steps: steps, values: draft, onChange: update, onComplete: () => { saveMutation.mutate(); }, formId: "admin-blog", currentStep: currentStep, onStepChange: setCurrentStep, completeLabel: isEdit ? "Save Changes" : "Create Post", isLoading: isLoading }), deleteModal] }));
|
|
167
191
|
if (embedded) {
|
|
168
|
-
return _jsx("div", { className: "overflow-y-auto p-4", children:
|
|
192
|
+
return _jsx("div", { className: "overflow-y-auto p-4", children: formContent });
|
|
169
193
|
}
|
|
170
|
-
|
|
171
|
-
const previewSection = (_jsxs("div", { className: "rounded-lg border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface-raised)] p-5 max-h-[calc(100vh-12rem)] overflow-y-auto", children: [_jsx("div", { className: "flex items-center gap-2 mb-3 text-xs font-semibold uppercase tracking-wide text-[var(--appkit-color-text-muted)]", children: _jsx("span", { children: "Live preview" }) }), coverImage ? (_jsx("img", { src: coverImage, alt: "", className: "w-full rounded-md mb-4 object-cover max-h-64" })) : null, _jsx(Heading, { level: 1, className: "text-2xl font-bold mb-1", children: title || "Untitled post" }), _jsxs(Text, { className: "text-xs text-[var(--appkit-color-text-muted)] mb-4", children: [authorName || "Anonymous", category ? ` · ${category}` : ""] }), excerpt ? (_jsx(Text, { className: "italic text-[var(--appkit-color-text-secondary)] mb-4", children: excerpt })) : null, _jsx(RichTextRenderer, { html: content || "<em>No content yet…</em>" }), tags.length > 0 ? (_jsx("div", { className: "mt-6 flex flex-wrap gap-1.5", children: tags.map((t) => (_jsxs("span", { className: "px-2 py-0.5 rounded-full bg-[var(--appkit-color-surface)] border border-[var(--appkit-color-border)] text-xs", children: ["#", t] }, t))) })) : null] }, "preview"));
|
|
194
|
+
const previewSection = (_jsxs("div", { className: "rounded-lg border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface-raised)] p-5 max-h-[calc(100vh-12rem)] overflow-y-auto", children: [_jsx("div", { className: "flex items-center gap-2 mb-3 text-xs font-semibold uppercase tracking-wide text-[var(--appkit-color-text-muted)]", children: _jsx("span", { children: "Live preview" }) }), draft.coverImage ? (_jsx("img", { src: draft.coverImage, alt: "", className: "w-full rounded-md mb-4 object-cover max-h-64" })) : null, _jsx(Heading, { level: 1, className: "text-2xl font-bold mb-1", children: draft.title || "Untitled post" }), _jsxs(Text, { className: "text-xs text-[var(--appkit-color-text-muted)] mb-4", children: [draft.authorName || "Anonymous", draft.category ? ` · ${draft.category}` : ""] }), draft.excerpt ? (_jsx(Text, { className: "italic text-[var(--appkit-color-text-secondary)] mb-4", children: draft.excerpt })) : null, _jsx(RichTextRenderer, { html: draft.content || "<em>No content yet…</em>" }), draft.tags.length > 0 ? (_jsx("div", { className: "mt-6 flex flex-wrap gap-1.5", children: draft.tags.map((t) => (_jsxs("span", { className: "px-2 py-0.5 rounded-full bg-[var(--appkit-color-surface)] border border-[var(--appkit-color-border)] text-xs", children: ["#", t] }, t))) })) : null] }, "preview"));
|
|
172
195
|
return (_jsx(StackedViewShell, { portal: "admin", ...rest, title: isEdit ? "Edit Post" : "New Blog Post", sections: [
|
|
173
|
-
_jsxs("div", { className: "lg:grid lg:grid-cols-[3fr_2fr] lg:gap-6", children: [_jsx("div", { children:
|
|
196
|
+
_jsxs("div", { className: "lg:grid lg:grid-cols-[3fr_2fr] lg:gap-6", children: [_jsx("div", { children: formContent }), _jsx("div", { className: "mt-6 lg:mt-0 lg:sticky lg:top-4 lg:self-start", children: previewSection })] }, "split"),
|
|
174
197
|
] }));
|
|
175
198
|
}
|
|
@@ -14,6 +14,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
14
14
|
*/
|
|
15
15
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
16
16
|
import { Button, Checkbox, Container, Heading, Input, Row, Section, Select, Stack, Text, Textarea, } from "../../../ui";
|
|
17
|
+
import { FieldInput, FormShellContext, useFormShellState } from "../../../ui/forms";
|
|
17
18
|
import { BundleItemsPicker, defaultBundleItemsFetch, } from "../../categories/components/BundleItemsPicker";
|
|
18
19
|
import { BundleDynamicRuleEditor } from "../../categories/components/BundleDynamicRuleEditor";
|
|
19
20
|
import { BUNDLE_COPY } from "../../../_internal/shared/features/categories/bundle-copy";
|
|
@@ -76,8 +77,9 @@ export function AdminBundleEditorView({ bundleId, onSaved, onDeleted, }) {
|
|
|
76
77
|
const [loading, setLoading] = useState(isEdit);
|
|
77
78
|
const [saving, setSaving] = useState(false);
|
|
78
79
|
const [deleting, setDeleting] = useState(false);
|
|
79
|
-
const [
|
|
80
|
+
const [apiError, setApiError] = useState(null);
|
|
80
81
|
const [metadata, setMetadata] = useState({});
|
|
82
|
+
const { shellCtx, setFieldError, clearErrors } = useFormShellState();
|
|
81
83
|
// Load existing bundle on mount when editing
|
|
82
84
|
useEffect(() => {
|
|
83
85
|
if (!bundleId)
|
|
@@ -97,7 +99,7 @@ export function AdminBundleEditorView({ bundleId, onSaved, onDeleted, }) {
|
|
|
97
99
|
.catch((err) => {
|
|
98
100
|
if (cancelled)
|
|
99
101
|
return;
|
|
100
|
-
|
|
102
|
+
setApiError(err instanceof Error
|
|
101
103
|
? err.message
|
|
102
104
|
: BUNDLE_COPY.adminEditor.errors.loadFailed);
|
|
103
105
|
})
|
|
@@ -110,16 +112,20 @@ export function AdminBundleEditorView({ bundleId, onSaved, onDeleted, }) {
|
|
|
110
112
|
};
|
|
111
113
|
}, [bundleId]);
|
|
112
114
|
const handleSave = useCallback(async () => {
|
|
113
|
-
|
|
115
|
+
clearErrors();
|
|
116
|
+
setApiError(null);
|
|
114
117
|
const priceInPaise = parsePriceRupees(form.priceRupees);
|
|
118
|
+
let hasError = false;
|
|
115
119
|
if (!form.name.trim()) {
|
|
116
|
-
|
|
117
|
-
|
|
120
|
+
setFieldError("name", BUNDLE_COPY.adminEditor.errors.nameRequired);
|
|
121
|
+
hasError = true;
|
|
118
122
|
}
|
|
119
123
|
if (priceInPaise === null) {
|
|
120
|
-
|
|
121
|
-
|
|
124
|
+
setFieldError("price", BUNDLE_COPY.adminEditor.errors.priceInvalid);
|
|
125
|
+
hasError = true;
|
|
122
126
|
}
|
|
127
|
+
if (hasError)
|
|
128
|
+
return;
|
|
123
129
|
setSaving(true);
|
|
124
130
|
try {
|
|
125
131
|
// SB-UNI-5 2026-05-13 — static vs dynamic rule branching.
|
|
@@ -171,14 +177,14 @@ export function AdminBundleEditorView({ bundleId, onSaved, onDeleted, }) {
|
|
|
171
177
|
}
|
|
172
178
|
}
|
|
173
179
|
catch (err) {
|
|
174
|
-
|
|
180
|
+
setApiError(err instanceof Error
|
|
175
181
|
? err.message
|
|
176
182
|
: BUNDLE_COPY.adminEditor.errors.saveFailed);
|
|
177
183
|
}
|
|
178
184
|
finally {
|
|
179
185
|
setSaving(false);
|
|
180
186
|
}
|
|
181
|
-
}, [form, bundleId, isEdit, onSaved]);
|
|
187
|
+
}, [form, bundleId, isEdit, onSaved, clearErrors, setFieldError]);
|
|
182
188
|
const handleDelete = useCallback(async () => {
|
|
183
189
|
if (!bundleId)
|
|
184
190
|
return;
|
|
@@ -186,7 +192,7 @@ export function AdminBundleEditorView({ bundleId, onSaved, onDeleted, }) {
|
|
|
186
192
|
return;
|
|
187
193
|
}
|
|
188
194
|
setDeleting(true);
|
|
189
|
-
|
|
195
|
+
setApiError(null);
|
|
190
196
|
try {
|
|
191
197
|
const res = await fetch(`/api/admin/bundles/${encodeURIComponent(bundleId)}`, { method: "DELETE" });
|
|
192
198
|
if (!res.ok)
|
|
@@ -194,7 +200,7 @@ export function AdminBundleEditorView({ bundleId, onSaved, onDeleted, }) {
|
|
|
194
200
|
onDeleted?.();
|
|
195
201
|
}
|
|
196
202
|
catch (err) {
|
|
197
|
-
|
|
203
|
+
setApiError(err instanceof Error
|
|
198
204
|
? err.message
|
|
199
205
|
: BUNDLE_COPY.adminEditor.errors.deleteFailed);
|
|
200
206
|
}
|
|
@@ -206,7 +212,7 @@ export function AdminBundleEditorView({ bundleId, onSaved, onDeleted, }) {
|
|
|
206
212
|
if (loading) {
|
|
207
213
|
return (_jsx(Section, { className: "py-10", children: _jsx(Container, { size: "lg", children: _jsx(Text, { children: BUNDLE_COPY.adminEditor.loading }) }) }));
|
|
208
214
|
}
|
|
209
|
-
return (_jsx(Section, { className: "py-10", children: _jsx(Container, { size: "lg", children: _jsxs(Stack, { gap: "lg", children: [_jsxs(Row, { gap: "sm", align: "center", justify: "between", className: "flex-wrap", children: [_jsx(Heading, { level: 1, className: "text-2xl font-semibold text-zinc-900 dark:text-zinc-100", children: isEdit
|
|
210
|
-
|
|
211
|
-
|
|
215
|
+
return (_jsx(FormShellContext.Provider, { value: shellCtx, children: _jsx(Section, { className: "py-10", children: _jsx(Container, { size: "lg", children: _jsxs(Stack, { gap: "lg", children: [_jsxs(Row, { gap: "sm", align: "center", justify: "between", className: "flex-wrap", children: [_jsx(Heading, { level: 1, className: "text-2xl font-semibold text-zinc-900 dark:text-zinc-100", children: isEdit
|
|
216
|
+
? BUNDLE_COPY.adminEditorTitleEdit
|
|
217
|
+
: BUNDLE_COPY.adminEditorTitleNew }), isEdit && (_jsx(Button, { variant: "danger", onClick: handleDelete, disabled: deleting, children: BUNDLE_COPY.adminEditor.deleteButton(deleting) }))] }), apiError && (_jsx(Text, { color: "danger", role: "alert", children: apiError })), _jsxs(Stack, { gap: "md", children: [_jsx(FieldInput, { name: "name", label: BUNDLE_COPY.adminEditor.fields.nameLabel, value: form.name, onChange: (v) => setForm((f) => ({ ...f, name: v })), placeholder: BUNDLE_COPY.adminEditor.fields.namePlaceholder, disabled: saving, required: true }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: BUNDLE_COPY.adminEditor.fields.descriptionLabel }), _jsx(Textarea, { value: form.description, onChange: (e) => setForm((f) => ({ ...f, description: e.target.value })), placeholder: BUNDLE_COPY.adminEditor.fields.descriptionPlaceholder, rows: 4, disabled: saving })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(FieldInput, { name: "price", label: BUNDLE_COPY.adminEditor.fields.priceLabel, type: "number", inputMode: "decimal", min: 1, step: 1, value: form.priceRupees, onChange: (v) => setForm((f) => ({ ...f, priceRupees: v })), placeholder: BUNDLE_COPY.adminEditor.fields.pricePlaceholder, disabled: saving, required: true }), _jsx(Text, { size: "xs", color: "muted", children: BUNDLE_COPY.adminEditor.fields.pricePaiseHint(parsePriceRupees(form.priceRupees)) })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: BUNDLE_COPY.adminEditor.fields.coverImageLabel }), _jsx(Input, { type: "url", value: form.coverImage, onChange: (e) => setForm((f) => ({ ...f, coverImage: e.target.value })), placeholder: "https://\u2026", disabled: saving })] }), _jsx(Checkbox, { checked: form.isActive, onChange: (e) => setForm((f) => ({ ...f, isActive: e.target.checked })), disabled: saving, label: BUNDLE_COPY.adminEditor.fields.activeLabel }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: BUNDLE_COPY.adminEditor.ruleTypeLabel }), _jsx(Select, { options: RULE_TYPE_OPTIONS, value: form.ruleType, onValueChange: (next) => setForm((f) => ({ ...f, ruleType: next })), disabled: saving, "aria-label": BUNDLE_COPY.adminEditor.ruleTypeLabel })] }), form.ruleType === "static" ? (_jsx(BundleItemsPicker, { value: form.productIds, onChange: (next) => setForm((f) => ({ ...f, productIds: next })), fetchProducts: fetchProducts, initialMetadata: metadata })) : (_jsx(BundleDynamicRuleEditor, { value: form.dynamicRule, onChange: (next) => setForm((f) => ({ ...f, dynamicRule: next })), disabled: saving }))] }), _jsx(Row, { gap: "sm", align: "center", justify: "end", children: _jsx(Button, { variant: "primary", onClick: handleSave, disabled: saving, "aria-busy": saving, children: BUNDLE_COPY.adminEditor.saveButton(saving, isEdit) }) })] }) }) }) }));
|
|
212
218
|
}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
export interface AdminBundlesViewProps {
|
|
2
|
-
/** Builds the href for the row-click edit action. */
|
|
3
2
|
getEditHref: (row: {
|
|
4
3
|
id: string;
|
|
5
4
|
}) => string;
|
|
6
|
-
/** Builds the href for the "New bundle" CTA. */
|
|
7
5
|
newHref: string;
|
|
8
6
|
}
|
|
9
|
-
export declare function AdminBundlesView({ getEditHref, newHref
|
|
7
|
+
export declare function AdminBundlesView({ getEditHref, newHref }: AdminBundlesViewProps): import("react/jsx-runtime").JSX.Element;
|