@mohasinac/appkit 4.41.6 → 4.41.7
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/digital-code/DigitalContentPoolManager.js +2 -1
- package/dist/_internal/server/features/products/service.d.ts +11 -0
- package/dist/_internal/server/features/products/service.js +37 -0
- package/dist/_internal/shared/actions/action-registry.js +11 -0
- package/dist/_internal/shared/features/products/schema.d.ts +43 -319
- package/dist/_internal/shared/features/products/schema.js +40 -1
- package/dist/_internal/shared/media/limits.d.ts +12 -0
- package/dist/_internal/shared/media/limits.js +12 -0
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/features/blog/components/BlogPostForm.js +2 -1
- package/dist/features/catalogue/components/CatalogueItemEditorView.js +2 -1
- package/dist/features/homepage/components/CharacterHotspotForm.js +3 -2
- package/dist/features/media/AvatarUpload.js +2 -1
- package/dist/features/media/upload/ImageUpload.js +2 -1
- package/dist/features/seller/actions/seller-actions.js +2 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.js +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { IMAGE_ACCEPT_ATTR } from "../../../shared/media/limits";
|
|
3
4
|
import { useCallback, useEffect, useState } from "react";
|
|
4
5
|
import { normalizeError } from "../../../../errors/normalize";
|
|
5
6
|
import { Alert, Button, Div, Row, Span, Stack, Text } from "../../../../ui";
|
|
@@ -63,5 +64,5 @@ export function DigitalContentPoolManager({ productId, api, canUploadFiles = fal
|
|
|
63
64
|
await api.addCodes(productId, codes);
|
|
64
65
|
setBulk("");
|
|
65
66
|
});
|
|
66
|
-
}, children: "Add codes" }), _jsx(FilePickerButton, { variant: "secondary", size: "sm", disabled: busy, accept: canUploadFiles ? undefined :
|
|
67
|
+
}, children: "Add codes" }), _jsx(FilePickerButton, { variant: "secondary", size: "sm", disabled: busy, accept: canUploadFiles ? undefined : IMAGE_ACCEPT_ATTR, onFile: (file) => void run(() => api.addAsset(productId, file)), children: canUploadFiles ? "Upload QR or file" : "Upload QR image" })] }), _jsx(Div, { children: entries === null ? (_jsx(Text, { size: "sm", color: "muted", children: "Loading\u2026" })) : entries.length === 0 ? (_jsx(Text, { size: "sm", color: "muted", children: "Nothing in the pool yet." })) : (_jsx(Stack, { gap: "xs", children: entries.map((e) => (_jsxs(Row, { gap: "sm", align: "center", justify: "between", children: [_jsxs(Text, { size: "sm", children: [KIND_LABEL[e.contentKind], e.fileName ? ` — ${e.fileName}` : "", _jsxs(Span, { color: "muted", children: [" \u00B7 ", e.status] })] }), e.status !== "claimed" && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", disabled: busy, onClick: () => void run(() => api.remove(productId, e.id)), children: "Remove" }))] }, e.id))) })) })] }));
|
|
67
68
|
}
|
|
@@ -18,6 +18,17 @@ export declare function assertPrizeDrawNotLocked(product: Pick<ProductDocument,
|
|
|
18
18
|
* seller from swapping in a cheaper item after a buyer has already won.
|
|
19
19
|
*/
|
|
20
20
|
export declare function assertPrizeDrawWonItemsImmutable(currentProduct: Pick<ProductDocument, "prizeDrawItems">, input: unknown): void;
|
|
21
|
+
/**
|
|
22
|
+
* Anti-scam guard — an auction's economic terms (starting bid, reserve, close
|
|
23
|
+
* time) stop being editable the moment a real bid exists. Moving the goalposts
|
|
24
|
+
* under a live bidder is the one auction edit that must never pass.
|
|
25
|
+
*
|
|
26
|
+
* 🛑 It compares VALUES, not key presence. The seller editor round-trips the
|
|
27
|
+
* whole draft, so every save resubmits `startingBid` and `auctionEndDate`
|
|
28
|
+
* unchanged — rejecting on presence alone would make a live auction entirely
|
|
29
|
+
* unsaveable, and the seller could no longer fix so much as a typo.
|
|
30
|
+
*/
|
|
31
|
+
export declare function assertAuctionTermsMutable(existing: Pick<ProductDocument, "listingType" | "bidsHaveStarted" | "bidCount" | "startingBid" | "reservePrice" | "auctionEndDate">, input: unknown): void;
|
|
21
32
|
/** Assert the product has available stock for a purchase. */
|
|
22
33
|
export declare function assertInStock(product: ProductDocument, quantity?: number): void;
|
|
23
34
|
/** Derive effective price (respects currentBid for auctions, base price otherwise). */
|
|
@@ -66,6 +66,43 @@ export function assertPrizeDrawWonItemsImmutable(currentProduct, input) {
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Anti-scam guard — an auction's economic terms (starting bid, reserve, close
|
|
71
|
+
* time) stop being editable the moment a real bid exists. Moving the goalposts
|
|
72
|
+
* under a live bidder is the one auction edit that must never pass.
|
|
73
|
+
*
|
|
74
|
+
* 🛑 It compares VALUES, not key presence. The seller editor round-trips the
|
|
75
|
+
* whole draft, so every save resubmits `startingBid` and `auctionEndDate`
|
|
76
|
+
* unchanged — rejecting on presence alone would make a live auction entirely
|
|
77
|
+
* unsaveable, and the seller could no longer fix so much as a typo.
|
|
78
|
+
*/
|
|
79
|
+
export function assertAuctionTermsMutable(existing, input) {
|
|
80
|
+
if ((existing.listingType ?? "standard") !== "auction")
|
|
81
|
+
return;
|
|
82
|
+
const bidding = existing.bidsHaveStarted === true || (existing.bidCount ?? 0) > 0;
|
|
83
|
+
if (!bidding)
|
|
84
|
+
return;
|
|
85
|
+
if (typeof input !== "object" || input === null)
|
|
86
|
+
return;
|
|
87
|
+
const patch = input;
|
|
88
|
+
const time = (v) => {
|
|
89
|
+
if (v == null)
|
|
90
|
+
return null;
|
|
91
|
+
const d = v instanceof Date ? v : new Date(v);
|
|
92
|
+
return Number.isNaN(d.getTime()) ? null : d.getTime();
|
|
93
|
+
};
|
|
94
|
+
const changed = [];
|
|
95
|
+
if ("startingBid" in patch && patch.startingBid !== existing.startingBid)
|
|
96
|
+
changed.push("starting bid");
|
|
97
|
+
if ("reservePrice" in patch && patch.reservePrice !== existing.reservePrice)
|
|
98
|
+
changed.push("reserve price");
|
|
99
|
+
if ("auctionEndDate" in patch &&
|
|
100
|
+
time(patch.auctionEndDate) !== time(existing.auctionEndDate))
|
|
101
|
+
changed.push("end date");
|
|
102
|
+
if (changed.length > 0) {
|
|
103
|
+
throw new ValidationError(`Bidding has already started on this auction, so its ${changed.join(" and ")} can no longer be changed. Everything else on the listing is still editable.`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
69
106
|
/** Assert the product has available stock for a purchase. */
|
|
70
107
|
export function assertInStock(product, quantity = 1) {
|
|
71
108
|
if ((product.availableQuantity ?? 0) < quantity) {
|
|
@@ -1527,6 +1527,17 @@ export const ACTIONS = {
|
|
|
1527
1527
|
description: "Approve a return request and initiate the refund flow.",
|
|
1528
1528
|
kind: "primary",
|
|
1529
1529
|
permissions: ["admin", "moderator"],
|
|
1530
|
+
// Required by Rule #7 — approving starts a real refund, which is money
|
|
1531
|
+
// leaving the platform and is not undoable from this screen. Its absence
|
|
1532
|
+
// also CRASHED /admin/return-requests outright: the view reads
|
|
1533
|
+
// `confirmation!.title`, so a missing block threw "Cannot read properties
|
|
1534
|
+
// of undefined (reading 'title')" during render and the page never painted.
|
|
1535
|
+
confirmation: {
|
|
1536
|
+
title: "Approve return request?",
|
|
1537
|
+
body: "The order will be marked Refunded, the buyer will be notified, and the refund process will begin. This cannot be undone from here.",
|
|
1538
|
+
confirmLabel: "Approve return",
|
|
1539
|
+
confirmKind: "primary",
|
|
1540
|
+
},
|
|
1530
1541
|
},
|
|
1531
1542
|
"reject-return": {
|
|
1532
1543
|
iconKey: "reject",
|
|
@@ -1,181 +1,20 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
export declare const productInputSchema: z.ZodObject<{
|
|
3
|
-
|
|
4
|
-
description: z.ZodOptional<z.ZodString>;
|
|
5
|
-
category: z.ZodString;
|
|
6
|
-
brandSlug: z.ZodOptional<z.ZodString>;
|
|
7
|
-
price: z.ZodNumber;
|
|
8
|
-
currency: z.ZodDefault<z.ZodString>;
|
|
9
|
-
stockQuantity: z.ZodDefault<z.ZodNumber>;
|
|
10
|
-
availableQuantity: z.ZodDefault<z.ZodNumber>;
|
|
11
|
-
mainImage: z.ZodEffects<z.ZodString, string, string>;
|
|
12
|
-
images: z.ZodDefault<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
13
|
-
condition: z.ZodOptional<z.ZodEnum<["new", "like_new", "good", "fair", "poor", "used", "refurbished", "broken"]>>;
|
|
14
|
-
tags: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
15
|
-
seoTitle: z.ZodOptional<z.ZodString>;
|
|
16
|
-
seoDescription: z.ZodOptional<z.ZodString>;
|
|
17
|
-
seoKeywords: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
18
|
-
shippingInfo: z.ZodOptional<z.ZodString>;
|
|
19
|
-
returnPolicy: z.ZodOptional<z.ZodString>;
|
|
20
|
-
finalSale: z.ZodOptional<z.ZodBoolean>;
|
|
21
|
-
shippingPaidBy: z.ZodOptional<z.ZodEnum<["seller", "buyer"]>>;
|
|
22
|
-
insurance: z.ZodOptional<z.ZodBoolean>;
|
|
23
|
-
insuranceCost: z.ZodOptional<z.ZodNumber>;
|
|
24
|
-
allowOffers: z.ZodOptional<z.ZodBoolean>;
|
|
25
|
-
minOfferPercent: z.ZodOptional<z.ZodNumber>;
|
|
26
|
-
features: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
2
|
+
export declare const productInputSchema: z.ZodObject<{} & {
|
|
3
|
+
[x: string]: z.ZodOptional<z.ZodUnknown>;
|
|
27
4
|
}, "strip", z.ZodTypeAny, {
|
|
28
|
-
|
|
29
|
-
title: string;
|
|
30
|
-
price: number;
|
|
31
|
-
category: string;
|
|
32
|
-
stockQuantity: number;
|
|
33
|
-
availableQuantity: number;
|
|
34
|
-
mainImage: string;
|
|
35
|
-
images: string[];
|
|
36
|
-
tags: string[];
|
|
37
|
-
brandSlug?: string | undefined;
|
|
38
|
-
features?: string[] | undefined;
|
|
39
|
-
description?: string | undefined;
|
|
40
|
-
shippingInfo?: string | undefined;
|
|
41
|
-
returnPolicy?: string | undefined;
|
|
42
|
-
condition?: "new" | "used" | "refurbished" | "like_new" | "good" | "fair" | "poor" | "broken" | undefined;
|
|
43
|
-
insurance?: boolean | undefined;
|
|
44
|
-
insuranceCost?: number | undefined;
|
|
45
|
-
shippingPaidBy?: "buyer" | "seller" | undefined;
|
|
46
|
-
seoTitle?: string | undefined;
|
|
47
|
-
seoDescription?: string | undefined;
|
|
48
|
-
seoKeywords?: string[] | undefined;
|
|
49
|
-
finalSale?: boolean | undefined;
|
|
50
|
-
allowOffers?: boolean | undefined;
|
|
51
|
-
minOfferPercent?: number | undefined;
|
|
5
|
+
[x: string]: unknown;
|
|
52
6
|
}, {
|
|
53
|
-
|
|
54
|
-
price: number;
|
|
55
|
-
category: string;
|
|
56
|
-
mainImage: string;
|
|
57
|
-
currency?: string | undefined;
|
|
58
|
-
brandSlug?: string | undefined;
|
|
59
|
-
stockQuantity?: number | undefined;
|
|
60
|
-
features?: string[] | undefined;
|
|
61
|
-
description?: string | undefined;
|
|
62
|
-
availableQuantity?: number | undefined;
|
|
63
|
-
images?: string[] | undefined;
|
|
64
|
-
tags?: string[] | undefined;
|
|
65
|
-
shippingInfo?: string | undefined;
|
|
66
|
-
returnPolicy?: string | undefined;
|
|
67
|
-
condition?: "new" | "used" | "refurbished" | "like_new" | "good" | "fair" | "poor" | "broken" | undefined;
|
|
68
|
-
insurance?: boolean | undefined;
|
|
69
|
-
insuranceCost?: number | undefined;
|
|
70
|
-
shippingPaidBy?: "buyer" | "seller" | undefined;
|
|
71
|
-
seoTitle?: string | undefined;
|
|
72
|
-
seoDescription?: string | undefined;
|
|
73
|
-
seoKeywords?: string[] | undefined;
|
|
74
|
-
finalSale?: boolean | undefined;
|
|
75
|
-
allowOffers?: boolean | undefined;
|
|
76
|
-
minOfferPercent?: number | undefined;
|
|
7
|
+
[x: string]: unknown;
|
|
77
8
|
}>;
|
|
78
9
|
export declare const productUpdateSchema: z.ZodObject<{
|
|
79
|
-
|
|
80
|
-
description: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
81
|
-
category: z.ZodOptional<z.ZodString>;
|
|
82
|
-
brandSlug: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
83
|
-
price: z.ZodOptional<z.ZodNumber>;
|
|
84
|
-
currency: z.ZodOptional<z.ZodDefault<z.ZodString>>;
|
|
85
|
-
stockQuantity: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
|
|
86
|
-
availableQuantity: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
|
|
87
|
-
mainImage: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
88
|
-
images: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>>;
|
|
89
|
-
condition: z.ZodOptional<z.ZodOptional<z.ZodEnum<["new", "like_new", "good", "fair", "poor", "used", "refurbished", "broken"]>>>;
|
|
90
|
-
tags: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString, "many">>>;
|
|
91
|
-
seoTitle: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
92
|
-
seoDescription: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
93
|
-
seoKeywords: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>;
|
|
94
|
-
shippingInfo: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
95
|
-
returnPolicy: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
96
|
-
finalSale: z.ZodOptional<z.ZodOptional<z.ZodBoolean>>;
|
|
97
|
-
shippingPaidBy: z.ZodOptional<z.ZodOptional<z.ZodEnum<["seller", "buyer"]>>>;
|
|
98
|
-
insurance: z.ZodOptional<z.ZodOptional<z.ZodBoolean>>;
|
|
99
|
-
insuranceCost: z.ZodOptional<z.ZodOptional<z.ZodNumber>>;
|
|
100
|
-
allowOffers: z.ZodOptional<z.ZodOptional<z.ZodBoolean>>;
|
|
101
|
-
minOfferPercent: z.ZodOptional<z.ZodOptional<z.ZodNumber>>;
|
|
102
|
-
features: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>;
|
|
10
|
+
[x: string]: z.ZodOptional<z.ZodOptional<z.ZodUnknown>>;
|
|
103
11
|
}, "strip", z.ZodTypeAny, {
|
|
104
|
-
|
|
105
|
-
title?: string | undefined;
|
|
106
|
-
price?: number | undefined;
|
|
107
|
-
category?: string | undefined;
|
|
108
|
-
brandSlug?: string | undefined;
|
|
109
|
-
stockQuantity?: number | undefined;
|
|
110
|
-
features?: string[] | undefined;
|
|
111
|
-
description?: string | undefined;
|
|
112
|
-
availableQuantity?: number | undefined;
|
|
113
|
-
mainImage?: string | undefined;
|
|
114
|
-
images?: string[] | undefined;
|
|
115
|
-
tags?: string[] | undefined;
|
|
116
|
-
shippingInfo?: string | undefined;
|
|
117
|
-
returnPolicy?: string | undefined;
|
|
118
|
-
condition?: "new" | "used" | "refurbished" | "like_new" | "good" | "fair" | "poor" | "broken" | undefined;
|
|
119
|
-
insurance?: boolean | undefined;
|
|
120
|
-
insuranceCost?: number | undefined;
|
|
121
|
-
shippingPaidBy?: "buyer" | "seller" | undefined;
|
|
122
|
-
seoTitle?: string | undefined;
|
|
123
|
-
seoDescription?: string | undefined;
|
|
124
|
-
seoKeywords?: string[] | undefined;
|
|
125
|
-
finalSale?: boolean | undefined;
|
|
126
|
-
allowOffers?: boolean | undefined;
|
|
127
|
-
minOfferPercent?: number | undefined;
|
|
12
|
+
[x: string]: unknown;
|
|
128
13
|
}, {
|
|
129
|
-
|
|
130
|
-
title?: string | undefined;
|
|
131
|
-
price?: number | undefined;
|
|
132
|
-
category?: string | undefined;
|
|
133
|
-
brandSlug?: string | undefined;
|
|
134
|
-
stockQuantity?: number | undefined;
|
|
135
|
-
features?: string[] | undefined;
|
|
136
|
-
description?: string | undefined;
|
|
137
|
-
availableQuantity?: number | undefined;
|
|
138
|
-
mainImage?: string | undefined;
|
|
139
|
-
images?: string[] | undefined;
|
|
140
|
-
tags?: string[] | undefined;
|
|
141
|
-
shippingInfo?: string | undefined;
|
|
142
|
-
returnPolicy?: string | undefined;
|
|
143
|
-
condition?: "new" | "used" | "refurbished" | "like_new" | "good" | "fair" | "poor" | "broken" | undefined;
|
|
144
|
-
insurance?: boolean | undefined;
|
|
145
|
-
insuranceCost?: number | undefined;
|
|
146
|
-
shippingPaidBy?: "buyer" | "seller" | undefined;
|
|
147
|
-
seoTitle?: string | undefined;
|
|
148
|
-
seoDescription?: string | undefined;
|
|
149
|
-
seoKeywords?: string[] | undefined;
|
|
150
|
-
finalSale?: boolean | undefined;
|
|
151
|
-
allowOffers?: boolean | undefined;
|
|
152
|
-
minOfferPercent?: number | undefined;
|
|
14
|
+
[x: string]: unknown;
|
|
153
15
|
}>;
|
|
154
16
|
export declare const auctionInputSchema: z.ZodObject<{
|
|
155
|
-
|
|
156
|
-
description: z.ZodOptional<z.ZodString>;
|
|
157
|
-
category: z.ZodString;
|
|
158
|
-
brandSlug: z.ZodOptional<z.ZodString>;
|
|
159
|
-
price: z.ZodNumber;
|
|
160
|
-
currency: z.ZodDefault<z.ZodString>;
|
|
161
|
-
stockQuantity: z.ZodDefault<z.ZodNumber>;
|
|
162
|
-
availableQuantity: z.ZodDefault<z.ZodNumber>;
|
|
163
|
-
mainImage: z.ZodEffects<z.ZodString, string, string>;
|
|
164
|
-
images: z.ZodDefault<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
165
|
-
condition: z.ZodOptional<z.ZodEnum<["new", "like_new", "good", "fair", "poor", "used", "refurbished", "broken"]>>;
|
|
166
|
-
tags: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
167
|
-
seoTitle: z.ZodOptional<z.ZodString>;
|
|
168
|
-
seoDescription: z.ZodOptional<z.ZodString>;
|
|
169
|
-
seoKeywords: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
170
|
-
shippingInfo: z.ZodOptional<z.ZodString>;
|
|
171
|
-
returnPolicy: z.ZodOptional<z.ZodString>;
|
|
172
|
-
finalSale: z.ZodOptional<z.ZodBoolean>;
|
|
173
|
-
shippingPaidBy: z.ZodOptional<z.ZodEnum<["seller", "buyer"]>>;
|
|
174
|
-
insurance: z.ZodOptional<z.ZodBoolean>;
|
|
175
|
-
insuranceCost: z.ZodOptional<z.ZodNumber>;
|
|
176
|
-
allowOffers: z.ZodOptional<z.ZodBoolean>;
|
|
177
|
-
minOfferPercent: z.ZodOptional<z.ZodNumber>;
|
|
178
|
-
features: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
17
|
+
[x: string]: z.ZodOptional<z.ZodUnknown>;
|
|
179
18
|
} & {
|
|
180
19
|
listingType: z.ZodLiteral<"auction">;
|
|
181
20
|
startingBid: z.ZodNumber;
|
|
@@ -187,99 +26,30 @@ export declare const auctionInputSchema: z.ZodObject<{
|
|
|
187
26
|
auctionExtensionMinutes: z.ZodOptional<z.ZodNumber>;
|
|
188
27
|
auctionShippingPaidBy: z.ZodOptional<z.ZodEnum<["seller", "winner"]>>;
|
|
189
28
|
}, "strip", z.ZodTypeAny, {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
auctionEndDate: string;
|
|
201
|
-
startingBid: number;
|
|
202
|
-
autoExtendable: boolean;
|
|
203
|
-
brandSlug?: string | undefined;
|
|
204
|
-
features?: string[] | undefined;
|
|
205
|
-
description?: string | undefined;
|
|
206
|
-
shippingInfo?: string | undefined;
|
|
207
|
-
returnPolicy?: string | undefined;
|
|
208
|
-
condition?: "new" | "used" | "refurbished" | "like_new" | "good" | "fair" | "poor" | "broken" | undefined;
|
|
209
|
-
insurance?: boolean | undefined;
|
|
210
|
-
insuranceCost?: number | undefined;
|
|
211
|
-
shippingPaidBy?: "buyer" | "seller" | undefined;
|
|
212
|
-
reservePrice?: number | undefined;
|
|
213
|
-
buyNowPrice?: number | undefined;
|
|
214
|
-
minBidIncrement?: number | undefined;
|
|
215
|
-
auctionExtensionMinutes?: number | undefined;
|
|
216
|
-
auctionShippingPaidBy?: "seller" | "winner" | undefined;
|
|
217
|
-
seoTitle?: string | undefined;
|
|
218
|
-
seoDescription?: string | undefined;
|
|
219
|
-
seoKeywords?: string[] | undefined;
|
|
220
|
-
finalSale?: boolean | undefined;
|
|
221
|
-
allowOffers?: boolean | undefined;
|
|
222
|
-
minOfferPercent?: number | undefined;
|
|
29
|
+
[x: string]: unknown;
|
|
30
|
+
listingType?: unknown;
|
|
31
|
+
startingBid?: unknown;
|
|
32
|
+
reservePrice?: unknown;
|
|
33
|
+
buyNowPrice?: unknown;
|
|
34
|
+
minBidIncrement?: unknown;
|
|
35
|
+
auctionEndDate?: unknown;
|
|
36
|
+
autoExtendable?: unknown;
|
|
37
|
+
auctionExtensionMinutes?: unknown;
|
|
38
|
+
auctionShippingPaidBy?: unknown;
|
|
223
39
|
}, {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
features?: string[] | undefined;
|
|
235
|
-
description?: string | undefined;
|
|
236
|
-
availableQuantity?: number | undefined;
|
|
237
|
-
images?: string[] | undefined;
|
|
238
|
-
tags?: string[] | undefined;
|
|
239
|
-
shippingInfo?: string | undefined;
|
|
240
|
-
returnPolicy?: string | undefined;
|
|
241
|
-
condition?: "new" | "used" | "refurbished" | "like_new" | "good" | "fair" | "poor" | "broken" | undefined;
|
|
242
|
-
insurance?: boolean | undefined;
|
|
243
|
-
insuranceCost?: number | undefined;
|
|
244
|
-
shippingPaidBy?: "buyer" | "seller" | undefined;
|
|
245
|
-
reservePrice?: number | undefined;
|
|
246
|
-
buyNowPrice?: number | undefined;
|
|
247
|
-
minBidIncrement?: number | undefined;
|
|
248
|
-
autoExtendable?: boolean | undefined;
|
|
249
|
-
auctionExtensionMinutes?: number | undefined;
|
|
250
|
-
auctionShippingPaidBy?: "seller" | "winner" | undefined;
|
|
251
|
-
seoTitle?: string | undefined;
|
|
252
|
-
seoDescription?: string | undefined;
|
|
253
|
-
seoKeywords?: string[] | undefined;
|
|
254
|
-
finalSale?: boolean | undefined;
|
|
255
|
-
allowOffers?: boolean | undefined;
|
|
256
|
-
minOfferPercent?: number | undefined;
|
|
40
|
+
[x: string]: unknown;
|
|
41
|
+
listingType?: unknown;
|
|
42
|
+
startingBid?: unknown;
|
|
43
|
+
reservePrice?: unknown;
|
|
44
|
+
buyNowPrice?: unknown;
|
|
45
|
+
minBidIncrement?: unknown;
|
|
46
|
+
auctionEndDate?: unknown;
|
|
47
|
+
autoExtendable?: unknown;
|
|
48
|
+
auctionExtensionMinutes?: unknown;
|
|
49
|
+
auctionShippingPaidBy?: unknown;
|
|
257
50
|
}>;
|
|
258
51
|
export declare const preOrderInputSchema: z.ZodObject<{
|
|
259
|
-
|
|
260
|
-
description: z.ZodOptional<z.ZodString>;
|
|
261
|
-
category: z.ZodString;
|
|
262
|
-
brandSlug: z.ZodOptional<z.ZodString>;
|
|
263
|
-
price: z.ZodNumber;
|
|
264
|
-
currency: z.ZodDefault<z.ZodString>;
|
|
265
|
-
stockQuantity: z.ZodDefault<z.ZodNumber>;
|
|
266
|
-
availableQuantity: z.ZodDefault<z.ZodNumber>;
|
|
267
|
-
mainImage: z.ZodEffects<z.ZodString, string, string>;
|
|
268
|
-
images: z.ZodDefault<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
269
|
-
condition: z.ZodOptional<z.ZodEnum<["new", "like_new", "good", "fair", "poor", "used", "refurbished", "broken"]>>;
|
|
270
|
-
tags: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
271
|
-
seoTitle: z.ZodOptional<z.ZodString>;
|
|
272
|
-
seoDescription: z.ZodOptional<z.ZodString>;
|
|
273
|
-
seoKeywords: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
274
|
-
shippingInfo: z.ZodOptional<z.ZodString>;
|
|
275
|
-
returnPolicy: z.ZodOptional<z.ZodString>;
|
|
276
|
-
finalSale: z.ZodOptional<z.ZodBoolean>;
|
|
277
|
-
shippingPaidBy: z.ZodOptional<z.ZodEnum<["seller", "buyer"]>>;
|
|
278
|
-
insurance: z.ZodOptional<z.ZodBoolean>;
|
|
279
|
-
insuranceCost: z.ZodOptional<z.ZodNumber>;
|
|
280
|
-
allowOffers: z.ZodOptional<z.ZodBoolean>;
|
|
281
|
-
minOfferPercent: z.ZodOptional<z.ZodNumber>;
|
|
282
|
-
features: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
52
|
+
[x: string]: z.ZodOptional<z.ZodUnknown>;
|
|
283
53
|
} & {
|
|
284
54
|
listingType: z.ZodLiteral<"pre-order">;
|
|
285
55
|
preOrderDeliveryDate: z.ZodString;
|
|
@@ -288,67 +58,21 @@ export declare const preOrderInputSchema: z.ZodObject<{
|
|
|
288
58
|
preOrderProductionStatus: z.ZodDefault<z.ZodEnum<["upcoming", "in_production", "ready_to_ship"]>>;
|
|
289
59
|
preOrderCancellable: z.ZodDefault<z.ZodBoolean>;
|
|
290
60
|
}, "strip", z.ZodTypeAny, {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
mainImage: string;
|
|
299
|
-
images: string[];
|
|
300
|
-
tags: string[];
|
|
301
|
-
preOrderDeliveryDate: string;
|
|
302
|
-
preOrderProductionStatus: "upcoming" | "in_production" | "ready_to_ship";
|
|
303
|
-
preOrderCancellable: boolean;
|
|
304
|
-
brandSlug?: string | undefined;
|
|
305
|
-
features?: string[] | undefined;
|
|
306
|
-
description?: string | undefined;
|
|
307
|
-
shippingInfo?: string | undefined;
|
|
308
|
-
returnPolicy?: string | undefined;
|
|
309
|
-
condition?: "new" | "used" | "refurbished" | "like_new" | "good" | "fair" | "poor" | "broken" | undefined;
|
|
310
|
-
insurance?: boolean | undefined;
|
|
311
|
-
insuranceCost?: number | undefined;
|
|
312
|
-
shippingPaidBy?: "buyer" | "seller" | undefined;
|
|
313
|
-
preOrderDepositPercent?: number | undefined;
|
|
314
|
-
preOrderMaxQuantity?: number | undefined;
|
|
315
|
-
seoTitle?: string | undefined;
|
|
316
|
-
seoDescription?: string | undefined;
|
|
317
|
-
seoKeywords?: string[] | undefined;
|
|
318
|
-
finalSale?: boolean | undefined;
|
|
319
|
-
allowOffers?: boolean | undefined;
|
|
320
|
-
minOfferPercent?: number | undefined;
|
|
61
|
+
[x: string]: unknown;
|
|
62
|
+
listingType?: unknown;
|
|
63
|
+
preOrderDeliveryDate?: unknown;
|
|
64
|
+
preOrderDepositPercent?: unknown;
|
|
65
|
+
preOrderMaxQuantity?: unknown;
|
|
66
|
+
preOrderProductionStatus?: unknown;
|
|
67
|
+
preOrderCancellable?: unknown;
|
|
321
68
|
}, {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
brandSlug?: string | undefined;
|
|
330
|
-
stockQuantity?: number | undefined;
|
|
331
|
-
features?: string[] | undefined;
|
|
332
|
-
description?: string | undefined;
|
|
333
|
-
availableQuantity?: number | undefined;
|
|
334
|
-
images?: string[] | undefined;
|
|
335
|
-
tags?: string[] | undefined;
|
|
336
|
-
shippingInfo?: string | undefined;
|
|
337
|
-
returnPolicy?: string | undefined;
|
|
338
|
-
condition?: "new" | "used" | "refurbished" | "like_new" | "good" | "fair" | "poor" | "broken" | undefined;
|
|
339
|
-
insurance?: boolean | undefined;
|
|
340
|
-
insuranceCost?: number | undefined;
|
|
341
|
-
shippingPaidBy?: "buyer" | "seller" | undefined;
|
|
342
|
-
preOrderDepositPercent?: number | undefined;
|
|
343
|
-
preOrderMaxQuantity?: number | undefined;
|
|
344
|
-
preOrderProductionStatus?: "upcoming" | "in_production" | "ready_to_ship" | undefined;
|
|
345
|
-
preOrderCancellable?: boolean | undefined;
|
|
346
|
-
seoTitle?: string | undefined;
|
|
347
|
-
seoDescription?: string | undefined;
|
|
348
|
-
seoKeywords?: string[] | undefined;
|
|
349
|
-
finalSale?: boolean | undefined;
|
|
350
|
-
allowOffers?: boolean | undefined;
|
|
351
|
-
minOfferPercent?: number | undefined;
|
|
69
|
+
[x: string]: unknown;
|
|
70
|
+
listingType?: unknown;
|
|
71
|
+
preOrderDeliveryDate?: unknown;
|
|
72
|
+
preOrderDepositPercent?: unknown;
|
|
73
|
+
preOrderMaxQuantity?: unknown;
|
|
74
|
+
preOrderProductionStatus?: unknown;
|
|
75
|
+
preOrderCancellable?: unknown;
|
|
352
76
|
}>;
|
|
353
77
|
export declare const setFeaturedSchema: z.ZodObject<{
|
|
354
78
|
productId: z.ZodString;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { mediaUrlSchema } from "../../../../validation/schemas";
|
|
3
3
|
import { PRODUCT_MAX_IMAGES } from "../../media/limits";
|
|
4
|
+
import { PRODUCT_UPDATABLE_FIELDS } from "../../../../features/products/schemas/firestore";
|
|
4
5
|
const priceSchema = z.number().min(0, "Price must be non-negative");
|
|
5
6
|
const conditionSchema = z.enum(["new", "like_new", "good", "fair", "poor", "used", "refurbished", "broken"]);
|
|
6
|
-
|
|
7
|
+
const productBaseShape = z.object({
|
|
7
8
|
title: z.string().min(3, "Title must be at least 3 characters").max(200),
|
|
8
9
|
description: z.string().max(10000).optional(),
|
|
9
10
|
category: z.string().min(1, "Category is required"),
|
|
@@ -30,6 +31,44 @@ export const productInputSchema = z.object({
|
|
|
30
31
|
minOfferPercent: z.number().min(0).max(100).optional(),
|
|
31
32
|
features: z.array(z.string()).max(50).optional(),
|
|
32
33
|
});
|
|
34
|
+
/**
|
|
35
|
+
* Every other field a seller legitimately owns on their own listing.
|
|
36
|
+
*
|
|
37
|
+
* 🛑 Declared, never `.passthrough()`. `sellerUpdateProduct` hands its parsed
|
|
38
|
+
* input straight to `productRepository.updateProduct` with no field filter, so
|
|
39
|
+
* passthrough would let a seller forge `currentBid` / `bidCount` / `viewCount`
|
|
40
|
+
* or reassign `storeId` to another store. The three sibling product-update
|
|
41
|
+
* routes DO use `.passthrough()`; they get away with it because they are
|
|
42
|
+
* admin-gated. This one is not.
|
|
43
|
+
*
|
|
44
|
+
* Derived from `PRODUCT_UPDATABLE_FIELDS` — the codebase's existing declaration
|
|
45
|
+
* of what a seller may change — so the two cannot drift (Root Cause #61).
|
|
46
|
+
*
|
|
47
|
+
* Why this exists: `productInputSchema` declared 24 fields while that constant
|
|
48
|
+
* lists 55, and `z.object()` SILENTLY STRIPS whatever it does not declare. The
|
|
49
|
+
* parse SUCCEEDED, so nothing surfaced anywhere — the seller saw a success
|
|
50
|
+
* toast and 38 fields were discarded on the way to Firestore, including
|
|
51
|
+
* `status` (so Publish wrote nothing at all), `listingType`, and all four
|
|
52
|
+
* per-type blocks that `draftToProductInput` had just carefully assembled.
|
|
53
|
+
* Update-side sibling of Root Cause #101.
|
|
54
|
+
*/
|
|
55
|
+
const EXTRA_WRITABLE_FIELDS = [
|
|
56
|
+
...PRODUCT_UPDATABLE_FIELDS,
|
|
57
|
+
// Absent from that constant, but plainly the seller's own listing data —
|
|
58
|
+
// their media, their tax codes. Omitting them is an oversight in the list,
|
|
59
|
+
// not a policy: there is no integrity risk in a seller setting either.
|
|
60
|
+
"video",
|
|
61
|
+
"gstRate",
|
|
62
|
+
"hsnCode",
|
|
63
|
+
// Auction terms. Writable while the listing is still a draft; once a real
|
|
64
|
+
// bid exists, `sellerUpdateProduct` refuses them — changing the starting bid
|
|
65
|
+
// or the end date under a live bidder is the one case that must not pass.
|
|
66
|
+
"startingBid",
|
|
67
|
+
"auctionEndDate",
|
|
68
|
+
];
|
|
69
|
+
const declaredKeys = new Set(Object.keys(productBaseShape.shape));
|
|
70
|
+
const extraWritableShape = Object.fromEntries(EXTRA_WRITABLE_FIELDS.filter((field) => !declaredKeys.has(field)).map((field) => [field, z.unknown().optional()]));
|
|
71
|
+
export const productInputSchema = productBaseShape.extend(extraWritableShape);
|
|
33
72
|
export const productUpdateSchema = productInputSchema.partial();
|
|
34
73
|
export const auctionInputSchema = productInputSchema.extend({
|
|
35
74
|
// SB1-G — canonical discriminator. Locked to "auction" via literal.
|
|
@@ -37,6 +37,18 @@ export type AllowedImageMime = (typeof ALLOWED_IMAGE_MIMES)[number];
|
|
|
37
37
|
export type AllowedVideoMime = (typeof ALLOWED_VIDEO_MIMES)[number];
|
|
38
38
|
export type AllowedDocMime = (typeof ALLOWED_DOC_MIMES)[number];
|
|
39
39
|
export type AllowedMime = (typeof ALLOWED_MIMES)[number];
|
|
40
|
+
/**
|
|
41
|
+
* The `accept` attribute for an image picker.
|
|
42
|
+
*
|
|
43
|
+
* 🛑 Never `image/*` — that wildcard matches `image/svg+xml`, which
|
|
44
|
+
* `ALLOWED_IMAGE_MIMES` deliberately excludes as an XSS surface. The server
|
|
45
|
+
* does reject it (`classifyMime` returns null and `/api/media/finalize` 422s),
|
|
46
|
+
* but only AFTER the picker has accepted it and the crop editor has opened on
|
|
47
|
+
* it — so the user frames and crops an image that was never going to upload,
|
|
48
|
+
* and the refusal arrives at the end looking like a failure rather than a rule.
|
|
49
|
+
* Deriving the attribute from the allowlist makes the picker refuse it first.
|
|
50
|
+
*/
|
|
51
|
+
export declare const IMAGE_ACCEPT_ATTR: string;
|
|
40
52
|
export declare const ALLOWED_TYPES_LABEL = "JPEG, PNG, GIF, WebP, MP4, WebM, QuickTime, 3GP, MKV, PDF";
|
|
41
53
|
export declare const MIME_TO_EXT: Record<string, string>;
|
|
42
54
|
export declare function classifyMime(mime: string): MediaKind | null;
|
|
@@ -76,6 +76,18 @@ export const ALLOWED_MIMES = [
|
|
|
76
76
|
...ALLOWED_VIDEO_MIMES,
|
|
77
77
|
...ALLOWED_DOC_MIMES,
|
|
78
78
|
];
|
|
79
|
+
/**
|
|
80
|
+
* The `accept` attribute for an image picker.
|
|
81
|
+
*
|
|
82
|
+
* 🛑 Never `image/*` — that wildcard matches `image/svg+xml`, which
|
|
83
|
+
* `ALLOWED_IMAGE_MIMES` deliberately excludes as an XSS surface. The server
|
|
84
|
+
* does reject it (`classifyMime` returns null and `/api/media/finalize` 422s),
|
|
85
|
+
* but only AFTER the picker has accepted it and the crop editor has opened on
|
|
86
|
+
* it — so the user frames and crops an image that was never going to upload,
|
|
87
|
+
* and the refusal arrives at the end looking like a failure rather than a rule.
|
|
88
|
+
* Deriving the attribute from the allowlist makes the picker refuse it first.
|
|
89
|
+
*/
|
|
90
|
+
export const IMAGE_ACCEPT_ATTR = ALLOWED_IMAGE_MIMES.join(",");
|
|
79
91
|
export const ALLOWED_TYPES_LABEL = "JPEG, PNG, GIF, WebP, MP4, WebM, QuickTime, 3GP, MKV, PDF";
|
|
80
92
|
// Server-detected MIME → canonical filename extension. The browser-supplied
|
|
81
93
|
// File.type is untrusted; the server picks the ext from the detected MIME.
|
package/dist/client.d.ts
CHANGED
|
@@ -402,7 +402,7 @@ export type { ListingTypeCapability } from "./_internal/shared/listing-types/cap
|
|
|
402
402
|
export { LISTING_TYPE_REGISTRY, pluginFor, detectListingTypeFromSlug } from "./_internal/shared/listing-types/_registry";
|
|
403
403
|
export type { ListingTypePlugin } from "./_internal/shared/listing-types/_registry";
|
|
404
404
|
export type { ListingType } from "./features/products/types/index";
|
|
405
|
-
export { MEGABYTE, MAX_IMAGE_BYTES, MAX_PDF_BYTES, MAX_VIDEO_BYTES, MAX_LABEL, MAX_BYTES, ALLOWED_IMAGE_MIMES, ALLOWED_VIDEO_MIMES, ALLOWED_DOC_MIMES, ALLOWED_MIMES, ALLOWED_TYPES_LABEL, MIME_TO_EXT, PDF_MAGIC, VIDEO_CONVERSION_HINTS, classifyMime, isAllowedMime, maxBytesFor, getConversionHint, PRODUCT_MAX_IMAGES, PRODUCT_MAX_VIDEOS, PRODUCT_IMAGE_INDEX_MAX, } from "./_internal/shared/media/limits";
|
|
405
|
+
export { MEGABYTE, MAX_IMAGE_BYTES, MAX_PDF_BYTES, MAX_VIDEO_BYTES, MAX_LABEL, MAX_BYTES, ALLOWED_IMAGE_MIMES, IMAGE_ACCEPT_ATTR, ALLOWED_VIDEO_MIMES, ALLOWED_DOC_MIMES, ALLOWED_MIMES, ALLOWED_TYPES_LABEL, MIME_TO_EXT, PDF_MAGIC, VIDEO_CONVERSION_HINTS, classifyMime, isAllowedMime, maxBytesFor, getConversionHint, PRODUCT_MAX_IMAGES, PRODUCT_MAX_VIDEOS, PRODUCT_IMAGE_INDEX_MAX, } from "./_internal/shared/media/limits";
|
|
406
406
|
export type { MediaKind, AllowedImageMime, AllowedVideoMime, AllowedDocMime, AllowedMime, } from "./_internal/shared/media/limits";
|
|
407
407
|
export { ScamAwarenessModal } from "./features/scams/components/ScamAwarenessModal";
|
|
408
408
|
export type { ScamAwarenessModalProps } from "./features/scams/components/ScamAwarenessModal";
|
package/dist/client.js
CHANGED
|
@@ -416,7 +416,7 @@ export { DashboardLayoutClient, RoleGuard } from "./_internal/client/features/la
|
|
|
416
416
|
export { LISTING_TYPE_CAPABILITIES, capabilityFor, canAddToCart, canBid, supportsShipping, requiresVendorVerified, requiresJurisdictionCheck, hasInstantFulfillment, assertNever, } from "./_internal/shared/listing-types/capabilities";
|
|
417
417
|
export { LISTING_TYPE_REGISTRY, pluginFor, detectListingTypeFromSlug } from "./_internal/shared/listing-types/_registry";
|
|
418
418
|
// Media upload limits — shared by client uploaders + server sign/finalize routes.
|
|
419
|
-
export { MEGABYTE, MAX_IMAGE_BYTES, MAX_PDF_BYTES, MAX_VIDEO_BYTES, MAX_LABEL, MAX_BYTES, ALLOWED_IMAGE_MIMES, ALLOWED_VIDEO_MIMES, ALLOWED_DOC_MIMES, ALLOWED_MIMES, ALLOWED_TYPES_LABEL, MIME_TO_EXT, PDF_MAGIC, VIDEO_CONVERSION_HINTS, classifyMime, isAllowedMime, maxBytesFor, getConversionHint, PRODUCT_MAX_IMAGES, PRODUCT_MAX_VIDEOS, PRODUCT_IMAGE_INDEX_MAX, } from "./_internal/shared/media/limits";
|
|
419
|
+
export { MEGABYTE, MAX_IMAGE_BYTES, MAX_PDF_BYTES, MAX_VIDEO_BYTES, MAX_LABEL, MAX_BYTES, ALLOWED_IMAGE_MIMES, IMAGE_ACCEPT_ATTR, ALLOWED_VIDEO_MIMES, ALLOWED_DOC_MIMES, ALLOWED_MIMES, ALLOWED_TYPES_LABEL, MIME_TO_EXT, PDF_MAGIC, VIDEO_CONVERSION_HINTS, classifyMime, isAllowedMime, maxBytesFor, getConversionHint, PRODUCT_MAX_IMAGES, PRODUCT_MAX_VIDEOS, PRODUCT_IMAGE_INDEX_MAX, } from "./_internal/shared/media/limits";
|
|
420
420
|
export { ScamAwarenessModal } from "./features/scams/components/ScamAwarenessModal";
|
|
421
421
|
export { SellerTrustBadge } from "./features/scams/components/SellerTrustBadge";
|
|
422
422
|
// [CLIENT-ONLY] — Admin panel components for action/nav permission management.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { IMAGE_ACCEPT_ATTR } from "../../../_internal/shared/media/limits";
|
|
3
4
|
import { Div, Input, Label, RichTextEditor, Select, Stack, TagInput, Text, Textarea, } from "../../../ui";
|
|
4
5
|
import { Checkbox } from "../../forms/index";
|
|
5
6
|
import { MediaUploadField, MediaUploadList } from "../../media/index";
|
|
@@ -11,7 +12,7 @@ function renderBlogPostMetaFields(props) {
|
|
|
11
12
|
}
|
|
12
13
|
function renderBlogPostMediaFields(props) {
|
|
13
14
|
const { update, isReadonly, labels, onUploadCover, onUploadContentImage, onUploadAdditionalImage, onAbort, coverImageUrl, contentImages, additionalImages } = props;
|
|
14
|
-
return (_jsxs(_Fragment, { children: [_jsx(MediaUploadField, { label: labels.coverImage, value: coverImageUrl || "", onChange: (url) => update({ coverImage: url ? { url, type: "image" } : null }), onChangeField: (media) => update({ coverImage: media }), onUpload: onUploadCover, onAbort: onAbort, accept:
|
|
15
|
+
return (_jsxs(_Fragment, { children: [_jsx(MediaUploadField, { label: labels.coverImage, value: coverImageUrl || "", onChange: (url) => update({ coverImage: url ? { url, type: "image" } : null }), onChangeField: (media) => update({ coverImage: media }), onUpload: onUploadCover, onAbort: onAbort, accept: IMAGE_ACCEPT_ATTR, maxSizeMB: 10, disabled: isReadonly, helperText: labels.coverImageHelper }), _jsx(MediaUploadList, { label: labels.contentImages, value: contentImages, onChange: (media) => update({ contentImages: media }), onUpload: onUploadContentImage, onAbort: onAbort, accept: "image/*,video/*", maxItems: 10, maxSizeMB: 10, disabled: isReadonly, helperText: labels.contentImagesHelper }), _jsx(MediaUploadList, { label: labels.additionalImages, value: additionalImages, onChange: (media) => update({ additionalImages: media }), onUpload: onUploadAdditionalImage, onAbort: onAbort, accept: "image/*,video/*", maxItems: 5, maxSizeMB: 10, disabled: isReadonly, helperText: labels.additionalImagesHelper }), isReadonly && coverImageUrl && _jsx(Text, { size: "xs", variant: "secondary", children: coverImageUrl })] }));
|
|
15
16
|
}
|
|
16
17
|
export function BlogPostForm({ value, onChange, categoryOptions, statusOptions, labels, onUploadCover, onUploadContentImage, onUploadAdditionalImage, onAbort, isReadonly = false, renderContentField, renderContentReadonly, }) {
|
|
17
18
|
const coverImageUrl = getMediaUrl(value.coverImage) ?? null;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { IMAGE_ACCEPT_ATTR } from "../../../_internal/shared/media/limits";
|
|
3
4
|
/**
|
|
4
5
|
* Add or edit an item in a user's personal catalogue.
|
|
5
6
|
*
|
|
@@ -82,7 +83,7 @@ export function CatalogueItemEditorView({ item, onSaved }) {
|
|
|
82
83
|
// `mainImage` is derived, so it moves with the gallery rather
|
|
83
84
|
// than pointing at a photo the user has since removed.
|
|
84
85
|
onChange({ images: urls, mainImage: urls[0] ?? "" });
|
|
85
|
-
}, onUpload: handleUpload, accept:
|
|
86
|
+
}, onUpload: handleUpload, accept: IMAGE_ACCEPT_ATTR, maxItems: 8, helperText: "Photos older than 30 days must be refreshed before this item can be listed." })),
|
|
86
87
|
categorySlugs: ({ values, onChange }) => (
|
|
87
88
|
// Explicit `<string>`: with `multiple`, `value` is `V[]`, so an
|
|
88
89
|
// inferred V reads the array itself as the option type.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { IMAGE_ACCEPT_ATTR } from "../../../_internal/shared/media/limits";
|
|
3
4
|
import { normalizeError } from "../../../errors/normalize";
|
|
4
5
|
import { useEffect, useState, useRef } from "react";
|
|
5
6
|
import { z } from "zod";
|
|
@@ -159,7 +160,7 @@ export function CharacterHotspotForm({ initial, onUploadImage, onSave, onAfterSa
|
|
|
159
160
|
className: "mt-1 text-center text-[10px]", style: { color: "var(--color-muted)" }, children: s.label })] }, s.key))) }), step === "image" && (_jsxs(Div, { className: CLS_STEP, style: {
|
|
160
161
|
borderColor: "var(--border-ink)",
|
|
161
162
|
background: "var(--surface-elevated)",
|
|
162
|
-
}, children: [_jsx(Heading, { level: 2, size: "lg", weight: "bold", children: "Upload Background Image" }), _jsx(Text, { style: { color: "var(--color-muted)" }, size: "sm", children: "Choose a wide panoramic image that shows all the characters. You will place pins on it in the next step." }), _jsxs(Label, { layout: "flex-col-center", gap: "md", rounded: "lg", padding: "xl", className: "cursor-pointer border-2 border-dashed transition-colors hover:opacity-80", style: { borderColor: "var(--border-ink)" }, children: [_jsx(Span, { size: "3xl", children: "\uD83D\uDDBC" }), _jsx(Span, { weight: "medium", children: uploading ? "Uploading…" : "Click to choose image" }), _jsx(Span, { size: "xs", style: { color: "var(--color-muted)" }, children: "JPG, PNG, WebP \u2014 wide landscape images work best" }), _jsx(Input, { type: "file", accept:
|
|
163
|
+
}, children: [_jsx(Heading, { level: 2, size: "lg", weight: "bold", children: "Upload Background Image" }), _jsx(Text, { style: { color: "var(--color-muted)" }, size: "sm", children: "Choose a wide panoramic image that shows all the characters. You will place pins on it in the next step." }), _jsxs(Label, { layout: "flex-col-center", gap: "md", rounded: "lg", padding: "xl", className: "cursor-pointer border-2 border-dashed transition-colors hover:opacity-80", style: { borderColor: "var(--border-ink)" }, children: [_jsx(Span, { size: "3xl", children: "\uD83D\uDDBC" }), _jsx(Span, { weight: "medium", children: uploading ? "Uploading…" : "Click to choose image" }), _jsx(Span, { size: "xs", style: { color: "var(--color-muted)" }, children: "JPG, PNG, WebP \u2014 wide landscape images work best" }), _jsx(Input, { type: "file", accept: IMAGE_ACCEPT_ATTR, className: "hidden", onChange: handleImageUpload, disabled: uploading })] }), imageUrl && (_jsxs(Stack, { gap: "xs", children: [_jsx(Div, { className: CLS_IMG_WRAP, style: { paddingTop: "37.5%" }, children: _jsx(Image, { src: resolveMediaUrl(imageUrl), alt: imageAlt, fill: true, className: "object-cover", sizes: "680px" }) }), _jsx(Text, { className: CLS_SUCCESS_TEXT, children: "\u2713 Image uploaded" })] })), _jsxs(Stack, { gap: "xs", children: [_jsx(Label, { size: "sm", weight: "bold", children: "Image Alt Text" }), _jsx(Input, { type: "text", value: imageAlt, onChange: (e) => setImageAlt(e.target.value), placeholder: "DC, Marvel and Anime characters", className: CLS_INPUT, style: {
|
|
163
164
|
borderColor: "var(--border-ink)",
|
|
164
165
|
background: "var(--surface-elevated)",
|
|
165
166
|
color: "var(--color-black)",
|
|
@@ -319,7 +320,7 @@ export function CharacterHotspotForm({ initial, onUploadImage, onSave, onAfterSa
|
|
|
319
320
|
}, children: "+" }), pin.name && (_jsx(Div, { textWeight: "bold", className: "pointer-events-none absolute left-8 top-1/2 -translate-y-1/2 whitespace-nowrap text-[9px] text-white", rounded: "default", style: { paddingInline: "0.375rem", paddingBlock: "0.125rem", background: "black" }, children: pin.name }))] }, pin.id)))] }), pins.length > 0 ? (_jsx(Ul, { rounded: "lg", className: "divide-y border", style: { borderColor: "var(--border-ink)" }, children: pins.map((pin, i) => (_jsx(Li, { padding: "inline", children: _jsxs(Row, { gap: "sm", children: [_jsx(Span, { layout: "flex-center", color: "inverse", className: "h-6 w-6 shrink-0", rounded: "full", size: "xs", weight: "bold", style: { background: pin.accent || DEFAULT_ACCENT_HEX }, children: i + 1 }), _jsxs(Div, { className: "min-w-0 flex-1", children: [_jsx(Text, { className: "truncate", size: "sm", weight: "bold", children: pin.name || (_jsx(Span, { className: "italic", style: { color: "var(--color-muted)" }, children: "Unnamed" })) }), _jsxs(Text, { style: { color: "var(--color-muted)" }, size: "xs", children: [pin.universe, " \u00B7 ", pin.xPct.toFixed(0), "%,", " ", pin.yPct.toFixed(0), "%"] })] }), _jsx(Button, { type: "button", onClick: () => deletePin(pin.id), variant: "ghost", className: CLS_DELETE_PIN, title: "Remove pin", children: "\u2715" })] }) }, pin.id))) })) : (_jsx(Div, { textSize: "sm", className: "border-2 border-dashed text-left", rounded: "lg", padding: "xl", style: {
|
|
320
321
|
borderColor: "var(--border-ink)",
|
|
321
322
|
color: "var(--color-muted)",
|
|
322
|
-
}, children: "No pins yet \u2014 add some using the button below." })), _jsxs(Details, { rounded: "lg", padding: "md", className: "border", style: { borderColor: "var(--border-ink)" }, children: [_jsx(Summary, { size: "sm", weight: "medium", children: "Image Settings" }), _jsxs(Stack, { className: "mt-3", gap: "3", children: [_jsxs(Label, { layout: "inline-flex", gap: "md", rounded: "default", padding: "toolbar", className: "cursor-pointer border-2 border-dashed transition-colors hover:opacity-80", size: "sm", weight: "medium", style: { borderColor: "var(--border-ink)" }, children: [uploading ? "Uploading…" : "Replace Image", _jsx(Input, { type: "file", accept:
|
|
323
|
+
}, children: "No pins yet \u2014 add some using the button below." })), _jsxs(Details, { rounded: "lg", padding: "md", className: "border", style: { borderColor: "var(--border-ink)" }, children: [_jsx(Summary, { size: "sm", weight: "medium", children: "Image Settings" }), _jsxs(Stack, { className: "mt-3", gap: "3", children: [_jsxs(Label, { layout: "inline-flex", gap: "md", rounded: "default", padding: "toolbar", className: "cursor-pointer border-2 border-dashed transition-colors hover:opacity-80", size: "sm", weight: "medium", style: { borderColor: "var(--border-ink)" }, children: [uploading ? "Uploading…" : "Replace Image", _jsx(Input, { type: "file", accept: IMAGE_ACCEPT_ATTR, className: "hidden", onChange: handleImageUpload, disabled: uploading })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Label, { size: "sm", weight: "bold", children: "Image Alt Text" }), _jsx(Input, { type: "text", value: imageAlt, onChange: (e) => setImageAlt(e.target.value), className: CLS_INPUT, style: {
|
|
323
324
|
borderColor: "var(--border-ink)",
|
|
324
325
|
background: "var(--surface-elevated)",
|
|
325
326
|
color: "var(--color-black)",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { IMAGE_ACCEPT_ATTR } from "../../_internal/shared/media/limits";
|
|
3
4
|
import { useEffect, useRef, useState } from "react";
|
|
4
5
|
import { Alert, Button, Div, Input, Progress, Row, Stack, Text, useToast } from "../../ui";
|
|
5
6
|
import { useMediaUpload } from "./hooks/useMedia";
|
|
@@ -131,6 +132,6 @@ export function AvatarUpload({ currentPhotoURL, currentCropData, userId, display
|
|
|
131
132
|
fileInputRef.current.value = "";
|
|
132
133
|
};
|
|
133
134
|
const hasPending = pendingCropData !== null && pendingUploadFile !== null;
|
|
134
|
-
return (_jsxs(_Fragment, { children: [_jsxs(Stack, { gap: "md", children: [_jsxs(Row, { align: "start", gap: "lg", children: [_jsx(Div, { className: "shrink-0", children: _jsx(AvatarDisplay, { cropData: hasPending ? pendingCropData : cropData, size: "2xl", className: `border-4 ${hasPending ? "border-primary/60 ring-2 ring-primary/20" : "border-[var(--appkit-color-border)]"}`, displayName: displayName }) }), _jsxs(Stack, { className: "flex-1", gap: "3", children: [_jsx(Text, { variant: "secondary", size: "xs", children: "JPG, PNG, WEBP or GIF up to 10MB." }), isUploading ? (_jsx(Progress, { value: 50, variant: "primary", size: "sm", label: t.uploading })) : null, hasPending && !isUploading ? (_jsxs(Stack, { gap: "sm", children: [_jsx(Text, { variant: "secondary", className: "text-primary", size: "xs", children: t.readyToSave }), _jsxs(Row, { gap: "3", children: [_jsx(Button, { type: "button", onClick: handleConfirmSave, size: "sm", children: t.saveAvatar }), _jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: handleCancelPending, children: t.cancelChange })] })] })) : null, !hasPending && !isUploading ? (_jsxs(Row, { gap: "3", children: [_jsx(Button, { type: "button", onClick: () => fileInputRef.current?.click(), size: "sm", children: previewUrl ? t.changePhoto : t.chooseImage }), previewUrl ? (_jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: handleRemovePhoto, children: t.removePhoto })) : null] })) : null, _jsx(Input, { ref: fileInputRef, bare: true, type: "file", accept:
|
|
135
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Stack, { gap: "md", children: [_jsxs(Row, { align: "start", gap: "lg", children: [_jsx(Div, { className: "shrink-0", children: _jsx(AvatarDisplay, { cropData: hasPending ? pendingCropData : cropData, size: "2xl", className: `border-4 ${hasPending ? "border-primary/60 ring-2 ring-primary/20" : "border-[var(--appkit-color-border)]"}`, displayName: displayName }) }), _jsxs(Stack, { className: "flex-1", gap: "3", children: [_jsx(Text, { variant: "secondary", size: "xs", children: "JPG, PNG, WEBP or GIF up to 10MB." }), isUploading ? (_jsx(Progress, { value: 50, variant: "primary", size: "sm", label: t.uploading })) : null, hasPending && !isUploading ? (_jsxs(Stack, { gap: "sm", children: [_jsx(Text, { variant: "secondary", className: "text-primary", size: "xs", children: t.readyToSave }), _jsxs(Row, { gap: "3", children: [_jsx(Button, { type: "button", onClick: handleConfirmSave, size: "sm", children: t.saveAvatar }), _jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: handleCancelPending, children: t.cancelChange })] })] })) : null, !hasPending && !isUploading ? (_jsxs(Row, { gap: "3", children: [_jsx(Button, { type: "button", onClick: () => fileInputRef.current?.click(), size: "sm", children: previewUrl ? t.changePhoto : t.chooseImage }), previewUrl ? (_jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: handleRemovePhoto, children: t.removePhoto })) : null] })) : null, _jsx(Input, { ref: fileInputRef, bare: true, type: "file", accept: IMAGE_ACCEPT_ATTR, onChange: handleFileSelect, className: "hidden", "aria-label": t.changePhoto })] })] }), uploadApiError ? (_jsx(Alert, { variant: "error", children: uploadApiError.message })) : null] }), showCropModal && tempImageUrl ? (_jsx(ImageCropModal, { isOpen: showCropModal, imageUrl: tempImageUrl, onClose: handleCropCancel, onSave: handleCropSave, initialCropData: cropData || undefined })) : null] }));
|
|
135
136
|
}
|
|
136
137
|
export default AvatarUpload;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { IMAGE_ACCEPT_ATTR } from "../../../_internal/shared/media/limits";
|
|
3
4
|
import { normalizeError } from "../../../errors/normalize";
|
|
4
5
|
/**
|
|
5
6
|
* ImageUpload — canonical image upload component for @mohasinac/feat-media.
|
|
@@ -185,5 +186,5 @@ export function ImageUpload({ currentImage, onUpload, onChange, accept = "image/
|
|
|
185
186
|
};
|
|
186
187
|
return (_jsxs(Stack, { gap: "3", children: [label && (_jsx(Label, { className: "block", color: "muted", size: "sm", weight: "medium", children: label })), _jsx(Div, { className: "relative", children: preview ? (_jsxs(Stack, { gap: "sm", children: [_jsxs(Div, { className: `relative h-32 max-w-xs mx-auto ${__O.hidden}`, rounded: "lg", border: "default", children: [_jsx(MediaImage, { src: preview, alt: "Preview", size: "card" }), uploading && progress > 0 && (_jsx(Div, { className: "absolute inset-x-0 bottom-0", children: _jsx(Progress, { value: progress, size: "sm" }) }))] }), !uploading && (_jsxs(Row, { wrap: true, gap: "sm", children: [(enableCrop || enableAdvancedCrop) && (_jsx(Button, { type: "button", onClick: () => openEditor(preview), variant: "secondary", size: "sm", children: tMediaEditor("editImage") })), _jsx(Button, { type: "button", onClick: () => fileInputRef.current?.click(), variant: "outline", size: "sm", children: tUpload("change") }), _jsx(Button, { type: "button", onClick: handleRemove, variant: "danger", size: "sm", children: tUpload("remove") })] }))] })) : (_jsxs(_Fragment, { children: [showCamera && isCameraSupported && (_jsx(CameraCapture, { mode: "photo", facingMode: "environment", onCapture: handleCameraCapture, onError: setError })), !showCamera && (_jsxs(Stack, { gap: "sm", children: [_jsxs(Row, { gap: "sm", wrap: true, children: [showFileInput && (_jsx(Button, { type: "button", onClick: () => fileInputRef.current?.click(), disabled: uploading, variant: "outline", size: "sm", children: uploading ? tUpload("uploading") : tUpload("clickToUpload") })), captureSource !== "file-only" && isCameraSupported && (_jsx(Button, { type: "button", onClick: () => setCaptureMode("camera"), disabled: uploading, variant: "ghost", size: "sm", children: t("switchToCamera") })), captureSource !== "file-only" && !isCameraSupported && (_jsx(Button, { type: "button", onClick: () => mobileCaptureRef.current?.click(), disabled: uploading, variant: "ghost", size: "sm", children: t("switchToCamera") }))] }), _jsxs(Text, { size: "xs", variant: "secondary", children: [accept === "image/*"
|
|
187
188
|
? `JPG PNG GIF WebP`
|
|
188
|
-
: accept.split(",").map((a) => a.split("/")[1]?.toUpperCase() ?? a).join(" "), " ", "\u2014 max ", maxSizeMB, "MB"] })] }))] })) }), _jsx("input", { ref: fileInputRef, type: "file", accept: accept, onChange: handleFileChange, className: "hidden", "aria-hidden": "true", "data-testid": "media-upload-input" }), showCamera && !isCameraSupported && (_jsx("input", { ref: mobileCaptureRef, type: "file", accept:
|
|
189
|
+
: accept.split(",").map((a) => a.split("/")[1]?.toUpperCase() ?? a).join(" "), " ", "\u2014 max ", maxSizeMB, "MB"] })] }))] })) }), _jsx("input", { ref: fileInputRef, type: "file", accept: accept, onChange: handleFileChange, className: "hidden", "aria-hidden": "true", "data-testid": "media-upload-input" }), showCamera && !isCameraSupported && (_jsx("input", { ref: mobileCaptureRef, type: "file", accept: IMAGE_ACCEPT_ATTR, capture: "environment", onChange: handleFileChange, className: "hidden", "aria-hidden": "true", "data-testid": "media-upload-capture-input" })), helperText && !error && (_jsx(Text, { variant: "secondary", size: "xs", children: helperText })), error && _jsx(Alert, { variant: "error", children: error }), uploading && (_jsxs(Row, { gap: "sm", children: [_jsx(Spinner, { size: "sm" }), _jsx(Text, { size: "sm", variant: "secondary", children: tUpload("uploadingProgress", { progress }) })] })), cropPreviewUrl && !enableAdvancedCrop && (_jsx(ImageCropModal, { isOpen: showCropModal, imageUrl: cropPreviewUrl, onClose: handleCropClose, onSave: handleCropSave })), cropPreviewUrl && enableAdvancedCrop && (_jsx(ImageEditor, { isOpen: showAdvancedEditor, imageUrl: cropPreviewUrl, onClose: handleAdvancedEditorClose, onSave: handleAdvancedEditorSave, aspectRatio: cropAspectRatio }))] }));
|
|
189
190
|
}
|
|
@@ -17,7 +17,7 @@ import { userRepository } from "../../auth/repository/user.repository";
|
|
|
17
17
|
import { storeRepository } from "../../stores/repository/store.repository";
|
|
18
18
|
import { productRepository } from "../../products/repository/products.repository";
|
|
19
19
|
import { ProductStatusValues } from "../../products/schemas";
|
|
20
|
-
import { assertPrizeDrawNotLocked, assertPrizeDrawWonItemsImmutable, } from "../../../_internal/server/features/products/service";
|
|
20
|
+
import { assertPrizeDrawNotLocked, assertPrizeDrawWonItemsImmutable, assertAuctionTermsMutable, } from "../../../_internal/server/features/products/service";
|
|
21
21
|
import { orderRepository } from "../../orders/repository/orders.repository";
|
|
22
22
|
import { OrderStatusValues } from "../../orders/schemas";
|
|
23
23
|
import { payoutRepository } from "../../payments/repository/payout.repository";
|
|
@@ -561,6 +561,7 @@ export async function sellerUpdateProduct(userId, userRole, productId, input) {
|
|
|
561
561
|
assertPrizeDrawNotLocked(existing, "unpublished or archived");
|
|
562
562
|
}
|
|
563
563
|
assertPrizeDrawWonItemsImmutable(existing, input.prizeDrawItems);
|
|
564
|
+
assertAuctionTermsMutable(existing, input);
|
|
564
565
|
const finalizedData = await finalizeProductMediaReferences(input);
|
|
565
566
|
const updated = await productRepository.updateProduct(productId, finalizedData);
|
|
566
567
|
serverLogger.info("sellerUpdateProduct", { userId, productId });
|
package/dist/index.d.ts
CHANGED
|
@@ -3140,7 +3140,7 @@ export { LISTING_TYPE_REGISTRY, pluginFor, detectListingTypeFromSlug } from "./_
|
|
|
3140
3140
|
export type { ListingTypePlugin } from "./_internal/shared/listing-types/_registry";
|
|
3141
3141
|
export { getListingRule, getCategoryRule, pickOrderType, getSplitKey, runSyncPreflight, CHECKOUT_RULES, CATEGORY_CHECKOUT_RULES, CHECKOUT_MAX_ORDERS_PER_TX, PRIZE_DRAW_MAX_REVEALS_PER_ORDER, BUNDLE_MAX_QTY_PER_TX, STANDARD_MAX_QTY_PER_LINE, } from "./_internal/shared/checkout/rules";
|
|
3142
3142
|
export type { ListingCheckoutRule, CategoryCheckoutRule, CartItemProductPair, OrderItemInput, RefundPolicy, } from "./_internal/shared/checkout/rules";
|
|
3143
|
-
export { MEGABYTE, MAX_IMAGE_BYTES, MAX_PDF_BYTES, MAX_VIDEO_BYTES, MAX_LABEL, MAX_BYTES, ALLOWED_IMAGE_MIMES, ALLOWED_VIDEO_MIMES, ALLOWED_DOC_MIMES, ALLOWED_MIMES, ALLOWED_TYPES_LABEL, MIME_TO_EXT, PDF_MAGIC, VIDEO_CONVERSION_HINTS, classifyMime, isAllowedMime, maxBytesFor, getConversionHint, PRODUCT_MAX_IMAGES, PRODUCT_MAX_VIDEOS, PRODUCT_IMAGE_INDEX_MAX, } from "./_internal/shared/media/limits";
|
|
3143
|
+
export { MEGABYTE, MAX_IMAGE_BYTES, MAX_PDF_BYTES, MAX_VIDEO_BYTES, MAX_LABEL, MAX_BYTES, ALLOWED_IMAGE_MIMES, IMAGE_ACCEPT_ATTR, ALLOWED_VIDEO_MIMES, ALLOWED_DOC_MIMES, ALLOWED_MIMES, ALLOWED_TYPES_LABEL, MIME_TO_EXT, PDF_MAGIC, VIDEO_CONVERSION_HINTS, classifyMime, isAllowedMime, maxBytesFor, getConversionHint, PRODUCT_MAX_IMAGES, PRODUCT_MAX_VIDEOS, PRODUCT_IMAGE_INDEX_MAX, } from "./_internal/shared/media/limits";
|
|
3144
3144
|
export type { MediaKind, AllowedImageMime, AllowedVideoMime, AllowedDocMime, AllowedMime, } from "./_internal/shared/media/limits";
|
|
3145
3145
|
export { PRODUCT_FIELDS, PRODUCT_STATUS_TRANSITIONS, ORDER_FIELDS, REVIEW_FIELDS, BID_FIELDS, AD_FIELDS, AD_TRACKED_FIELDS, BLOG_FIELDS, BRAND_FIELDS, WISHLIST_FIELDS, HISTORY_FIELDS, COUPON_USAGE_FIELDS, CAROUSEL_FIELDS, FAQ_FIELDS, HOMEPAGE_SECTION_FIELDS, SITE_SETTINGS_FIELDS, COMMON_FIELDS, OAUTH_STATE_VALUES, SCHEMA_DEFAULTS, } from "./constants/field-names";
|
|
3146
3146
|
export { SORT_DIR, sortBy } from "./constants/sort";
|
package/dist/index.js
CHANGED
|
@@ -5509,7 +5509,7 @@ export { LISTING_TYPE_REGISTRY, pluginFor, detectListingTypeFromSlug } from "./_
|
|
|
5509
5509
|
// Checkout rule registry â€" pure TS, safe in both client and server bundles.
|
|
5510
5510
|
export { getListingRule, getCategoryRule, pickOrderType, getSplitKey, runSyncPreflight, CHECKOUT_RULES, CATEGORY_CHECKOUT_RULES, CHECKOUT_MAX_ORDERS_PER_TX, PRIZE_DRAW_MAX_REVEALS_PER_ORDER, BUNDLE_MAX_QTY_PER_TX, STANDARD_MAX_QTY_PER_LINE, } from "./_internal/shared/checkout/rules";
|
|
5511
5511
|
// Media upload limits â€" pure constants, safe in both client and server bundles.
|
|
5512
|
-
export { MEGABYTE, MAX_IMAGE_BYTES, MAX_PDF_BYTES, MAX_VIDEO_BYTES, MAX_LABEL, MAX_BYTES, ALLOWED_IMAGE_MIMES, ALLOWED_VIDEO_MIMES, ALLOWED_DOC_MIMES, ALLOWED_MIMES, ALLOWED_TYPES_LABEL, MIME_TO_EXT, PDF_MAGIC, VIDEO_CONVERSION_HINTS, classifyMime, isAllowedMime, maxBytesFor, getConversionHint, PRODUCT_MAX_IMAGES, PRODUCT_MAX_VIDEOS, PRODUCT_IMAGE_INDEX_MAX, } from "./_internal/shared/media/limits";
|
|
5512
|
+
export { MEGABYTE, MAX_IMAGE_BYTES, MAX_PDF_BYTES, MAX_VIDEO_BYTES, MAX_LABEL, MAX_BYTES, ALLOWED_IMAGE_MIMES, IMAGE_ACCEPT_ATTR, ALLOWED_VIDEO_MIMES, ALLOWED_DOC_MIMES, ALLOWED_MIMES, ALLOWED_TYPES_LABEL, MIME_TO_EXT, PDF_MAGIC, VIDEO_CONVERSION_HINTS, classifyMime, isAllowedMime, maxBytesFor, getConversionHint, PRODUCT_MAX_IMAGES, PRODUCT_MAX_VIDEOS, PRODUCT_IMAGE_INDEX_MAX, } from "./_internal/shared/media/limits";
|
|
5513
5513
|
// ── Field name constants — canonical source of truth for all Firestore field names ──
|
|
5514
5514
|
// NOTE: STORE_FIELDS, CATEGORY_FIELDS, EVENT_FIELDS, EVENT_ENTRY_FIELDS, PAYOUT_FIELDS,
|
|
5515
5515
|
// COUPON_FIELDS, CART_FIELDS, USER_FIELDS, ADDRESS_FIELDS, NOTIFICATION_FIELDS,
|
package/dist/server.d.ts
CHANGED
|
@@ -528,7 +528,7 @@ export { LISTING_TYPE_REGISTRY, pluginFor, detectListingTypeFromSlug } from "./_
|
|
|
528
528
|
export type { ListingTypePlugin } from "./_internal/shared/listing-types/_registry";
|
|
529
529
|
export { applyMediaContextGuards, CONTEXT_LIMITS, } from "./_internal/server/features/media/contextGuards";
|
|
530
530
|
export type { GuardResult, GuardError, GuardSuccess } from "./_internal/server/features/media/contextGuards";
|
|
531
|
-
export { MEGABYTE, MAX_IMAGE_BYTES, MAX_PDF_BYTES, MAX_VIDEO_BYTES, MAX_LABEL, MAX_BYTES, ALLOWED_IMAGE_MIMES, ALLOWED_VIDEO_MIMES, ALLOWED_DOC_MIMES, ALLOWED_MIMES, ALLOWED_TYPES_LABEL, MIME_TO_EXT, PDF_MAGIC, VIDEO_CONVERSION_HINTS, classifyMime, isAllowedMime, maxBytesFor, getConversionHint, PRODUCT_MAX_IMAGES, PRODUCT_MAX_VIDEOS, PRODUCT_IMAGE_INDEX_MAX, } from "./_internal/shared/media/limits";
|
|
531
|
+
export { MEGABYTE, MAX_IMAGE_BYTES, MAX_PDF_BYTES, MAX_VIDEO_BYTES, MAX_LABEL, MAX_BYTES, ALLOWED_IMAGE_MIMES, IMAGE_ACCEPT_ATTR, ALLOWED_VIDEO_MIMES, ALLOWED_DOC_MIMES, ALLOWED_MIMES, ALLOWED_TYPES_LABEL, MIME_TO_EXT, PDF_MAGIC, VIDEO_CONVERSION_HINTS, classifyMime, isAllowedMime, maxBytesFor, getConversionHint, PRODUCT_MAX_IMAGES, PRODUCT_MAX_VIDEOS, PRODUCT_IMAGE_INDEX_MAX, } from "./_internal/shared/media/limits";
|
|
532
532
|
export type { MediaKind, AllowedImageMime, AllowedVideoMime, AllowedDocMime, AllowedMime, } from "./_internal/shared/media/limits";
|
|
533
533
|
export { getServerPermissions, checkPermission, checkAnyPermission, makeAdminSectionLayout, } from "./_internal/server/features/auth/permissions";
|
|
534
534
|
export type { ResolvedPermissions, AdminSectionLayoutOpts, } from "./_internal/server/features/auth/permissions";
|
package/dist/server.js
CHANGED
|
@@ -1407,7 +1407,7 @@ export { LISTING_TYPE_REGISTRY, pluginFor, detectListingTypeFromSlug } from "./_
|
|
|
1407
1407
|
// Media context guards — shared by /api/media/sign + legacy /api/media/upload.
|
|
1408
1408
|
export { applyMediaContextGuards, CONTEXT_LIMITS, } from "./_internal/server/features/media/contextGuards";
|
|
1409
1409
|
// Media upload limits — shared by /api/media/sign + /api/media/finalize + /api/media/upload.
|
|
1410
|
-
export { MEGABYTE, MAX_IMAGE_BYTES, MAX_PDF_BYTES, MAX_VIDEO_BYTES, MAX_LABEL, MAX_BYTES, ALLOWED_IMAGE_MIMES, ALLOWED_VIDEO_MIMES, ALLOWED_DOC_MIMES, ALLOWED_MIMES, ALLOWED_TYPES_LABEL, MIME_TO_EXT, PDF_MAGIC, VIDEO_CONVERSION_HINTS, classifyMime, isAllowedMime, maxBytesFor, getConversionHint, PRODUCT_MAX_IMAGES, PRODUCT_MAX_VIDEOS, PRODUCT_IMAGE_INDEX_MAX, } from "./_internal/shared/media/limits";
|
|
1410
|
+
export { MEGABYTE, MAX_IMAGE_BYTES, MAX_PDF_BYTES, MAX_VIDEO_BYTES, MAX_LABEL, MAX_BYTES, ALLOWED_IMAGE_MIMES, IMAGE_ACCEPT_ATTR, ALLOWED_VIDEO_MIMES, ALLOWED_DOC_MIMES, ALLOWED_MIMES, ALLOWED_TYPES_LABEL, MIME_TO_EXT, PDF_MAGIC, VIDEO_CONVERSION_HINTS, classifyMime, isAllowedMime, maxBytesFor, getConversionHint, PRODUCT_MAX_IMAGES, PRODUCT_MAX_VIDEOS, PRODUCT_IMAGE_INDEX_MAX, } from "./_internal/shared/media/limits";
|
|
1411
1411
|
// ── RBAC server helpers ───────────────────────────────────────────────────────
|
|
1412
1412
|
// getServerPermissions, checkPermission, makeAdminSectionLayout
|
|
1413
1413
|
export { getServerPermissions, checkPermission, checkAnyPermission, makeAdminSectionLayout, } from "./_internal/server/features/auth/permissions";
|