@mohasinac/appkit 4.41.7 β 4.41.9
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.
|
@@ -77,7 +77,21 @@ export async function handleOrderCreate(input, ctx) {
|
|
|
77
77
|
const firstItem = items[0];
|
|
78
78
|
const firstItemName = firstItem?.productTitle ?? "an item";
|
|
79
79
|
const additionalItemCount = Math.max(0, items.length - 1);
|
|
80
|
-
|
|
80
|
+
/*
|
|
81
|
+
* π DECRYPT. This handler receives the RAW Firestore snapshot β the type
|
|
82
|
+
* above says so β so it never passes through the repository's `mapDoc`, and
|
|
83
|
+
* `userName` is PII, stored as `enc:v1:β¦`.
|
|
84
|
+
*
|
|
85
|
+
* Undecrypted, the ciphertext was interpolated straight into the announcement
|
|
86
|
+
* body and PERSISTED there, so every admin alert read
|
|
87
|
+
* "ποΈ New order! enc:v1:67Mn0rITjTWGsZZE:β¦ purchased β¦". Measured on
|
|
88
|
+
* production: 383 such rows. The message is baked at write time, so the
|
|
89
|
+
* damage is permanent per row β decrypting on read would not repair them.
|
|
90
|
+
*
|
|
91
|
+
* `decryptPii` was already imported in this file and used for the store
|
|
92
|
+
* owner's phone eleven lines up; the buyer's name was simply missed.
|
|
93
|
+
*/
|
|
94
|
+
const buyerName = decryptPii(order.userName) ?? order.userName ?? "A customer";
|
|
81
95
|
const message = buildPurchaseAnnouncementMessage({
|
|
82
96
|
buyerName,
|
|
83
97
|
firstItemName,
|
|
@@ -54,14 +54,24 @@ function AdminCardItem({ row, view, selected, onToggleSelect, onRowClick, render
|
|
|
54
54
|
return (_jsxs(Row, { gap: "sm", className: [
|
|
55
55
|
"px-[var(--appkit-space-4)] py-[var(--appkit-space-3)] cursor-pointer transition-colors hover:bg-surface-hover",
|
|
56
56
|
selected ? "bg-primary-50/40 dark:bg-primary-900/10" : "",
|
|
57
|
-
].filter(Boolean).join(" "), onClick: handleClick, role: onRowClick ? "button" : undefined, children: [onToggleSelect && (_jsx(Div, { "data-no-row-click": true, className: "shrink-0", onClick: (e) => {
|
|
57
|
+
].filter(Boolean).join(" "), onClick: handleClick, role: onRowClick ? "button" : undefined, children: [onToggleSelect && (_jsx(Div, { "data-no-row-click": true, className: "shrink-0", onClick: (e) => {
|
|
58
|
+
e.stopPropagation();
|
|
59
|
+
if (e.target === e.currentTarget)
|
|
60
|
+
onToggleSelect(row.id);
|
|
61
|
+
}, children: _jsx(Checkbox, { bare: true, checked: selected, onChange: () => onToggleSelect(row.id), className: "h-4 w-4 rounded border-zinc-300 text-primary accent-primary cursor-pointer", "aria-label": `Select ${row.primary}` }) })), _jsx(RowAvatar, { image: row.image, alt: row.primary, icon: resourceIcon }), _jsxs(Stack, { gap: "none", className: "flex-1 min-w-0", children: [_jsx(Text, { size: "sm", weight: "semibold", className: "truncate", color: "primary", children: row.primary }), _jsx(Text, { size: "xs", color: "muted", className: "truncate", children: row.secondary }), row.barcodeId && (_jsx(Text, { size: "xs", color: "faint", className: "truncate font-mono", children: row.barcodeId }))] }), flags.length > 0 && (_jsx(Row, { gap: "xs", className: "hidden sm:flex shrink-0", children: flags.map(({ key, label, tone }) => (_jsx(Span, { padding: "pill-2xs", weight: "medium", className: `inline-flex text-[10px] ${STATUS_TONE_CLASSES[tone]}`, rounded: "full", children: label }, key))) })), _jsx(StatusBadge, { status: row.status }), _jsx(Span, { size: "xs", color: "muted", className: "hidden sm:block shrink-0 w-24", align: "end", children: row.updatedAt }), renderRowActions && (_jsx(Div, { "data-no-row-click": true, className: "shrink-0", onClick: (e) => e.stopPropagation(), children: renderRowActions(row) }))] }));
|
|
58
62
|
}
|
|
59
63
|
return (_jsxs(Div, { rounded: "xl", className: [
|
|
60
64
|
"border overflow-hidden cursor-pointer transition-all hover:shadow-md hover:-translate-y-0.5",
|
|
61
65
|
selected
|
|
62
66
|
? "border-primary ring-1 ring-primary/20 bg-primary-50/30 dark:bg-primary-900/10"
|
|
63
67
|
: " border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)]",
|
|
64
|
-
].filter(Boolean).join(" "), onClick: handleClick, role: onRowClick ? "button" : undefined, children: [onToggleSelect && (_jsxs(Row, { gap: "xs", "data-no-row-click": true, paddingY: "t-sm", padding: "x-sm",
|
|
68
|
+
].filter(Boolean).join(" "), onClick: handleClick, role: onRowClick ? "button" : undefined, children: [onToggleSelect && (_jsxs(Row, { gap: "xs", "data-no-row-click": true, paddingY: "t-sm", padding: "x-sm",
|
|
69
|
+
// Same double-toggle guard as the compact row above β see that comment.
|
|
70
|
+
onClick: (e) => {
|
|
71
|
+
e.stopPropagation();
|
|
72
|
+
if (e.target === e.currentTarget)
|
|
73
|
+
onToggleSelect(row.id);
|
|
74
|
+
}, children: [_jsx(Checkbox, { bare: true, checked: selected, onChange: () => onToggleSelect(row.id), className: "h-4 w-4 rounded border-zinc-300 text-primary accent-primary cursor-pointer", "aria-label": `Select ${row.primary}` }), flags.length > 0 && (_jsx(Row, { gap: "xs", wrap: true, children: flags.map(({ key, label, tone }) => (_jsx(Span, { padding: "pill-2xs", weight: "medium", className: `inline-flex text-[10px] ${STATUS_TONE_CLASSES[tone]}`, rounded: "full", children: label }, key))) }))] })), _jsxs(Stack, { gap: "xs", className: `${__P.p3}.5`, children: [_jsxs(Row, { gap: "sm", align: "center", children: [_jsx(RowAvatar, { image: row.image, alt: row.primary, icon: resourceIcon, size: "12" }), _jsxs(Stack, { gap: "none", className: "min-w-0 flex-1", children: [_jsx(Text, { size: "sm", weight: "semibold", className: "line-clamp-2 leading-snug", color: "primary", children: row.primary }), _jsx(Text, { size: "xs", color: "muted", className: "truncate", children: row.secondary })] })] }), row.barcodeId && (_jsx(Text, { size: "xs", color: "faint", className: "truncate font-mono", children: row.barcodeId })), _jsxs(Row, { justify: "between", gap: "xs", children: [_jsx(StatusBadge, { status: row.status }), _jsx(Span, { color: "muted", className: "text-[11px] shrink-0", children: row.updatedAt })] }), renderRowActions && (_jsx(Row, { "data-no-row-click": true, justify: "end", gap: "xs", className: "border-t border-[var(--appkit-color-border)] -mx-[var(--appkit-space-3)] -mb-[var(--appkit-space-3)] mt-[var(--appkit-space-1)] px-[var(--appkit-space-3)] py-[var(--appkit-space-2)]", onClick: (e) => e.stopPropagation(), children: renderRowActions(row) }))] })] }));
|
|
65
75
|
}
|
|
66
76
|
export function AdminViewCards({ rows, view, isLoading, emptyLabel = "No items found", onRowClick, selectedIdSet, onToggleSelect, renderRowActions, resourceIcon, }) {
|
|
67
77
|
if (isLoading) {
|
|
@@ -15,7 +15,6 @@ import { DEFAULT_MIN_OFFER_PERCENT } from "../../../_internal/shared/features/of
|
|
|
15
15
|
import { PRODUCT_MAX_IMAGES, PRODUCT_MAX_VIDEOS, PRODUCT_IMAGE_INDEX_MAX, } from "../../../_internal/shared/media/limits";
|
|
16
16
|
import { isFinalSale } from "../../products/constants/final-sale";
|
|
17
17
|
import { normalizeError } from "../../../errors/normalize";
|
|
18
|
-
import { toUserMessage } from "../../../errors/error-display-map";
|
|
19
18
|
/**
|
|
20
19
|
* "bundle" is a categoryType, not a ListingType, so it can't live in
|
|
21
20
|
* LISTING_TYPE_REGISTRY β this is the single, explicit escape hatch that
|
|
@@ -351,6 +350,29 @@ const EDIT_SECTIONS = [
|
|
|
351
350
|
{ id: "publish", label: "Publish" },
|
|
352
351
|
];
|
|
353
352
|
// ββ Main SellerProductShell βββββββββββββββββββββββββββββββββββββββββββββββ
|
|
353
|
+
/**
|
|
354
|
+
* The message for a failed save.
|
|
355
|
+
*
|
|
356
|
+
* π "Fix the highlighted errors" may ONLY be said when something is actually
|
|
357
|
+
* highlighted. `toUserMessage` short-circuits to its `fallback` whenever no
|
|
358
|
+
* translator is passed (`if (!t) return generic`), so passing that sentence as
|
|
359
|
+
* the fallback rendered it for EVERY failure code β an authorization or
|
|
360
|
+
* not-found refusal was reported to the seller as a validation problem, with
|
|
361
|
+
* nothing marked and nothing to act on.
|
|
362
|
+
*
|
|
363
|
+
* With no issues we say so plainly and quote the CODE. A code is a stable
|
|
364
|
+
* identifier, not server text, so this does not reintroduce the raw-message
|
|
365
|
+
* leak Rule #9 Β§6 forbids β it is the same reasoning that puts the error digest
|
|
366
|
+
* in front of users (Root Cause #78): an opaque handle they can quote beats an
|
|
367
|
+
* explanation that is wrong.
|
|
368
|
+
*/
|
|
369
|
+
function saveFailureMessage(code, issueCount) {
|
|
370
|
+
if (issueCount > 0)
|
|
371
|
+
return "Fix the highlighted errors and try again.";
|
|
372
|
+
return code
|
|
373
|
+
? `Could not save β the server refused this change (${code}). Nothing was changed.`
|
|
374
|
+
: "Could not save β the server refused this change. Nothing was changed.";
|
|
375
|
+
}
|
|
354
376
|
export function SellerProductShell({ mode, listingType = "standard", initialValues,
|
|
355
377
|
/*
|
|
356
378
|
* `productId` was declared on the props interface and NEVER DESTRUCTURED β
|
|
@@ -426,9 +448,7 @@ productId, onSave, onPublish, onDiscard, isLoading = false, storeSlug, renderCat
|
|
|
426
448
|
}
|
|
427
449
|
applyZodIssues(result.issues ?? [], setFieldError);
|
|
428
450
|
if (!silent) {
|
|
429
|
-
showToast(
|
|
430
|
-
fallback: "Fix the highlighted errors and try again.",
|
|
431
|
-
}), "error");
|
|
451
|
+
showToast(saveFailureMessage(result.code, (result.issues ?? []).length), "error");
|
|
432
452
|
}
|
|
433
453
|
}
|
|
434
454
|
catch (err) {
|
|
@@ -469,9 +489,7 @@ productId, onSave, onPublish, onDiscard, isLoading = false, storeSlug, renderCat
|
|
|
469
489
|
return;
|
|
470
490
|
}
|
|
471
491
|
applyZodIssues(result.issues ?? [], setFieldError);
|
|
472
|
-
showToast(
|
|
473
|
-
fallback: "Fix the highlighted errors and try again.",
|
|
474
|
-
}), "error");
|
|
492
|
+
showToast(saveFailureMessage(result.code, (result.issues ?? []).length), "error");
|
|
475
493
|
}
|
|
476
494
|
catch (err) {
|
|
477
495
|
void normalizeError(err);
|
package/dist/index.d.ts
CHANGED
|
@@ -3040,7 +3040,7 @@ export { formatMonthYear } from "./utils/date.formatter";
|
|
|
3040
3040
|
export { generateMediaFilename } from "./utils/id-generators";
|
|
3041
3041
|
export { deriveContextTypeFromFilename } from "./utils/id-generators";
|
|
3042
3042
|
export type { MediaFilenameContext } from "./utils/id-generators";
|
|
3043
|
-
export { resolveMediaUrl, resolveVideoUrl, FIREBASE_STORAGE_HOST, GCS_HOST, isStoredMediaRef, MEDIA_PROXY_PREFIX, MEDIA_URL_MAX_LENGTH, MEDIA_URL_MESSAGE, APPROVED_MEDIA_DOMAINS, } from "./utils/media-url";
|
|
3043
|
+
export { resolveMediaUrl, resolveVideoUrl, FIREBASE_STORAGE_HOST, GCS_HOST, isStoredMediaRef, getYouTubeVideoId, MEDIA_PROXY_PREFIX, MEDIA_URL_MAX_LENGTH, MEDIA_URL_MESSAGE, APPROVED_MEDIA_DOMAINS, } from "./utils/media-url";
|
|
3044
3044
|
export { generateFAQId } from "./utils/id-generators";
|
|
3045
3045
|
export type { GenerateFAQIdInput } from "./utils/id-generators";
|
|
3046
3046
|
export { generateCroppedImageFilename } from "./utils/id-generators";
|
package/dist/index.js
CHANGED
|
@@ -5401,7 +5401,7 @@ export { formatCurrency, formatFileSize } from "./utils/number.formatter";
|
|
|
5401
5401
|
export { formatMonthYear } from "./utils/date.formatter";
|
|
5402
5402
|
export { generateMediaFilename } from "./utils/id-generators"; // generateProductImageFilename already exported from "./utils/id-generators";
|
|
5403
5403
|
export { deriveContextTypeFromFilename } from "./utils/id-generators";
|
|
5404
|
-
export { resolveMediaUrl, resolveVideoUrl, FIREBASE_STORAGE_HOST, GCS_HOST, isStoredMediaRef, MEDIA_PROXY_PREFIX, MEDIA_URL_MAX_LENGTH, MEDIA_URL_MESSAGE, APPROVED_MEDIA_DOMAINS, } from "./utils/media-url";
|
|
5404
|
+
export { resolveMediaUrl, resolveVideoUrl, FIREBASE_STORAGE_HOST, GCS_HOST, isStoredMediaRef, getYouTubeVideoId, MEDIA_PROXY_PREFIX, MEDIA_URL_MAX_LENGTH, MEDIA_URL_MESSAGE, APPROVED_MEDIA_DOMAINS, } from "./utils/media-url";
|
|
5405
5405
|
export { generateFAQId } from "./utils/id-generators";
|
|
5406
5406
|
export { generateCroppedImageFilename } from "./utils/id-generators";
|
|
5407
5407
|
// D5 + VC7 buyerβseller conversations were deleted 2026-08-31 (W11/D16). The
|