@mohasinac/appkit 4.42.18 → 4.43.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/lottery/LotteryDetailView.js +22 -1
- package/dist/_internal/server/features/products/list-public.d.ts +24 -0
- package/dist/_internal/server/features/products/list-public.js +10 -2
- package/dist/_internal/server/jobs/core/onCategoryWrite.js +5 -0
- package/dist/_internal/server/jobs/core/onProductWrite.js +31 -0
- package/dist/_internal/server/jobs/core/onReviewWrite.js +8 -0
- package/dist/_internal/server/jobs/core/onStoreWrite.d.ts +1 -1
- package/dist/_internal/server/jobs/core/onStoreWrite.js +8 -2
- package/dist/_internal/server/jobs/core/revalidateNotify.d.ts +28 -0
- package/dist/_internal/server/jobs/core/revalidateNotify.js +125 -0
- package/dist/_internal/shared/cache/revalidate-targets.d.ts +49 -0
- package/dist/_internal/shared/cache/revalidate-targets.js +157 -0
- package/dist/features/auctions/components/AuctionBottomActions.js +10 -1
- package/dist/features/auctions/components/AuctionDetailPageView.js +2 -1
- package/dist/features/auctions/components/AuctionLiveStatusBadge.d.ts +20 -0
- package/dist/features/auctions/components/AuctionLiveStatusBadge.js +21 -0
- package/dist/features/auctions/components/PlaceBidFormClient.d.ts +1 -1
- package/dist/features/auctions/components/PlaceBidFormClient.js +20 -2
- package/dist/features/auctions/hooks/useIsAuctionEnded.d.ts +41 -0
- package/dist/features/auctions/hooks/useIsAuctionEnded.js +76 -0
- package/dist/features/blog/api/route.js +16 -1
- package/dist/features/categories/actions/category-actions.d.ts +2 -2
- package/dist/features/categories/actions/category-actions.js +22 -10
- package/dist/features/categories/components/CategoriesIndexListing.d.ts +1 -3
- package/dist/features/categories/components/CategoriesIndexListing.js +1 -1
- package/dist/features/categories/components/CategoriesIndexPageView.d.ts +27 -1
- package/dist/features/categories/components/CategoriesIndexPageView.js +24 -33
- package/dist/features/categories/repository/categories.repository.d.ts +11 -1
- package/dist/features/categories/repository/categories.repository.js +25 -7
- package/dist/features/faq/utils/getHomepageFaqsCached.d.ts +20 -0
- package/dist/features/faq/utils/getHomepageFaqsCached.js +22 -0
- package/dist/features/homepage/components/MarketplaceHomepageView.js +13 -3
- package/dist/features/homepage/lib/live-stats.js +18 -8
- package/dist/features/media/MediaImage.js +19 -2
- package/dist/features/products/actions/product-actions.d.ts +0 -9
- package/dist/features/products/actions/product-actions.js +25 -0
- package/dist/features/products/components/ArtStickersListView.js +7 -1
- package/dist/features/products/components/PrizeDrawsSection.js +5 -0
- package/dist/features/products/components/ProductsIndexPageView.js +1 -1
- package/dist/features/reviews/repository/reviews.repository.d.ts +22 -0
- package/dist/features/reviews/repository/reviews.repository.js +35 -0
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +2 -2
- package/dist/seed/_helpers/media.d.ts +1 -1
- package/dist/seed/_helpers/media.js +44 -8
- package/dist/seed/orders-seed-data.js +5 -3
- package/dist/server.d.ts +2 -0
- package/dist/server.js +6 -0
- package/dist/utils/media-url.d.ts +26 -0
- package/dist/utils/media-url.js +144 -0
- package/package.json +1 -1
package/dist/utils/media-url.js
CHANGED
|
@@ -38,6 +38,122 @@ export const APPROVED_MEDIA_DOMAINS = [
|
|
|
38
38
|
"images.unsplash.com",
|
|
39
39
|
];
|
|
40
40
|
export const MEDIA_URL_MESSAGE = "Must be a stored media reference (/media/<slug>) or a URL on an approved CDN domain";
|
|
41
|
+
/* ── Placeholder imagery ─────────────────────────────────────────────────── */
|
|
42
|
+
/**
|
|
43
|
+
* Local, static stand-ins for seed photography. Served straight out of
|
|
44
|
+
* `public/` — no function, no upstream fetch, no sharp pipeline.
|
|
45
|
+
*
|
|
46
|
+
* 🛑 This is the single most load-bearing cost control in the app. Every seed
|
|
47
|
+
* image used to resolve to `/api/media/ext?url=https://placehold.co/…`, i.e. a
|
|
48
|
+
* Node lambda that fetched a third party (up to 2 × 4 s) and ran a full sharp
|
|
49
|
+
* decode/watermark/encode — ONE INVOCATION PER IMAGE. Measured 2026-10-09: the
|
|
50
|
+
* homepage referenced **160** such images at ~49.5 KB each, and
|
|
51
|
+
* `52.52 GB of Fast Origin Transfer ÷ 49.5 KB ≈ 1.06 M` proxy responses in a
|
|
52
|
+
* week. That is what suspended the project (HTTP 402).
|
|
53
|
+
*
|
|
54
|
+
* Six tiles, because the stored URL already encodes which of the six
|
|
55
|
+
* `SEED_PHOTO_COLOURS` it was generated with — so the mapping needs no new
|
|
56
|
+
* data and no migration. The homepage goes 160 images → 6.
|
|
57
|
+
*/
|
|
58
|
+
export const SEED_TILE_PREFIX = "/images/seed-tiles/";
|
|
59
|
+
/**
|
|
60
|
+
* Must stay in lockstep with `SEED_PHOTO_COLOURS` in
|
|
61
|
+
* `appkit/src/seed/_helpers/media.ts`, and with the filenames in
|
|
62
|
+
* `public/images/seed-tiles/`. A colour missing here is not an error — it falls
|
|
63
|
+
* back to a deterministic pick — so the failure mode is "less variety", never a
|
|
64
|
+
* broken image.
|
|
65
|
+
*/
|
|
66
|
+
const SEED_TILE_COLOURS = [
|
|
67
|
+
"1e293b",
|
|
68
|
+
"334155",
|
|
69
|
+
"3f3f46",
|
|
70
|
+
"44403c",
|
|
71
|
+
"312e81",
|
|
72
|
+
"164e63",
|
|
73
|
+
];
|
|
74
|
+
/**
|
|
75
|
+
* Hosts that only ever serve synthetic placeholder imagery.
|
|
76
|
+
*
|
|
77
|
+
* These are the four the seed helper documents having cycled through as each
|
|
78
|
+
* one fell over (picsum 503 on 2026-08-31, placekitten 521, loremflickr too
|
|
79
|
+
* slow for the proxy's 4 s timeout). Watermarking a generated placeholder is
|
|
80
|
+
* pointless, and proxying one costs a function invocation plus full origin
|
|
81
|
+
* transfer for an image we can synthesise locally for free.
|
|
82
|
+
*/
|
|
83
|
+
const PLACEHOLDER_MEDIA_HOSTS = new Set([
|
|
84
|
+
"placehold.co",
|
|
85
|
+
"placeholder.com",
|
|
86
|
+
"via.placeholder.com",
|
|
87
|
+
"picsum.photos",
|
|
88
|
+
"fastly.picsum.photos",
|
|
89
|
+
"placekitten.com",
|
|
90
|
+
"loremflickr.com",
|
|
91
|
+
"dummyimage.com",
|
|
92
|
+
]);
|
|
93
|
+
/** Deterministic 32-bit hash — same input, same tile, every render. */
|
|
94
|
+
function tileHash(input) {
|
|
95
|
+
let h = 2166136261;
|
|
96
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
97
|
+
h ^= input.charCodeAt(i);
|
|
98
|
+
h = Math.imul(h, 16777619);
|
|
99
|
+
}
|
|
100
|
+
return h >>> 0;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The inner `url=` of a stored `/api/media/ext?url=…` value, or null.
|
|
104
|
+
*
|
|
105
|
+
* This is what lets the fix reach data that is ALREADY in Firestore. `seedPhoto`
|
|
106
|
+
* persisted the wrapped form for ~400 assets, and `resolveMediaUrl` used to hand
|
|
107
|
+
* such a value straight back untouched — `new URL()` throws on the relative path
|
|
108
|
+
* and the `catch` returns the input — so the proxy was entered on every render.
|
|
109
|
+
* Unwrapping here fixes every stored row with no migration.
|
|
110
|
+
*/
|
|
111
|
+
function innerExtUrl(value) {
|
|
112
|
+
if (!value.startsWith(MEDIA_ENDPOINTS.EXT))
|
|
113
|
+
return null;
|
|
114
|
+
const q = value.indexOf("?");
|
|
115
|
+
if (q === -1)
|
|
116
|
+
return null;
|
|
117
|
+
try {
|
|
118
|
+
return new URLSearchParams(value.slice(q + 1)).get("url");
|
|
119
|
+
}
|
|
120
|
+
catch (_err) {
|
|
121
|
+
void normalizeError(_err);
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* A local tile for `absUrl` when it points at a placeholder host, else undefined.
|
|
127
|
+
*
|
|
128
|
+
* placehold.co bakes its colours into the path (`/{w}x{h}/{bg}/{fg}/png`), so the
|
|
129
|
+
* first 6-hex segment IS the background the fixture was generated with — the
|
|
130
|
+
* size segment contains an `x` and cannot match, and the foreground comes after
|
|
131
|
+
* the background, so `.find()` picks the right one.
|
|
132
|
+
*/
|
|
133
|
+
export function placeholderTileFor(absUrl) {
|
|
134
|
+
let parsed;
|
|
135
|
+
try {
|
|
136
|
+
parsed = new URL(absUrl);
|
|
137
|
+
}
|
|
138
|
+
catch (_err) {
|
|
139
|
+
void normalizeError(_err);
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
const host = parsed.hostname.replace(/^www\./, "");
|
|
143
|
+
if (!PLACEHOLDER_MEDIA_HOSTS.has(host))
|
|
144
|
+
return undefined;
|
|
145
|
+
const bg = parsed.pathname
|
|
146
|
+
.split("/")
|
|
147
|
+
.find((seg) => /^[0-9a-fA-F]{6}$/.test(seg))
|
|
148
|
+
?.toLowerCase();
|
|
149
|
+
if (bg && SEED_TILE_COLOURS.includes(bg))
|
|
150
|
+
return `${SEED_TILE_PREFIX}${bg}.svg`;
|
|
151
|
+
// Unknown or absent colour (picsum/placekitten carry none) — pick
|
|
152
|
+
// deterministically from the path so the same fixture always gets the same
|
|
153
|
+
// tile and the grid keeps its variety.
|
|
154
|
+
const fallback = SEED_TILE_COLOURS[tileHash(parsed.pathname) % SEED_TILE_COLOURS.length];
|
|
155
|
+
return `${SEED_TILE_PREFIX}${fallback}.svg`;
|
|
156
|
+
}
|
|
41
157
|
/**
|
|
42
158
|
* Is `value` something we are willing to PERSIST as a media reference?
|
|
43
159
|
*
|
|
@@ -95,6 +211,11 @@ export function isStoredMediaRef(value) {
|
|
|
95
211
|
return false;
|
|
96
212
|
if (value.startsWith(MEDIA_PROXY_PREFIX))
|
|
97
213
|
return true;
|
|
214
|
+
// A local seed tile (`/images/seed-tiles/<hex>.svg`). Persistable: it is a
|
|
215
|
+
// static asset we ship, so unlike the `/api/media/ext?url=` form rejected
|
|
216
|
+
// below it is not a render-time transform and does not double-wrap.
|
|
217
|
+
if (value.startsWith(SEED_TILE_PREFIX))
|
|
218
|
+
return true;
|
|
98
219
|
if (value.startsWith("blob:") || value.startsWith("data:"))
|
|
99
220
|
return false;
|
|
100
221
|
if (value.startsWith(MEDIA_ENDPOINTS.EXT))
|
|
@@ -148,6 +269,8 @@ export function resolveMediaUrl(url, opts) {
|
|
|
148
269
|
}
|
|
149
270
|
if (url.startsWith(PROXY_PREFIX))
|
|
150
271
|
return url;
|
|
272
|
+
if (url.startsWith(SEED_TILE_PREFIX))
|
|
273
|
+
return url;
|
|
151
274
|
// A blob:/data: URI is only ever valid in the tab that created it (e.g. a
|
|
152
275
|
// freshly-selected file preview via URL.createObjectURL, or a FileReader
|
|
153
276
|
// data URL fed to a crop modal). Routing it through the external-URL
|
|
@@ -155,8 +278,29 @@ export function resolveMediaUrl(url, opts) {
|
|
|
155
278
|
// rendered directly instead.
|
|
156
279
|
if (url.startsWith("blob:") || url.startsWith("data:"))
|
|
157
280
|
return url;
|
|
281
|
+
/*
|
|
282
|
+
* An ALREADY-WRAPPED `/api/media/ext?url=…` value, which is what the seed
|
|
283
|
+
* catalogue persisted ~400 times. Unwrap and look at what it actually points
|
|
284
|
+
* at: a placeholder host becomes a local tile (no function, no fetch), and
|
|
285
|
+
* anything else falls through to the old behaviour of returning the wrapped
|
|
286
|
+
* value untouched. Without this the branch below is unreachable for stored
|
|
287
|
+
* data, because `new URL()` throws on the relative path.
|
|
288
|
+
*/
|
|
289
|
+
const inner = innerExtUrl(url);
|
|
290
|
+
if (inner) {
|
|
291
|
+
const innerTile = placeholderTileFor(inner);
|
|
292
|
+
if (innerTile)
|
|
293
|
+
return innerTile;
|
|
294
|
+
return url;
|
|
295
|
+
}
|
|
158
296
|
try {
|
|
159
297
|
const parsed = new URL(url);
|
|
298
|
+
// Synthetic placeholder imagery: never worth a function invocation. The
|
|
299
|
+
// placeholder hosts and our own Storage hosts are disjoint sets, so this
|
|
300
|
+
// can sit before the own-bucket branches without shadowing a real asset.
|
|
301
|
+
const tile = placeholderTileFor(url);
|
|
302
|
+
if (tile)
|
|
303
|
+
return tile;
|
|
160
304
|
if (parsed.hostname.endsWith(FIREBASE_STORAGE_HOST)) {
|
|
161
305
|
const m = parsed.pathname.match(/\/o\/([^?]+)/);
|
|
162
306
|
if (m)
|