@mohasinac/appkit 4.29.0 → 4.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_internal/client/features/layout/SidebarCollapseToggle.js +1 -1
- package/dist/_internal/server/jobs/handlers/_helpers.d.ts +22 -0
- package/dist/_internal/server/jobs/handlers/_helpers.js +25 -1
- package/dist/_internal/shared/features/blog/schema.d.ts +2 -2
- package/dist/_internal/shared/features/blog/schema.js +2 -1
- package/dist/_internal/shared/features/brands/schema.d.ts +2 -2
- package/dist/_internal/shared/features/brands/schema.js +3 -1
- package/dist/_internal/shared/features/categories/bundle-schemas.d.ts +3 -3
- package/dist/_internal/shared/features/categories/bundle-schemas.js +2 -1
- package/dist/_internal/shared/features/events/schema.d.ts +2 -2
- package/dist/_internal/shared/features/events/schema.js +2 -1
- package/dist/_internal/shared/features/products/schema.d.ts +8 -8
- package/dist/_internal/shared/features/products/schema.js +3 -2
- package/dist/_internal/shared/features/reviews/schema.d.ts +1 -1
- package/dist/_internal/shared/features/reviews/schema.js +2 -1
- package/dist/_internal/shared/types/action-result.d.ts +0 -2
- package/dist/client/api/surface-error.d.ts +9 -0
- package/dist/client/api/surface-error.js +29 -3
- package/dist/client/api/useApiMutation.d.ts +10 -0
- package/dist/client/api/useApiMutation.js +2 -1
- package/dist/core/server-action.d.ts +3 -14
- package/dist/core/server-action.js +11 -2
- package/dist/errors/error-envelope.d.ts +73 -0
- package/dist/errors/error-envelope.js +96 -0
- package/dist/errors/error-handler.js +14 -7
- package/dist/features/account/components/NotificationBell.js +1 -1
- package/dist/features/account/components/UserSidebar.js +2 -2
- package/dist/features/account/components/UserSupportView.js +2 -6
- package/dist/features/account/hooks/useProfile.d.ts +6 -0
- package/dist/features/account/hooks/useProfile.js +1 -0
- package/dist/features/admin/components/AdminAdEditorView.js +1 -3
- package/dist/features/admin/components/AdminAddressClustersView.js +1 -3
- package/dist/features/admin/components/AdminAddressEditorView.js +2 -4
- package/dist/features/admin/components/AdminAddressesView.js +1 -3
- package/dist/features/admin/components/AdminAllEventEntriesView.js +1 -3
- package/dist/features/admin/components/AdminBidsView.js +1 -3
- package/dist/features/admin/components/AdminBlogEditorView.js +2 -2
- package/dist/features/admin/components/AdminBrandEditorView.js +4 -5
- package/dist/features/admin/components/AdminCarouselGroupEditorView.js +1 -3
- package/dist/features/admin/components/AdminCategoryEditorView.js +2 -4
- package/dist/features/admin/components/AdminContactEditorView.js +1 -3
- package/dist/features/admin/components/AdminContactView.js +2 -6
- package/dist/features/admin/components/AdminCouponEditorView.js +2 -4
- package/dist/features/admin/components/AdminEmployeeEditorView.js +3 -9
- package/dist/features/admin/components/AdminFaqEditorView.js +2 -4
- package/dist/features/admin/components/AdminFeatureEditorView.js +0 -9
- package/dist/features/admin/components/AdminFeatureFlagsView.js +2 -4
- package/dist/features/admin/components/AdminNavEditorView.js +1 -3
- package/dist/features/admin/components/AdminNavigationView.js +3 -3
- package/dist/features/admin/components/AdminNewsletterView.js +1 -3
- package/dist/features/admin/components/AdminNotificationsView.js +2 -6
- package/dist/features/admin/components/AdminOffersView.js +1 -3
- package/dist/features/admin/components/AdminOrderEditorView.js +1 -3
- package/dist/features/admin/components/AdminPaymentClustersView.js +1 -3
- package/dist/features/admin/components/AdminPaymentMethodsView.js +1 -3
- package/dist/features/admin/components/AdminPayoutMarkPaidModal.js +2 -4
- package/dist/features/admin/components/AdminProductEditorView.js +2 -4
- package/dist/features/admin/components/AdminReturnRequestsView.js +2 -6
- package/dist/features/admin/components/AdminReviewsView.js +2 -6
- package/dist/features/admin/components/AdminScammerEditorView.js +2 -6
- package/dist/features/admin/components/AdminSectionsView.js +2 -6
- package/dist/features/admin/components/AdminSessionsView.js +1 -3
- package/dist/features/admin/components/AdminSidebar.js +2 -2
- package/dist/features/admin/components/AdminSiteSettingsView.js +1 -1
- package/dist/features/admin/components/AdminStoreEditorView.js +1 -3
- package/dist/features/admin/components/AdminStoresView.js +2 -6
- package/dist/features/admin/components/AdminSublistingCategoryEditorView.js +2 -6
- package/dist/features/admin/components/AdminSupportTicketDetailView.js +7 -21
- package/dist/features/admin/components/AdminTesterChecklistItemEditorView.js +2 -4
- package/dist/features/admin/components/AdminUserEditorView.js +6 -18
- package/dist/features/admin/components/AdminUsersView.js +2 -6
- package/dist/features/admin/components/BrandQuickCreateForm.js +2 -4
- package/dist/features/admin/components/CategoryQuickCreateForm.js +2 -4
- package/dist/features/auctions/components/PlaceBidFormClient.js +1 -1
- package/dist/features/auth/components/ForgotPasswordView.js +1 -1
- package/dist/features/auth/components/LoginForm.js +1 -1
- package/dist/features/auth/components/RegisterForm.js +1 -1
- package/dist/features/auth/components/ResetPasswordView.js +1 -1
- package/dist/features/auth/components/SocialAuthButtons.js +1 -1
- package/dist/features/auth/repository/session.repository.js +13 -1
- package/dist/features/blog/actions/blog-actions.d.ts +14 -14
- package/dist/features/blog/actions/blog-actions.js +6 -8
- package/dist/features/blog/schemas/index.d.ts +1 -1
- package/dist/features/blog/schemas/index.js +2 -4
- package/dist/features/categories/api/[id]/route.js +2 -1
- package/dist/features/categories/api/route.js +2 -1
- package/dist/features/events/schemas/index.d.ts +1 -1
- package/dist/features/events/schemas/index.js +2 -4
- package/dist/features/faq/components/ContactCTA.js +1 -1
- package/dist/features/filters/AsyncFacetSection.js +1 -1
- package/dist/features/filters/FilterFacetSection.js +1 -1
- package/dist/features/filters/RangeFilter.js +1 -1
- package/dist/features/filters/SwitchFilter.js +1 -1
- package/dist/features/homepage/api/carousel/[id]/route.js +4 -3
- package/dist/features/homepage/api/carousel/route.js +4 -3
- package/dist/features/homepage/api/route.js +3 -2
- package/dist/features/homepage/components/GoogleReviewsSection.js +1 -1
- package/dist/features/homepage/components/HeroBanner.js +1 -1
- package/dist/features/homepage/components/HeroCarousel.js +1 -1
- package/dist/features/homepage/components/SocialPostCard.js +1 -1
- package/dist/features/homepage/components/WhatsAppCommunitySection.js +1 -1
- package/dist/features/layout/AppLayoutShell.js +4 -4
- package/dist/features/layout/BottomNavbar.js +2 -2
- package/dist/features/layout/MainNavbar.js +1 -1
- package/dist/features/layout/TitleBarLayout.js +24 -12
- package/dist/features/media/AvatarUpload.js +28 -10
- package/dist/features/media/modals/ImageCropModal.js +1 -1
- package/dist/features/media/modals/ImageEditor.js +1 -1
- package/dist/features/products/components/MakeOfferButton.js +1 -1
- package/dist/features/reviews/api/[id]/route.js +3 -2
- package/dist/features/reviews/api/route.js +3 -2
- package/dist/features/search/components/Search.js +2 -2
- package/dist/features/seller/components/SellerAnalyticsAlertsView.js +3 -3
- package/dist/features/seller/components/SellerSidebar.js +2 -2
- package/dist/features/store-extensions/components/ReviewDecisionModal.js +1 -1
- package/dist/features/stores/repository/store.repository.d.ts +21 -0
- package/dist/features/stores/repository/store.repository.js +32 -0
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +0 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +4 -1
- package/dist/next/api/api-response.d.ts +29 -12
- package/dist/next/api/api-response.js +9 -8
- package/dist/next/api/apiHandler.d.ts +6 -1
- package/dist/next/api/apiHandler.js +13 -1
- package/dist/next/api/routeHandler.js +6 -6
- package/dist/next/middleware/pii-scrubber.js +5 -2
- package/dist/providers/db-firebase/base.repository.d.ts +14 -0
- package/dist/providers/db-firebase/base.repository.js +22 -0
- package/dist/providers/db-firebase/sieve.d.ts +14 -1
- package/dist/providers/db-firebase/sieve.js +3 -1
- package/dist/security/pii-encrypt.d.ts +0 -4
- package/dist/security/pii-encrypt.js +79 -13
- package/dist/security/pii-mask.d.ts +39 -1
- package/dist/security/pii-mask.js +33 -4
- package/dist/security/pii-redact.d.ts +0 -13
- package/dist/security/pii-redact.js +3 -1
- package/dist/security/pii-schemas.d.ts +0 -2
- package/dist/security/pii-schemas.js +16 -2
- package/dist/security/settings-encryption.js +31 -3
- package/dist/seed/stores-seed-data.js +18 -0
- package/dist/seed/test-utils.js +6 -2
- package/dist/ui/DataTable.js +2 -2
- package/dist/ui/components/CollapsibleSection.js +1 -1
- package/dist/ui/components/ImageGallery.js +1 -1
- package/dist/ui/components/ListingLayout.js +1 -1
- package/dist/ui/forms/FormShell.d.ts +0 -19
- package/dist/ui/forms/FormShell.js +4 -23
- package/dist/ui/forms/apply-zod-issues.d.ts +34 -0
- package/dist/ui/forms/apply-zod-issues.js +38 -0
- package/dist/ui/forms/index.d.ts +2 -1
- package/dist/ui/forms/index.js +2 -1
- package/dist/ui/icons/icon-registry.d.ts +8 -0
- package/dist/ui/icons/icon-registry.js +13 -1
- package/dist/utils/action-response.d.ts +1 -1
- package/dist/utils/media-url.d.ts +49 -0
- package/dist/utils/media-url.js +72 -1
- package/dist/validation/schemas.d.ts +15 -2
- package/dist/validation/schemas.js +18 -22
- package/package.json +1 -1
- package/scripts/seed-cli.mjs +10 -2
|
@@ -73,6 +73,7 @@ export function AdminSupportTicketDetailView({ open, onClose, ticketId, subject,
|
|
|
73
73
|
return out;
|
|
74
74
|
}, [parties]);
|
|
75
75
|
const updateMutation = useApiMutation({
|
|
76
|
+
errorMessage: "Failed to update ticket.",
|
|
76
77
|
mutationFn: async () => {
|
|
77
78
|
await apiClient.patch(ADMIN_ENDPOINTS.SUPPORT_TICKET_BY_ID(ticketId), {
|
|
78
79
|
status,
|
|
@@ -86,11 +87,9 @@ export function AdminSupportTicketDetailView({ open, onClose, ticketId, subject,
|
|
|
86
87
|
invalidate();
|
|
87
88
|
onClose();
|
|
88
89
|
},
|
|
89
|
-
onError: (err) => {
|
|
90
|
-
showToast(err?.message ?? "Failed to update ticket.", "error");
|
|
91
|
-
},
|
|
92
90
|
});
|
|
93
91
|
const replyMutation = useApiMutation({
|
|
92
|
+
errorMessage: "Failed to send reply.",
|
|
94
93
|
mutationFn: async () => {
|
|
95
94
|
await apiClient.post(SUPPORT_ENDPOINTS.TICKET_MESSAGES(ticketId), { body: replyBody, newStatus: status });
|
|
96
95
|
},
|
|
@@ -100,9 +99,6 @@ export function AdminSupportTicketDetailView({ open, onClose, ticketId, subject,
|
|
|
100
99
|
invalidate();
|
|
101
100
|
onClose();
|
|
102
101
|
},
|
|
103
|
-
onError: (err) => {
|
|
104
|
-
showToast(err?.message ?? "Failed to send reply.", "error");
|
|
105
|
-
},
|
|
106
102
|
});
|
|
107
103
|
// ST-4 — Apply Store Change action (only relevant for store_change_request)
|
|
108
104
|
const [storeStatus, setStoreStatus] = React.useState("active");
|
|
@@ -129,6 +125,7 @@ export function AdminSupportTicketDetailView({ open, onClose, ticketId, subject,
|
|
|
129
125
|
},
|
|
130
126
|
});
|
|
131
127
|
const applyStoreChange = useApiMutation({
|
|
128
|
+
errorMessage: "Failed to apply store change.",
|
|
132
129
|
mutationFn: async () => {
|
|
133
130
|
if (!linkedStoreId)
|
|
134
131
|
throw new Error("No linked store on this ticket.");
|
|
@@ -149,9 +146,6 @@ export function AdminSupportTicketDetailView({ open, onClose, ticketId, subject,
|
|
|
149
146
|
.catch(console.error);
|
|
150
147
|
invalidate();
|
|
151
148
|
},
|
|
152
|
-
onError: (err) => {
|
|
153
|
-
showToast(err?.message ?? "Failed to apply store change.", "error");
|
|
154
|
-
},
|
|
155
149
|
});
|
|
156
150
|
// ST-3 — Modify Order Items panel (fetches current items on demand, lets
|
|
157
151
|
// admin edit quantity per line + remove lines, then PATCHes the order).
|
|
@@ -160,6 +154,7 @@ export function AdminSupportTicketDetailView({ open, onClose, ticketId, subject,
|
|
|
160
154
|
const linkedOrderId = parties.orderId?.trim() || orderId;
|
|
161
155
|
const isOrderModificationRequest = category === "order_modification_request" || category === "order_issue";
|
|
162
156
|
const loadOrderItems = useApiMutation({
|
|
157
|
+
errorMessage: "Failed to load order.",
|
|
163
158
|
mutationFn: async () => {
|
|
164
159
|
if (!linkedOrderId)
|
|
165
160
|
throw new Error("No linked order on this ticket.");
|
|
@@ -174,14 +169,12 @@ export function AdminSupportTicketDetailView({ open, onClose, ticketId, subject,
|
|
|
174
169
|
})));
|
|
175
170
|
setOrderItemsOpen(true);
|
|
176
171
|
},
|
|
177
|
-
onError: (err) => {
|
|
178
|
-
showToast(err?.message ?? "Failed to load order.", "error");
|
|
179
|
-
},
|
|
180
172
|
});
|
|
181
173
|
// ST-5 — Lift Ban actions for unban_request tickets
|
|
182
174
|
const linkedUserId = parties.userId?.trim();
|
|
183
175
|
const isUnbanRequest = category === "unban_request";
|
|
184
176
|
const liftHardBan = useApiMutation({
|
|
177
|
+
errorMessage: "Failed to lift hard ban.",
|
|
185
178
|
mutationFn: async () => {
|
|
186
179
|
if (!linkedUserId)
|
|
187
180
|
throw new Error("No linked user on this ticket.");
|
|
@@ -198,11 +191,9 @@ export function AdminSupportTicketDetailView({ open, onClose, ticketId, subject,
|
|
|
198
191
|
.catch(console.error);
|
|
199
192
|
invalidate();
|
|
200
193
|
},
|
|
201
|
-
onError: (err) => {
|
|
202
|
-
showToast(err?.message ?? "Failed to lift hard ban.", "error");
|
|
203
|
-
},
|
|
204
194
|
});
|
|
205
195
|
const liftSoftBanTickets = useApiMutation({
|
|
196
|
+
errorMessage: "Failed to lift soft ban.",
|
|
206
197
|
mutationFn: async () => {
|
|
207
198
|
if (!linkedUserId)
|
|
208
199
|
throw new Error("No linked user on this ticket.");
|
|
@@ -219,11 +210,9 @@ export function AdminSupportTicketDetailView({ open, onClose, ticketId, subject,
|
|
|
219
210
|
.catch(console.error);
|
|
220
211
|
invalidate();
|
|
221
212
|
},
|
|
222
|
-
onError: (err) => {
|
|
223
|
-
showToast(err?.message ?? "Failed to lift soft ban.", "error");
|
|
224
|
-
},
|
|
225
213
|
});
|
|
226
214
|
const applyOrderItems = useApiMutation({
|
|
215
|
+
errorMessage: "Failed to update order items.",
|
|
227
216
|
mutationFn: async () => {
|
|
228
217
|
if (!linkedOrderId)
|
|
229
218
|
throw new Error("No linked order on this ticket.");
|
|
@@ -256,9 +245,6 @@ export function AdminSupportTicketDetailView({ open, onClose, ticketId, subject,
|
|
|
256
245
|
invalidate();
|
|
257
246
|
setOrderItemsOpen(false);
|
|
258
247
|
},
|
|
259
|
-
onError: (err) => {
|
|
260
|
-
showToast(err?.message ?? "Failed to update order items.", "error");
|
|
261
|
-
},
|
|
262
248
|
});
|
|
263
249
|
return (_jsx(SideDrawer, { isOpen: open, onClose: onClose, title: subject ?? "Support Ticket", children: _jsxs(Stack, { className: `${__P.p4}`, gap: "md", children: [_jsxs(Row, { align: "center", gap: "sm", wrap: true, children: [_jsx(Span, { size: "xs", weight: "medium", className: `inline-flex ${STATUS_COLOR[currentStatus ?? "open"] ?? STATUS_COLOR.open}`, rounded: "full", padding: "pill-sm-tall", children: (currentStatus ?? "open").replace(/_/g, " ") }), _jsx(Span, { size: "xs", rounded: "full", padding: "pill-sm-tall", surface: "subtle", color: "muted", children: category }), orderId && (_jsxs(Span, { size: "xs", rounded: "full", padding: "pill-sm-tall", surface: "subtle", color: "muted", children: ["Order: ", orderId] }))] }), _jsxs(Div, { textSize: "sm", color: "muted", children: ["From: ", _jsx(Span, { weight: "medium", color: "primary", children: userDisplayName })] }), description && (_jsxs(Div, { className: `${__P.p3}`, rounded: "lg", surface: "muted", border: "default", children: [_jsx(Text, { className: "mb-1 tracking-wide", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: "Description" }), _jsx(Text, { className: "whitespace-pre-wrap", color: "primary", size: "sm", children: description })] })), messages.length > 0 && (_jsxs(Stack, { gap: "sm", children: [_jsxs(Text, { className: "tracking-wide", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: ["Messages (", messages.length, ")"] }), _jsx(Stack, { className: `max-h-64 ${__O.yAuto}`, gap: "sm", children: messages.map((msg, i) => (_jsxs(Div, { textSize: "sm", surface: msg.authorRole === "user" ? "default" : "info-surface", className: msg.authorRole === "user" ? "border border-[var(--appkit-color-border)]" : "border border-[var(--appkit-color-info)]", rounded: "lg", padding: "sm", children: [_jsxs(Row, { color: "muted", textSize: "xs", className: "mb-1", align: "center", gap: "sm", children: [_jsx(Span, { weight: "medium", color: "muted", children: ROLE_LABEL[msg.authorRole ?? "user"] ?? msg.authorRole }), msg.createdAt && (_jsx(Span, { children: new Date(msg.createdAt).toLocaleString() }))] }), _jsx(Text, { className: "whitespace-pre-wrap", color: "primary", children: msg.body })] }, msg.id ?? i))) })] })), _jsxs(Stack, { gap: "xs", children: [_jsx(Label, { className: "uppercase tracking-wide", color: "muted", size: "xs", weight: "semibold", children: "Reply to user" }), _jsx(Textarea, { value: replyBody, onChange: (e) => setReplyBody(e.target.value), rows: 3, placeholder: "Type a reply\u2026" }), _jsx(Button, { type: "button", variant: "primary", size: "sm", isLoading: replyMutation.isPending, disabled: !replyBody.trim() || !ticketId || replyMutation.isPending, onClick: () => replyMutation.mutate(), children: "Send reply" })] }), _jsx(HorizontalRule, { tone: "subtle", spacing: "comfortable" }), _jsx(RecordStatusTimeline, { entries: statusHistory, truncatedCount: statusHistoryTruncated }), _jsx(HorizontalRule, { tone: "subtle", spacing: "comfortable" }), _jsx(Select, { label: "Status", options: STATUS_OPTIONS, value: status, onValueChange: setStatus }), _jsx(Select, { label: "Priority", options: PRIORITY_OPTIONS, value: priority, onValueChange: setPriority }), _jsxs(Stack, { gap: "xs", children: [_jsx(Label, { className: "uppercase tracking-wide", color: "muted", size: "xs", weight: "semibold", children: "Internal notes (staff only)" }), _jsx(Textarea, { value: notes, onChange: (e) => setNotes(e.target.value), rows: 3, placeholder: "Notes visible only to admins and employees\u2026" })] }), _jsxs(Stack, { padding: "sm", gap: "sm", rounded: "lg", border: "default", children: [_jsx(Text, { className: "tracking-wide", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: "Linked parties" }), _jsx(Text, { size: "xs", color: "muted", children: "Tag the buyer / store / order / product / bid this ticket concerns." }), [
|
|
264
250
|
["userId", "User slug"],
|
|
@@ -79,6 +79,7 @@ export function AdminTesterChecklistItemEditorView({ itemId, onSaved, onDeleted,
|
|
|
79
79
|
setPageKey(slugifyKey(value));
|
|
80
80
|
};
|
|
81
81
|
const saveMutation = useApiMutation({
|
|
82
|
+
errorMessage: "Failed to save checklist item.",
|
|
82
83
|
mutationFn: async () => {
|
|
83
84
|
const payload = {
|
|
84
85
|
groupKey: groupKey || slugifyKey(groupLabel),
|
|
@@ -104,18 +105,15 @@ export function AdminTesterChecklistItemEditorView({ itemId, onSaved, onDeleted,
|
|
|
104
105
|
if (onSaved && id)
|
|
105
106
|
onSaved(id);
|
|
106
107
|
},
|
|
107
|
-
onError: (err) => {
|
|
108
|
-
showToast(err?.message ?? "Failed to save checklist item.", "error");
|
|
109
|
-
},
|
|
110
108
|
});
|
|
111
109
|
const deleteMutation = useApiMutation({
|
|
110
|
+
errorMessage: "Failed to delete checklist item.",
|
|
112
111
|
mutationFn: () => apiClient.delete(ADMIN_ENDPOINTS.TESTER_CHECKLIST_ITEM_BY_ID(itemId)),
|
|
113
112
|
onSuccess: () => {
|
|
114
113
|
showToast("Checklist item deleted.", "success");
|
|
115
114
|
if (onDeleted)
|
|
116
115
|
onDeleted();
|
|
117
116
|
},
|
|
118
|
-
onError: (err) => showToast(err?.message ?? "Failed to delete checklist item.", "error"),
|
|
119
117
|
});
|
|
120
118
|
const isSubmitting = saveMutation.isPending || itemQuery.isLoading;
|
|
121
119
|
const canSave = Boolean(groupLabel.trim()) && Boolean(pageLabel.trim()) && Boolean(label.trim());
|
|
@@ -113,6 +113,7 @@ export function AdminUserEditorView({ open, onClose, userId, displayName, photoU
|
|
|
113
113
|
const invalidate = () => queryClient.invalidateQueries({ queryKey: ["admin", "users"] });
|
|
114
114
|
// --- Mutations ------------------------------------------------------------
|
|
115
115
|
const saveMutation = useApiMutation({
|
|
116
|
+
errorMessage: "Failed to update user.",
|
|
116
117
|
mutationFn: async () => {
|
|
117
118
|
// ST-2 — build publicProfile partial only when something changed; keeps
|
|
118
119
|
// the PATCH payload minimal and avoids overwriting unrelated subkeys.
|
|
@@ -150,11 +151,9 @@ export function AdminUserEditorView({ open, onClose, userId, displayName, photoU
|
|
|
150
151
|
invalidate();
|
|
151
152
|
onClose();
|
|
152
153
|
},
|
|
153
|
-
onError: (err) => {
|
|
154
|
-
showToast(err?.message ?? "Failed to update user.", "error");
|
|
155
|
-
},
|
|
156
154
|
});
|
|
157
155
|
const deleteMutation = useApiMutation({
|
|
156
|
+
errorMessage: "Failed to delete user.",
|
|
158
157
|
mutationFn: async () => {
|
|
159
158
|
await apiClient.delete(ADMIN_ENDPOINTS.USER_BY_ID(userId));
|
|
160
159
|
},
|
|
@@ -164,11 +163,9 @@ export function AdminUserEditorView({ open, onClose, userId, displayName, photoU
|
|
|
164
163
|
setDeleteOpen(false);
|
|
165
164
|
onClose();
|
|
166
165
|
},
|
|
167
|
-
onError: (err) => {
|
|
168
|
-
showToast(err?.message ?? "Failed to delete user.", "error");
|
|
169
|
-
},
|
|
170
166
|
});
|
|
171
167
|
const hardBanMutation = useApiMutation({
|
|
168
|
+
errorMessage: "Failed to ban user.",
|
|
172
169
|
mutationFn: async (reason) => {
|
|
173
170
|
await apiClient.post(ADMIN_ENDPOINTS.USER_HARD_BAN(userId), { reason });
|
|
174
171
|
},
|
|
@@ -179,11 +176,9 @@ export function AdminUserEditorView({ open, onClose, userId, displayName, photoU
|
|
|
179
176
|
setHardBanReasonInput("");
|
|
180
177
|
onClose();
|
|
181
178
|
},
|
|
182
|
-
onError: (err) => {
|
|
183
|
-
showToast(err?.message ?? "Failed to ban user.", "error");
|
|
184
|
-
},
|
|
185
179
|
});
|
|
186
180
|
const unbanMutation = useApiMutation({
|
|
181
|
+
errorMessage: "Failed to unban user.",
|
|
187
182
|
mutationFn: async () => {
|
|
188
183
|
await apiClient.post(ADMIN_ENDPOINTS.USER_UNBAN(userId), {});
|
|
189
184
|
},
|
|
@@ -192,11 +187,9 @@ export function AdminUserEditorView({ open, onClose, userId, displayName, photoU
|
|
|
192
187
|
invalidate();
|
|
193
188
|
onClose();
|
|
194
189
|
},
|
|
195
|
-
onError: (err) => {
|
|
196
|
-
showToast(err?.message ?? "Failed to unban user.", "error");
|
|
197
|
-
},
|
|
198
190
|
});
|
|
199
191
|
const softBanMutation = useApiMutation({
|
|
192
|
+
errorMessage: "Failed to apply soft ban.",
|
|
200
193
|
mutationFn: async (payload) => {
|
|
201
194
|
await apiClient.post(ADMIN_ENDPOINTS.USER_SOFT_BAN(userId), payload);
|
|
202
195
|
},
|
|
@@ -208,11 +201,9 @@ export function AdminUserEditorView({ open, onClose, userId, displayName, photoU
|
|
|
208
201
|
setSoftBanReason("");
|
|
209
202
|
setSoftBanExpiry("");
|
|
210
203
|
},
|
|
211
|
-
onError: (err) => {
|
|
212
|
-
showToast(err?.message ?? "Failed to apply soft ban.", "error");
|
|
213
|
-
},
|
|
214
204
|
});
|
|
215
205
|
const liftSoftBanMutation = useApiMutation({
|
|
206
|
+
errorMessage: "Failed to lift soft ban.",
|
|
216
207
|
mutationFn: async (action) => {
|
|
217
208
|
await apiClient.delete(ADMIN_ENDPOINTS.USER_SOFT_BAN_LIFT(userId, action));
|
|
218
209
|
},
|
|
@@ -220,9 +211,6 @@ export function AdminUserEditorView({ open, onClose, userId, displayName, photoU
|
|
|
220
211
|
showToast("Soft ban lifted.", "success");
|
|
221
212
|
invalidate();
|
|
222
213
|
},
|
|
223
|
-
onError: (err) => {
|
|
224
|
-
showToast(err?.message ?? "Failed to lift soft ban.", "error");
|
|
225
|
-
},
|
|
226
214
|
});
|
|
227
215
|
const isHardBanned = currentIsHardBanned ?? false;
|
|
228
216
|
const softBans = currentSoftBans ?? [];
|
|
@@ -52,6 +52,7 @@ export function AdminUsersView({ children, ...props }) {
|
|
|
52
52
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
53
53
|
}, [banJobEvent.status]);
|
|
54
54
|
const banUser = useApiMutation({
|
|
55
|
+
errorMessage: "Failed to start hard-ban.",
|
|
55
56
|
mutationFn: () => {
|
|
56
57
|
if (!banTargetId)
|
|
57
58
|
throw new Error("No user selected");
|
|
@@ -60,9 +61,6 @@ export function AdminUsersView({ children, ...props }) {
|
|
|
60
61
|
onSuccess: (result) => {
|
|
61
62
|
banJobEvent.subscribe(result.jobId, result.customToken);
|
|
62
63
|
},
|
|
63
|
-
onError: () => {
|
|
64
|
-
toast.showToast("Failed to start hard-ban.", "error");
|
|
65
|
-
},
|
|
66
64
|
});
|
|
67
65
|
const bulkUsers = useBulkAction({
|
|
68
66
|
mutationFn: (payload) => apiClient.post(ADMIN_ENDPOINTS.USERS_BULK, payload),
|
|
@@ -75,14 +73,12 @@ export function AdminUsersView({ children, ...props }) {
|
|
|
75
73
|
},
|
|
76
74
|
});
|
|
77
75
|
const unbanUser = useApiMutation({
|
|
76
|
+
errorMessage: "Failed to lift ban.",
|
|
78
77
|
mutationFn: (uid) => apiClient.post(ADMIN_ENDPOINTS.USER_UNBAN(uid), {}),
|
|
79
78
|
onSuccess: () => {
|
|
80
79
|
toast.showToast("Ban lifted.", "success");
|
|
81
80
|
void queryClient.invalidateQueries({ queryKey: ["admin", "users", "listing"] });
|
|
82
81
|
},
|
|
83
|
-
onError: () => {
|
|
84
|
-
toast.showToast("Failed to lift ban.", "error");
|
|
85
|
-
},
|
|
86
82
|
});
|
|
87
83
|
const handleManageBulkClick = (selection) => {
|
|
88
84
|
const rowId = selection.selectedIds[0];
|
|
@@ -8,7 +8,7 @@ import { apiClient } from "../../../http";
|
|
|
8
8
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
9
9
|
import { quickCreateTaxonomySchema } from "../schemas/small-forms";
|
|
10
10
|
import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary";
|
|
11
|
-
import { applyZodIssues } from "../../../ui/forms/
|
|
11
|
+
import { applyZodIssues } from "../../../ui/forms/apply-zod-issues";
|
|
12
12
|
function toBrandSlug(str) {
|
|
13
13
|
const base = str.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
14
14
|
return base.startsWith("brand-") ? base : `brand-${base}`;
|
|
@@ -19,6 +19,7 @@ export function BrandQuickCreateForm({ onSaved, onCancel }) {
|
|
|
19
19
|
const [isActive, setIsActive] = React.useState(true);
|
|
20
20
|
const { showToast } = useToast();
|
|
21
21
|
const mutation = useApiMutation({
|
|
22
|
+
errorMessage: "Failed to create brand.",
|
|
22
23
|
mutationFn: async () => apiClient.post(ADMIN_ENDPOINTS.BRANDS, {
|
|
23
24
|
name,
|
|
24
25
|
slug: toBrandSlug(name),
|
|
@@ -29,9 +30,6 @@ export function BrandQuickCreateForm({ onSaved, onCancel }) {
|
|
|
29
30
|
const id = res?.data?.id ?? res?.id ?? "";
|
|
30
31
|
onSaved(id, name);
|
|
31
32
|
},
|
|
32
|
-
onError: (err) => {
|
|
33
|
-
showToast(err?.message ?? "Failed to create brand.", "error");
|
|
34
|
-
},
|
|
35
33
|
});
|
|
36
34
|
return (_jsx(Form, { schema: quickCreateTaxonomySchema, onSubmit: (e) => e.preventDefault(), spacing: "md", children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsx(FormErrorSummary, {}), _jsx(Input, { label: "Brand name", value: name, onChange: (e) => setName(e.target.value), required: true, placeholder: "e.g. Hot Wheels", autoFocus: true }), _jsx(Input, { label: "Description", value: description, onChange: (e) => setDescription(e.target.value), placeholder: "Optional brief description" }), _jsx(Toggle, { label: "Active", checked: isActive, onChange: setIsActive }), _jsxs(Row, { gap: "3", padding: "t-xs", children: [_jsx(Button, { type: "submit", isLoading: mutation.isPending, disabled: mutation.isPending, onClick: () => {
|
|
37
35
|
clearErrors();
|
|
@@ -8,7 +8,7 @@ import { apiClient } from "../../../http";
|
|
|
8
8
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
9
9
|
import { quickCreateTaxonomySchema } from "../schemas/small-forms";
|
|
10
10
|
import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary";
|
|
11
|
-
import { applyZodIssues } from "../../../ui/forms/
|
|
11
|
+
import { applyZodIssues } from "../../../ui/forms/apply-zod-issues";
|
|
12
12
|
function toCategorySlug(str) {
|
|
13
13
|
return str.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
14
14
|
}
|
|
@@ -18,6 +18,7 @@ export function CategoryQuickCreateForm({ onSaved, onCancel }) {
|
|
|
18
18
|
const [isActive, setIsActive] = React.useState(true);
|
|
19
19
|
const { showToast } = useToast();
|
|
20
20
|
const mutation = useApiMutation({
|
|
21
|
+
errorMessage: "Failed to create category.",
|
|
21
22
|
mutationFn: async () => apiClient.post(ADMIN_ENDPOINTS.CATEGORIES, {
|
|
22
23
|
name,
|
|
23
24
|
slug: toCategorySlug(name),
|
|
@@ -28,9 +29,6 @@ export function CategoryQuickCreateForm({ onSaved, onCancel }) {
|
|
|
28
29
|
const id = res?.data?.id ?? res?.id ?? "";
|
|
29
30
|
onSaved(id, name);
|
|
30
31
|
},
|
|
31
|
-
onError: (err) => {
|
|
32
|
-
showToast(err?.message ?? "Failed to create category.", "error");
|
|
33
|
-
},
|
|
34
32
|
});
|
|
35
33
|
return (_jsx(Form, { schema: quickCreateTaxonomySchema, onSubmit: (e) => e.preventDefault(), spacing: "md", children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsx(FormErrorSummary, {}), _jsx(Input, { label: "Category name", value: name, onChange: (e) => setName(e.target.value), required: true, placeholder: "e.g. Trading Cards", autoFocus: true }), _jsx(Input, { label: "Description", value: description, onChange: (e) => setDescription(e.target.value), placeholder: "Optional brief description" }), _jsx(Toggle, { label: "Active", checked: isActive, onChange: setIsActive }), _jsxs(Row, { gap: "3", padding: "t-xs", children: [_jsx(Button, { type: "submit", isLoading: mutation.isPending, disabled: mutation.isPending, onClick: () => {
|
|
36
34
|
clearErrors();
|
|
@@ -7,7 +7,7 @@ import { isAuthError } from "../../../utils/auth-error";
|
|
|
7
7
|
import { Button, CountdownDisplay, Div, LoginRequiredModal, Modal, Row, Span, Stack, Text } from "../../../ui";
|
|
8
8
|
import { Form } from "../../../ui/components/Form";
|
|
9
9
|
import { FieldInput } from "../../../ui/forms/FieldInput";
|
|
10
|
-
import { applyZodIssues } from "../../../ui/forms/
|
|
10
|
+
import { applyZodIssues } from "../../../ui/forms/apply-zod-issues";
|
|
11
11
|
import { placeBidSchema } from "../schemas/bid-input";
|
|
12
12
|
import { useLiveAuctionBid } from "../hooks/useLiveAuctionBid";
|
|
13
13
|
import { AUCTION_BUYOUT_WINDOW_MINUTES } from "../../../_internal/shared/checkout/lanes";
|
|
@@ -5,7 +5,7 @@ import { useState } from "react";
|
|
|
5
5
|
import { Alert, Button, Div, Heading, Row, Stack, Text, useToast } from "../../../ui";
|
|
6
6
|
import { Form } from "../../../ui/components/Form";
|
|
7
7
|
import { FieldInput } from "../../../ui/forms/FieldInput";
|
|
8
|
-
import { applyZodIssues } from "../../../ui/forms/
|
|
8
|
+
import { applyZodIssues } from "../../../ui/forms/apply-zod-issues";
|
|
9
9
|
import { forgotPasswordSchema } from "../schemas";
|
|
10
10
|
import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary";
|
|
11
11
|
export function ForgotPasswordView({ onSubmit, isLoading = false, error, success, labels = {}, renderBackLink, className = "", }) {
|
|
@@ -6,7 +6,7 @@ import { Alert, Button, Div, Heading, Row, SiteMark, Stack, Text, useToast } fro
|
|
|
6
6
|
import { Form } from "../../../ui/components/Form";
|
|
7
7
|
import { FieldInput } from "../../../ui/forms/FieldInput";
|
|
8
8
|
import { FieldCheckbox } from "../../../ui/forms/FieldCheckbox";
|
|
9
|
-
import { applyZodIssues } from "../../../ui/forms/
|
|
9
|
+
import { applyZodIssues } from "../../../ui/forms/apply-zod-issues";
|
|
10
10
|
import { loginSchema } from "../schemas";
|
|
11
11
|
import { SocialAuthButtons } from "./SocialAuthButtons";
|
|
12
12
|
import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary";
|
|
@@ -7,7 +7,7 @@ import { Alert, Button, Div, Heading, Row, SiteMark, Stack, Text, useToast } fro
|
|
|
7
7
|
import { Form } from "../../../ui/components/Form";
|
|
8
8
|
import { FieldInput } from "../../../ui/forms/FieldInput";
|
|
9
9
|
import { FieldCheckbox } from "../../../ui/forms/FieldCheckbox";
|
|
10
|
-
import { applyZodIssues } from "../../../ui/forms/
|
|
10
|
+
import { applyZodIssues } from "../../../ui/forms/apply-zod-issues";
|
|
11
11
|
import { registerSchema, registerPasswordSchema } from "../schemas";
|
|
12
12
|
import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary";
|
|
13
13
|
const registerClientSchema = registerSchema.extend({
|
|
@@ -6,7 +6,7 @@ import { z } from "zod";
|
|
|
6
6
|
import { Alert, Button, Div, Heading, Row, Stack, Text, useToast } from "../../../ui";
|
|
7
7
|
import { Form } from "../../../ui/components/Form";
|
|
8
8
|
import { FieldInput } from "../../../ui/forms/FieldInput";
|
|
9
|
-
import { applyZodIssues } from "../../../ui/forms/
|
|
9
|
+
import { applyZodIssues } from "../../../ui/forms/apply-zod-issues";
|
|
10
10
|
import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary";
|
|
11
11
|
const resetClientSchema = z.object({
|
|
12
12
|
password: z.string().min(6, "Password must be at least 6 characters"),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Button, Div, Row, Span } from "../../../ui";
|
|
3
3
|
export function SocialAuthButtons({ onGoogle, disabled, dividerLabel, googleLabel, }) {
|
|
4
|
-
return (_jsxs(_Fragment, { children: [_jsxs(Div, { className: "relative", children: [_jsx(Row, { className: "absolute inset-0", align: "center", children: _jsx(Div, { className: "w-full border-t border-neutral-200" }) }), _jsx(Row, { textSize: "sm", className: "relative", justify: "center", children: _jsx(Span, { padding: "x-xs", surface: "default", color: "muted", children: dividerLabel }) })] }), _jsx(Div, { layout: "grid", gap: "3", className: "grid-cols-1", children: _jsxs(Button, { rounded: "md", type: "button", onClick: onGoogle, disabled: disabled, variant: "outline", paddingX: "md", paddingY: "sm", textSize: "sm", weight: "medium", className: "inline- justify-[center] border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] bg-[var(--appkit-color-surface)] text-[var(--appkit-color-text)] dark:text-[var(--appkit-color-text)] hover:bg-surface-hover disabled:cursor-not-allowed disabled:opacity-50", children: [_jsxs("svg", { className: "mr-2 h-5 w-5", viewBox: "0 0 24 24", "aria-hidden": "true", children: [_jsx("path", { fill: "currentColor", d: "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" }), _jsx("path", { fill: "currentColor", d: "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" }), _jsx("path", { fill: "currentColor", d: "M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" }), _jsx("path", { fill: "currentColor", d: "M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" })] }), googleLabel] }) })] }));
|
|
4
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Div, { className: "relative", children: [_jsx(Row, { className: "absolute inset-0", align: "center", children: _jsx(Div, { className: "w-full border-t border-neutral-200" }) }), _jsx(Row, { textSize: "sm", className: "relative", justify: "center", children: _jsx(Span, { padding: "x-xs", surface: "default", color: "muted", children: dividerLabel }) })] }), _jsx(Div, { layout: "grid", gap: "3", className: "grid-cols-1", children: _jsxs(Button, { rounded: "md", type: "button", onClick: onGoogle, disabled: disabled, variant: "outline", paddingX: "md", paddingY: "sm", textSize: "sm", weight: "medium", className: "inline- justify-[center] border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] bg-[var(--appkit-color-surface)] text-[var(--appkit-color-text)] dark:text-[var(--appkit-color-text)] hover:bg-surface-hover disabled:cursor-not-allowed disabled:opacity-50", children: [_jsxs("svg", { className: "mr-2 h-5 w-5 shrink-0", viewBox: "0 0 24 24", "aria-hidden": "true", children: [_jsx("path", { fill: "currentColor", d: "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" }), _jsx("path", { fill: "currentColor", d: "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" }), _jsx("path", { fill: "currentColor", d: "M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" }), _jsx("path", { fill: "currentColor", d: "M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" })] }), googleLabel] }) })] }));
|
|
5
5
|
}
|
|
@@ -57,8 +57,20 @@ export class SessionRepository extends BaseRepository {
|
|
|
57
57
|
async updateActivity(sessionId, data) {
|
|
58
58
|
const updateData = {
|
|
59
59
|
lastActivity: new Date(),
|
|
60
|
-
...(data?.location && { location: data.location }),
|
|
61
60
|
};
|
|
61
|
+
// Firestore `.update()` REPLACES a map field wholesale — it does not merge.
|
|
62
|
+
// Passing `location` straight through therefore destroyed whichever of
|
|
63
|
+
// `{ country, city }` the caller happened to omit, and `location.city` is
|
|
64
|
+
// PII-encrypted, so the loss is unrecoverable. Same footgun the profile
|
|
65
|
+
// route documents for `uiPreferences`.
|
|
66
|
+
//
|
|
67
|
+
// Latent rather than live: both current callers pass no `data` at all, so
|
|
68
|
+
// this branch never runs today. It costs one read, and only on the branch
|
|
69
|
+
// that supplies a location — the hot activity ping stays a single write.
|
|
70
|
+
if (data?.location) {
|
|
71
|
+
const current = await this.findById(sessionId); // mapDoc → already decrypted
|
|
72
|
+
updateData.location = { ...(current?.location ?? {}), ...data.location };
|
|
73
|
+
}
|
|
62
74
|
try {
|
|
63
75
|
await this.update(sessionId, updateData);
|
|
64
76
|
}
|
|
@@ -11,10 +11,10 @@ export declare const createBlogPostSchema: z.ZodObject<{
|
|
|
11
11
|
isFeatured: z.ZodDefault<z.ZodBoolean>;
|
|
12
12
|
status: z.ZodDefault<z.ZodEnum<["draft", "published", "archived"]>>;
|
|
13
13
|
coverImage: z.ZodOptional<z.ZodNullable<z.ZodUnion<[z.ZodObject<{
|
|
14
|
-
url: z.ZodString
|
|
14
|
+
url: z.ZodEffects<z.ZodString, string, string>;
|
|
15
15
|
type: z.ZodEnum<["image", "video", "file"]>;
|
|
16
16
|
alt: z.ZodOptional<z.ZodString>;
|
|
17
|
-
thumbnailUrl: z.ZodOptional<z.ZodString
|
|
17
|
+
thumbnailUrl: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
18
18
|
}, "strip", z.ZodTypeAny, {
|
|
19
19
|
type: "video" | "image" | "file";
|
|
20
20
|
url: string;
|
|
@@ -25,15 +25,15 @@ export declare const createBlogPostSchema: z.ZodObject<{
|
|
|
25
25
|
url: string;
|
|
26
26
|
alt?: string | undefined;
|
|
27
27
|
thumbnailUrl?: string | undefined;
|
|
28
|
-
}>, z.ZodEffects<z.ZodString, {
|
|
28
|
+
}>, z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, {
|
|
29
29
|
url: string;
|
|
30
30
|
type: "image";
|
|
31
31
|
}, string>]>>>;
|
|
32
32
|
contentImages: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
33
|
-
url: z.ZodString
|
|
33
|
+
url: z.ZodEffects<z.ZodString, string, string>;
|
|
34
34
|
type: z.ZodEnum<["image", "video", "file"]>;
|
|
35
35
|
alt: z.ZodOptional<z.ZodString>;
|
|
36
|
-
thumbnailUrl: z.ZodOptional<z.ZodString
|
|
36
|
+
thumbnailUrl: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
37
37
|
}, "strip", z.ZodTypeAny, {
|
|
38
38
|
type: "video" | "image" | "file";
|
|
39
39
|
url: string;
|
|
@@ -46,10 +46,10 @@ export declare const createBlogPostSchema: z.ZodObject<{
|
|
|
46
46
|
thumbnailUrl?: string | undefined;
|
|
47
47
|
}>, "many">>>;
|
|
48
48
|
additionalImages: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
49
|
-
url: z.ZodString
|
|
49
|
+
url: z.ZodEffects<z.ZodString, string, string>;
|
|
50
50
|
type: z.ZodEnum<["image", "video", "file"]>;
|
|
51
51
|
alt: z.ZodOptional<z.ZodString>;
|
|
52
|
-
thumbnailUrl: z.ZodOptional<z.ZodString
|
|
52
|
+
thumbnailUrl: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
53
53
|
}, "strip", z.ZodTypeAny, {
|
|
54
54
|
type: "video" | "image" | "file";
|
|
55
55
|
url: string;
|
|
@@ -150,10 +150,10 @@ export declare const updateBlogPostSchema: z.ZodObject<{
|
|
|
150
150
|
isFeatured: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
|
|
151
151
|
status: z.ZodOptional<z.ZodDefault<z.ZodEnum<["draft", "published", "archived"]>>>;
|
|
152
152
|
coverImage: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodUnion<[z.ZodObject<{
|
|
153
|
-
url: z.ZodString
|
|
153
|
+
url: z.ZodEffects<z.ZodString, string, string>;
|
|
154
154
|
type: z.ZodEnum<["image", "video", "file"]>;
|
|
155
155
|
alt: z.ZodOptional<z.ZodString>;
|
|
156
|
-
thumbnailUrl: z.ZodOptional<z.ZodString
|
|
156
|
+
thumbnailUrl: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
157
157
|
}, "strip", z.ZodTypeAny, {
|
|
158
158
|
type: "video" | "image" | "file";
|
|
159
159
|
url: string;
|
|
@@ -164,15 +164,15 @@ export declare const updateBlogPostSchema: z.ZodObject<{
|
|
|
164
164
|
url: string;
|
|
165
165
|
alt?: string | undefined;
|
|
166
166
|
thumbnailUrl?: string | undefined;
|
|
167
|
-
}>, z.ZodEffects<z.ZodString, {
|
|
167
|
+
}>, z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, {
|
|
168
168
|
url: string;
|
|
169
169
|
type: "image";
|
|
170
170
|
}, string>]>>>>;
|
|
171
171
|
contentImages: z.ZodOptional<z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
172
|
-
url: z.ZodString
|
|
172
|
+
url: z.ZodEffects<z.ZodString, string, string>;
|
|
173
173
|
type: z.ZodEnum<["image", "video", "file"]>;
|
|
174
174
|
alt: z.ZodOptional<z.ZodString>;
|
|
175
|
-
thumbnailUrl: z.ZodOptional<z.ZodString
|
|
175
|
+
thumbnailUrl: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
176
176
|
}, "strip", z.ZodTypeAny, {
|
|
177
177
|
type: "video" | "image" | "file";
|
|
178
178
|
url: string;
|
|
@@ -185,10 +185,10 @@ export declare const updateBlogPostSchema: z.ZodObject<{
|
|
|
185
185
|
thumbnailUrl?: string | undefined;
|
|
186
186
|
}>, "many">>>>;
|
|
187
187
|
additionalImages: z.ZodOptional<z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
188
|
-
url: z.ZodString
|
|
188
|
+
url: z.ZodEffects<z.ZodString, string, string>;
|
|
189
189
|
type: z.ZodEnum<["image", "video", "file"]>;
|
|
190
190
|
alt: z.ZodOptional<z.ZodString>;
|
|
191
|
-
thumbnailUrl: z.ZodOptional<z.ZodString
|
|
191
|
+
thumbnailUrl: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
192
192
|
}, "strip", z.ZodTypeAny, {
|
|
193
193
|
type: "video" | "image" | "file";
|
|
194
194
|
url: string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { sortBy } from "@mohasinac/appkit";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import { mediaUrlSchema } from "../../../validation/schemas";
|
|
3
4
|
import { serverLogger } from "../../../monitoring";
|
|
4
5
|
import { blogRepository } from "../repository/blog.repository";
|
|
5
6
|
import { coerceMediaField, getMediaUrl } from "../../../utils";
|
|
@@ -8,23 +9,20 @@ import { finalizeStagedMediaField, finalizeStagedMediaObject, finalizeStagedMedi
|
|
|
8
9
|
const singleImageMediaSchema = z
|
|
9
10
|
.union([
|
|
10
11
|
z.object({
|
|
11
|
-
url:
|
|
12
|
+
url: mediaUrlSchema,
|
|
12
13
|
type: z.enum(["image", "video", "file"]),
|
|
13
14
|
alt: z.string().optional(),
|
|
14
|
-
thumbnailUrl:
|
|
15
|
+
thumbnailUrl: mediaUrlSchema.optional(),
|
|
15
16
|
}),
|
|
16
|
-
|
|
17
|
-
.string()
|
|
18
|
-
.url()
|
|
19
|
-
.transform((url) => ({ url, type: "image" })),
|
|
17
|
+
mediaUrlSchema.transform((url) => ({ url, type: "image" })),
|
|
20
18
|
])
|
|
21
19
|
.nullable()
|
|
22
20
|
.optional();
|
|
23
21
|
const mediaFieldSchema = z.object({
|
|
24
|
-
url:
|
|
22
|
+
url: mediaUrlSchema,
|
|
25
23
|
type: z.enum(["image", "video", "file"]),
|
|
26
24
|
alt: z.string().optional(),
|
|
27
|
-
thumbnailUrl:
|
|
25
|
+
thumbnailUrl: mediaUrlSchema.optional(),
|
|
28
26
|
});
|
|
29
27
|
export const createBlogPostSchema = z.object({
|
|
30
28
|
title: z.string().min(1),
|
|
@@ -267,7 +267,7 @@ export declare const blogPostSchema: z.ZodObject<{
|
|
|
267
267
|
thumbnailUrl?: string | undefined;
|
|
268
268
|
youtubeId?: string | undefined;
|
|
269
269
|
duration?: number | undefined;
|
|
270
|
-
}>, z.ZodEffects<z.ZodString, {
|
|
270
|
+
}>, z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, {
|
|
271
271
|
url: string;
|
|
272
272
|
type: "image";
|
|
273
273
|
}, string>]>>>, {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from "./firestore";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import { mediaUrlSchema } from "../../../validation/schemas";
|
|
3
4
|
import { mediaFieldSchema } from "../../media/types/index";
|
|
4
5
|
import { auditTimestampsShape, firestoreDateSchema } from "../../../schemas/firestore-helpers";
|
|
5
6
|
// ─── Firestore document schema (W2) ───────────────────────────────────────────
|
|
@@ -31,10 +32,7 @@ export const blogPostFirestoreSchema = z.object({
|
|
|
31
32
|
const blogCoverImageSchema = z
|
|
32
33
|
.union([
|
|
33
34
|
mediaFieldSchema,
|
|
34
|
-
|
|
35
|
-
.string()
|
|
36
|
-
.url()
|
|
37
|
-
.transform((url) => ({ url, type: "image" })),
|
|
35
|
+
mediaUrlSchema.transform((url) => ({ url, type: "image" })),
|
|
38
36
|
])
|
|
39
37
|
.nullable()
|
|
40
38
|
.optional()
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { NextResponse } from "next/server.js";
|
|
12
12
|
import { z } from "zod";
|
|
13
|
+
import { mediaUrlSchema } from "../../../../validation/schemas";
|
|
13
14
|
import { getProviders } from "../../../../contracts";
|
|
14
15
|
import { createRouteHandler } from "../../../../next";
|
|
15
16
|
import { normalizeError } from "../../../../errors/normalize";
|
|
@@ -48,7 +49,7 @@ const categoryUpdateSchema = z
|
|
|
48
49
|
tier: z.number().int().min(0).optional(),
|
|
49
50
|
order: z.number().int().min(0).optional(),
|
|
50
51
|
description: z.string().optional(),
|
|
51
|
-
imageUrl:
|
|
52
|
+
imageUrl: mediaUrlSchema.optional(),
|
|
52
53
|
isFeatured: z.boolean().optional(),
|
|
53
54
|
showOnHomepage: z.boolean().optional(),
|
|
54
55
|
isActive: z.boolean().optional(),
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { NextResponse } from "next/server.js";
|
|
16
16
|
import { z } from "zod";
|
|
17
|
+
import { mediaUrlSchema } from "../../../validation/schemas";
|
|
17
18
|
import { getProviders } from "../../../contracts";
|
|
18
19
|
import { createRouteHandler } from "../../../next";
|
|
19
20
|
import { normalizeError } from "../../../errors/normalize";
|
|
@@ -177,7 +178,7 @@ const categoryCreateSchema = z
|
|
|
177
178
|
tier: z.number().int().min(0).optional(),
|
|
178
179
|
order: z.number().int().min(0).optional(),
|
|
179
180
|
description: z.string().optional(),
|
|
180
|
-
imageUrl:
|
|
181
|
+
imageUrl: mediaUrlSchema.optional(),
|
|
181
182
|
isFeatured: z.boolean().optional(),
|
|
182
183
|
showOnHomepage: z.boolean().optional(),
|
|
183
184
|
})
|
|
@@ -467,7 +467,7 @@ export declare const eventItemSchema: z.ZodObject<{
|
|
|
467
467
|
thumbnailUrl?: string | undefined;
|
|
468
468
|
youtubeId?: string | undefined;
|
|
469
469
|
duration?: number | undefined;
|
|
470
|
-
}>, z.ZodEffects<z.ZodString, {
|
|
470
|
+
}>, z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, {
|
|
471
471
|
url: string;
|
|
472
472
|
type: "image";
|
|
473
473
|
}, string>]>>>, {
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
export * from "./firestore";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import { mediaUrlSchema } from "../../../validation/schemas";
|
|
3
4
|
import { mediaFieldSchema } from "../../media/types/index";
|
|
4
5
|
const eventCoverImageSchema = z
|
|
5
6
|
.union([
|
|
6
7
|
mediaFieldSchema,
|
|
7
|
-
|
|
8
|
-
.string()
|
|
9
|
-
.url()
|
|
10
|
-
.transform((url) => ({ url, type: "image" })),
|
|
8
|
+
mediaUrlSchema.transform((url) => ({ url, type: "image" })),
|
|
11
9
|
])
|
|
12
10
|
.nullable()
|
|
13
11
|
.optional()
|