@mohasinac/appkit 4.55.0-next.0 → 4.55.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/.build-stamp.json +5 -5
- package/dist/_internal/server/features/checkout/actions.js +61 -2
- package/dist/_internal/shared/features/checkout/config.d.ts +1 -1
- package/dist/_internal/shared/features/checkout/config.js +1 -1
- package/dist/_internal/shared/features/site-settings/fees.d.ts +1 -1
- package/dist/_internal/shared/features/site-settings/fees.js +1 -1
- package/dist/_internal/shared/fees/calculator.d.ts +35 -3
- package/dist/_internal/shared/fees/calculator.js +34 -4
- package/dist/errors/error-codes.d.ts +15 -0
- package/dist/errors/error-codes.js +15 -0
- package/dist/errors/index.d.ts +1 -1
- package/dist/errors/index.js +1 -1
- package/dist/features/account/components/UserBidsView.js +4 -2
- package/dist/features/admin/components/AdminSiteSettingsView.js +18 -2
- package/dist/features/admin/schemas/firestore.d.ts +37 -2
- package/dist/features/admin/schemas/firestore.js +12 -1
- package/dist/features/cart/components/CartPriceBreakdown.js +1 -1
- package/dist/features/checkout/schemas/firestore.d.ts +12 -1
- package/dist/features/layout/NavbarLayout.js +139 -32
- package/dist/features/media/messages/en.json +67 -67
- package/dist/features/orders/schemas/firestore.d.ts +1 -1
- package/dist/features/products/components/ProductDetailPageView.js +2 -25
- package/dist/features/products/components/ProductOwnerQuickActions.d.ts +11 -0
- package/dist/features/products/components/ProductOwnerQuickActions.js +120 -0
- package/dist/features/products/components/ProductsIndexListing.js +35 -5
- package/dist/features/products/components/ProductsIndexPageView.js +40 -7
- package/dist/providers/shipping-shiprocket/index.d.ts +13 -4
- package/dist/providers/shipping-shiprocket/index.js +34 -10
- package/dist/security/csp.d.ts +1 -16
- package/dist/security/csp.js +3 -5
- package/dist/seed/faq-seed-data.js +1 -1
- package/dist/seed/site-settings-seed-data.js +1 -1
- package/dist/seo/json-ld.d.ts +8 -1
- package/dist/seo/json-ld.js +39 -2
- package/dist/styles.css +382 -537
- package/dist/tailwind-utilities.css +1 -1
- package/dist/tokens/themes/index.d.ts +1 -3
- package/dist/tokens/themes/index.js +1 -11
- package/dist/tokens/tokens.css +0 -155
- package/dist/ui/components/GatedPrice.style.css +43 -43
- package/dist/ui/components/QuickCreateModal.style.css +78 -78
- package/dist/ui/forms/FormShell.style.css +260 -260
- package/package.json +1 -1
- package/dist/tokens/themes/technical-dark.d.ts +0 -30
- package/dist/tokens/themes/technical-dark.js +0 -103
- package/dist/tokens/themes/technical-light.d.ts +0 -22
- package/dist/tokens/themes/technical-light.js +0 -89
|
@@ -10,6 +10,9 @@ import { GENERIC_PRODUCT_LISTING_TYPES } from "../constants/listing-tabs.js";
|
|
|
10
10
|
import { pluginFor } from "../../../_internal/shared/listing-types/_registry.js";
|
|
11
11
|
import { listPublicProducts, parsePublicProductParams, defaultAvailabilityForListingTypes, } from "../../../_internal/server/features/products/list-public.js";
|
|
12
12
|
import { parseSelectedListingTypes } from "../utils/listing-type.js";
|
|
13
|
+
import { isListingTypeEnabled } from "../../../_internal/shared/listing-types/feature-flags.js";
|
|
14
|
+
import { siteSettingsRepository } from "../../../repositories/index.js";
|
|
15
|
+
import { safeRead } from "../../../errors/safe-read.js";
|
|
13
16
|
const DEFAULT_PAGE_SIZE = 24;
|
|
14
17
|
const DEFAULT_SORT = sortBy(PRODUCT_FIELDS.CREATED_AT);
|
|
15
18
|
export async function ProductsIndexPageView({ searchParams = {} }) {
|
|
@@ -47,11 +50,41 @@ export async function ProductsIndexPageView({ searchParams = {} }) {
|
|
|
47
50
|
const effectiveTypes = selectedTypes.length > 0 ? selectedTypes : GENERIC_PRODUCT_LISTING_TYPES;
|
|
48
51
|
// See ArtStickersListView — one shared filter implementation with
|
|
49
52
|
// /api/products, so SSR and the client refetch agree by construction.
|
|
50
|
-
const products = await
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
const [products, settings] = await Promise.all([
|
|
54
|
+
listPublicProducts(parsePublicProductParams(searchParams, {
|
|
55
|
+
listingTypes: GENERIC_PRODUCT_LISTING_TYPES,
|
|
56
|
+
pageSize: DEFAULT_PAGE_SIZE,
|
|
57
|
+
sorts: DEFAULT_SORT,
|
|
58
|
+
...defaultAvailabilityForListingTypes(effectiveTypes),
|
|
59
|
+
})),
|
|
60
|
+
/*
|
|
61
|
+
* Read in parallel with the listing — the banner below is the only
|
|
62
|
+
* consumer, and serialising a settings read in front of the page's main
|
|
63
|
+
* query would spend a round trip against Rule #6's budget for one link.
|
|
64
|
+
*
|
|
65
|
+
* 🛑 `safeRead`, so a settings failure hides the banner rather than
|
|
66
|
+
* failing the catalogue. Auctions have their own nav entry and their own
|
|
67
|
+
* page; the banner is a shortcut, and a shortcut is the correct thing to
|
|
68
|
+
* lose when its gate cannot be evaluated.
|
|
69
|
+
*/
|
|
70
|
+
safeRead(() => siteSettingsRepository.findById("global"), {
|
|
71
|
+
route: "/products",
|
|
72
|
+
key: "products.siteSettings",
|
|
73
|
+
fallback: null,
|
|
74
|
+
}),
|
|
75
|
+
]);
|
|
76
|
+
/*
|
|
77
|
+
* 🛑 Auctions are a LISTING TYPE an admin can switch off, and this banner
|
|
78
|
+
* advertised them unconditionally.
|
|
79
|
+
*
|
|
80
|
+
* Every other auction entry point already respects the flag — the navbar
|
|
81
|
+
* child (filtered by key in `LayoutShellClient`), the footer link
|
|
82
|
+
* (`FOOTER_LINK_LISTING_TYPES`), the store tabs (`tabs.ts`), the
|
|
83
|
+
* category/brand tabs, and the homepage section (`gatedListing`). This was
|
|
84
|
+
* the one surface left promoting a page that renders nothing on a site with
|
|
85
|
+
* auctions disabled, which is Root Cause #37's dead-link shape reached from
|
|
86
|
+
* the other direction.
|
|
87
|
+
*/
|
|
88
|
+
const showAuctionsBanner = isListingTypeEnabled("auction", settings);
|
|
89
|
+
return (_jsxs(Main, { children: [_jsx(Section, { border: "bottom-subtle", paddingY: "b-md", padding: "t-xl", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 1, size: "3xl", weight: "bold", color: "primary", children: "Products" }), _jsx(Text, { className: "mt-1", color: "muted", size: "sm", children: "Discover amazing products and deals" }), showAuctionsBanner && (_jsx(Div, { className: "mt-3", children: _jsx(TextLink, { variant: "bare", href: String(ROUTES.PUBLIC.AUCTIONS), rounded: "full", paddingX: "sm", paddingY: "2xs", size: "xs", weight: "medium", layout: "inline-flex", align: "center", gap: "sm", className: "border border-primary/30 bg-primary/10 text-primary-700 dark:text-primary-400 hover:bg-primary/15 transition-colors", children: "\uD83C\uDFF7\uFE0F Looking for unique deals? Browse Auctions \u2192" }) }))] }) }), _jsxs(Container, { size: "xl", padding: "x-md", children: [_jsx(AdSlot, { id: "listing-sidebar-top", className: "mb-4 mt-4" }), _jsx(ProductsIndexListing, { initialData: products ?? undefined }), _jsx(AdSlot, { id: "listing-sidebar-bottom", className: "mt-8" })] })] }));
|
|
57
90
|
}
|
|
@@ -12,10 +12,19 @@
|
|
|
12
12
|
* `loginResponse` requires — the rest are ignored, as Zod ignores unknown
|
|
13
13
|
* keys by default.
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
15
|
+
* ✅ ALSO VERIFIED (2026-10-20): `GET /courier/serviceability/` from pickup
|
|
16
|
+
* `413001`. HTTP 200, and `data.available_courier_companies` came back with
|
|
17
|
+
* 10-11 entries for Mumbai, Bengaluru and Delhi carrying `courier_name`,
|
|
18
|
+
* `rate` and `estimated_delivery_days` — so `serviceabilityResponse` below
|
|
19
|
+
* is read off a real payload, not inferred. It is a read-only quote: no
|
|
20
|
+
* shipment is created and nothing is booked.
|
|
21
|
+
*
|
|
22
|
+
* 🛑 NOT VERIFIED: order creation, AWB assignment, tracking and label
|
|
23
|
+
* generation. Each MUTATES or bills a real carrier account — creating an
|
|
24
|
+
* order can schedule a pickup, an AWB can consume an allocation, a cancel
|
|
25
|
+
* may still bill — so none was called, and none is discoverable without
|
|
26
|
+
* incurring it. Their field names below are claims from the published docs,
|
|
27
|
+
* not facts read off a payload.
|
|
19
28
|
*
|
|
20
29
|
* That asymmetry is the reason every response is parsed through Zod rather
|
|
21
30
|
* than cast: an unverified contract that is wrong surfaces as a named error at
|
|
@@ -12,10 +12,19 @@
|
|
|
12
12
|
* `loginResponse` requires — the rest are ignored, as Zod ignores unknown
|
|
13
13
|
* keys by default.
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
15
|
+
* ✅ ALSO VERIFIED (2026-10-20): `GET /courier/serviceability/` from pickup
|
|
16
|
+
* `413001`. HTTP 200, and `data.available_courier_companies` came back with
|
|
17
|
+
* 10-11 entries for Mumbai, Bengaluru and Delhi carrying `courier_name`,
|
|
18
|
+
* `rate` and `estimated_delivery_days` — so `serviceabilityResponse` below
|
|
19
|
+
* is read off a real payload, not inferred. It is a read-only quote: no
|
|
20
|
+
* shipment is created and nothing is booked.
|
|
21
|
+
*
|
|
22
|
+
* 🛑 NOT VERIFIED: order creation, AWB assignment, tracking and label
|
|
23
|
+
* generation. Each MUTATES or bills a real carrier account — creating an
|
|
24
|
+
* order can schedule a pickup, an AWB can consume an allocation, a cancel
|
|
25
|
+
* may still bill — so none was called, and none is discoverable without
|
|
26
|
+
* incurring it. Their field names below are claims from the published docs,
|
|
27
|
+
* not facts read off a payload.
|
|
19
28
|
*
|
|
20
29
|
* That asymmetry is the reason every response is parsed through Zod rather
|
|
21
30
|
* than cast: an unverified contract that is wrong surfaces as a named error at
|
|
@@ -312,15 +321,30 @@ export class ShiprocketShippingProvider extends IShippingProvider {
|
|
|
312
321
|
}
|
|
313
322
|
async checkServiceability(pincode, weight) {
|
|
314
323
|
/*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
* "
|
|
319
|
-
*
|
|
324
|
+
* `pickup_postcode` is required by their API and comes from
|
|
325
|
+
* `SHIPROCKET_PICKUP_PINCODE`.
|
|
326
|
+
*
|
|
327
|
+
* 🛑 This comment used to say "we do not have it on this contract", which
|
|
328
|
+
* was wrong and cost the whole method: the consumer's `appkit.config.js →
|
|
329
|
+
* business.registeredAddress` has carried `postalCode` all along. Nothing
|
|
330
|
+
* set the var, so every call returned `{ isServiceable: false }` WITHOUT
|
|
331
|
+
* calling the API — checkout would have told every buyer we cannot ship to
|
|
332
|
+
* them, for every destination. A quote that is silently always "no" is
|
|
333
|
+
* indistinguishable from a carrier with no coverage, which is why it had no
|
|
334
|
+
* symptom. Verified live once the var was populated: 10-11 couriers quoted
|
|
335
|
+
* from 413001 to Mumbai, Bengaluru and Delhi.
|
|
336
|
+
*
|
|
337
|
+
* 🛑 appkit has no reader for a consumer config file, so the DEFAULT is the
|
|
338
|
+
* consumer's job: `src/providers.config.ts` populates this var from the
|
|
339
|
+
* business block at boot. Deliberately not threaded through
|
|
340
|
+
* `configurePlatformSupplier` — that seam's own 🛑 says it "is NOT a cache
|
|
341
|
+
* of site settings and must not become one"; it answers *who the invoice
|
|
342
|
+
* supplier is*, and a carrier pickup field there is mission creep on a seam
|
|
343
|
+
* with one stated purpose. One var, one reader, no precedence rule.
|
|
320
344
|
*/
|
|
321
345
|
const origin = process.env.SHIPROCKET_PICKUP_PINCODE?.trim();
|
|
322
346
|
if (!origin) {
|
|
323
|
-
serverLogger.warn("shiprocket:
|
|
347
|
+
serverLogger.warn("shiprocket: no pickup postcode — set business.registeredAddress.postalCode in appkit.config.js, or SHIPROCKET_PICKUP_PINCODE directly", { pincode });
|
|
324
348
|
return { isServiceable: false, couriers: [] };
|
|
325
349
|
}
|
|
326
350
|
try {
|
package/dist/security/csp.d.ts
CHANGED
|
@@ -12,20 +12,5 @@
|
|
|
12
12
|
*/
|
|
13
13
|
/** Generate a 16-byte base64 nonce for a single request. */
|
|
14
14
|
export declare function generateNonce(): string;
|
|
15
|
-
export interface BuildCSPOptions {
|
|
16
|
-
/**
|
|
17
|
-
* Add `'wasm-unsafe-eval'` to `script-src`, permitting
|
|
18
|
-
* `WebAssembly.compile`/`instantiate` from bytes.
|
|
19
|
-
*
|
|
20
|
-
* Required by any page that compiles WebAssembly at runtime — without it the
|
|
21
|
-
* production policy (`script-src 'self' 'nonce-…'`) blocks compilation
|
|
22
|
-
* outright, because WebAssembly compilation counts as script evaluation.
|
|
23
|
-
*
|
|
24
|
-
* Narrower than `'unsafe-eval'` on purpose: it unblocks WASM only and still
|
|
25
|
-
* forbids `eval` and `new Function` for JavaScript. Default `false`, so
|
|
26
|
-
* existing consumers are unaffected.
|
|
27
|
-
*/
|
|
28
|
-
wasm?: boolean;
|
|
29
|
-
}
|
|
30
15
|
/** Build the full CSP header value for the given nonce. */
|
|
31
|
-
export declare function buildCSP(nonce: string
|
|
16
|
+
export declare function buildCSP(nonce: string): string;
|
package/dist/security/csp.js
CHANGED
|
@@ -17,15 +17,13 @@ export function generateNonce() {
|
|
|
17
17
|
return btoa(String.fromCharCode(...bytes));
|
|
18
18
|
}
|
|
19
19
|
/** Build the full CSP header value for the given nonce. */
|
|
20
|
-
export function buildCSP(nonce
|
|
20
|
+
export function buildCSP(nonce) {
|
|
21
21
|
const isDev = process.env.NODE_ENV === "development";
|
|
22
|
-
const wasm = options.wasm === true ? " 'wasm-unsafe-eval'" : "";
|
|
23
22
|
return [
|
|
24
23
|
"default-src 'self'",
|
|
25
24
|
isDev
|
|
26
|
-
?
|
|
27
|
-
|
|
28
|
-
: `script-src 'self' 'nonce-${nonce}'${wasm}`,
|
|
25
|
+
? "script-src 'self' 'unsafe-eval' 'unsafe-inline'"
|
|
26
|
+
: `script-src 'self' 'nonce-${nonce}'`,
|
|
29
27
|
"style-src 'self' 'unsafe-inline'",
|
|
30
28
|
"img-src 'self' data: https: blob:",
|
|
31
29
|
"font-src 'self' data:",
|
|
@@ -525,7 +525,7 @@ const faqSeedRecords = [
|
|
|
525
525
|
{
|
|
526
526
|
id: "faq-emi-installments",
|
|
527
527
|
question: "Can I pay in installments (EMI)?",
|
|
528
|
-
answer: html(`<p>Yes — on eligible orders. If the seller has EMI enabled and your order with them meets the minimum order value, "Pay in EMI" appears as a payment option at checkout. Choose a tenure of 2–6 months, pay a small token amount upfront, and the remaining balance plus a small monthly surcharge is billed automatically in equal installments each month. Your order ships
|
|
528
|
+
answer: html(`<p>Yes — on eligible orders. If the seller has EMI enabled and your order with them meets the minimum order value, "Pay in EMI" appears as a payment option at checkout. Choose a tenure of 2–6 months, pay a small token amount upfront, and the remaining balance plus a small monthly surcharge is billed automatically in equal installments each month. Your order ships once the EMI is fully paid — the token confirms your commitment and secures the item, it is not a dispatch trigger. A seller may opt an individual listing into earlier dispatch, and that is stated on the listing.</p>`),
|
|
529
529
|
category: "orders_payment",
|
|
530
530
|
showOnHomepage: false,
|
|
531
531
|
showInFooter: false,
|
|
@@ -474,7 +474,7 @@ export const siteSettingsSeedData = {
|
|
|
474
474
|
minimumTransactionFee: 0,
|
|
475
475
|
gatewayFeePercent: 2,
|
|
476
476
|
codDepositPercent: 10,
|
|
477
|
-
|
|
477
|
+
codHandlingFeeCap: 200,
|
|
478
478
|
codHandlingFeePercent: 10,
|
|
479
479
|
// Enabled so a seeded install actually shows the add-ons — see the matching
|
|
480
480
|
// comment on DEFAULT_SITE_SETTINGS_DATA.commissions.
|
package/dist/seo/json-ld.d.ts
CHANGED
|
@@ -29,7 +29,14 @@ export interface ProductJsonLdInput {
|
|
|
29
29
|
price: number;
|
|
30
30
|
currency: string;
|
|
31
31
|
mainImage?: string;
|
|
32
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Prefer `productImageUrls(doc.images)`. The object form is accepted so a
|
|
34
|
+
* document holding `ProductImage[]` cannot take the page down — see the
|
|
35
|
+
* note on `imageUrlOf` in `productJsonLd`.
|
|
36
|
+
*/
|
|
37
|
+
images?: readonly (string | {
|
|
38
|
+
url?: string;
|
|
39
|
+
})[];
|
|
33
40
|
sellerName?: string;
|
|
34
41
|
category?: string;
|
|
35
42
|
status?: string;
|
package/dist/seo/json-ld.js
CHANGED
|
@@ -27,7 +27,27 @@ const SITE_NAME = process.env.NEXT_PUBLIC_SITE_NAME ?? "App";
|
|
|
27
27
|
* an empty value so `JSON.stringify` drops the key rather than emitting `""`.
|
|
28
28
|
*/
|
|
29
29
|
function absoluteUrl(value, base = SITE_URL) {
|
|
30
|
-
|
|
30
|
+
/*
|
|
31
|
+
* 🛑 `unknown` + a typeof test, NOT `string | null | undefined` + `?.trim()`.
|
|
32
|
+
*
|
|
33
|
+
* This took production down for hours. The signature said `string`, every
|
|
34
|
+
* caller type-checked, and then `ProductImage[]` replaced `string[]` on the
|
|
35
|
+
* product document — so `images.map(absoluteUrl)` received OBJECTS, `?.trim`
|
|
36
|
+
* was not a function, and the throw escaped into the RSC render and 500'd
|
|
37
|
+
* `/[locale]/products/[slug]` and `/[locale]/auctions/[id]`: **72 recorded
|
|
38
|
+
* failures**, the whole page lost to a decorative metadata field.
|
|
39
|
+
*
|
|
40
|
+
* A declared parameter type is a compile-time claim about CALLERS. It is
|
|
41
|
+
* worth nothing at the boundary where data arrives from Firestore, which is
|
|
42
|
+
* exactly where this function sits — and a schema migration is precisely the
|
|
43
|
+
* event that makes the claim false everywhere at once.
|
|
44
|
+
*
|
|
45
|
+
* So the contract is now behavioural: anything that is not a usable string
|
|
46
|
+
* is DROPPED, and the key is omitted. JSON-LD is optional enrichment —
|
|
47
|
+
* losing one image costs a rich-result thumbnail, while throwing costs the
|
|
48
|
+
* entire page. Those are not close.
|
|
49
|
+
*/
|
|
50
|
+
const v = typeof value === "string" ? value.trim() : undefined;
|
|
31
51
|
if (!v)
|
|
32
52
|
return undefined;
|
|
33
53
|
if (/^(https?:)?\/\//i.test(v) || v.startsWith("data:"))
|
|
@@ -92,11 +112,28 @@ export function productJsonLd(product) {
|
|
|
92
112
|
const url = `${SITE_URL}${product.detailPath ?? `/products/${product.slug}`}`;
|
|
93
113
|
// Absolutise, then de-duplicate: `mainImage` is very often also `images[0]`,
|
|
94
114
|
// which produced the same URL twice in the emitted array.
|
|
115
|
+
/*
|
|
116
|
+
* 🛑 Accepts BOTH shapes on purpose. `absoluteUrl` now drops a non-string
|
|
117
|
+
* instead of throwing, which stops the 500 — but silently dropping every
|
|
118
|
+
* image would trade a crash for an invisible loss of the `image` property
|
|
119
|
+
* that Product rich results REQUIRE. Reading `.url` off an object keeps the
|
|
120
|
+
* rich result working whichever shape the document holds, so a caller that
|
|
121
|
+
* forgets `productImageUrls()` degrades to nothing worse than correct.
|
|
122
|
+
*/
|
|
123
|
+
/*
|
|
124
|
+
* The cast narrows to `{ url?: string }` rather than `{ url?: unknown }`
|
|
125
|
+
* (`audit-unknown-leakage` blocks the latter, correctly — an `unknown` in a
|
|
126
|
+
* member position defeats checking on every read below it). The assertion is
|
|
127
|
+
* safe without being trusted: `absoluteUrl` tests `typeof value === "string"`
|
|
128
|
+
* itself, so a document whose `url` is a number is dropped rather than
|
|
129
|
+
* reaching `.trim()`. Narrow for the compiler, verify at runtime.
|
|
130
|
+
*/
|
|
131
|
+
const imageUrlOf = (entry) => typeof entry === "string" ? entry : entry?.url;
|
|
95
132
|
const images = [
|
|
96
133
|
...(product.mainImage ? [product.mainImage] : []),
|
|
97
134
|
...(product.images || []),
|
|
98
135
|
]
|
|
99
|
-
.map((img) => absoluteUrl(img))
|
|
136
|
+
.map((img) => absoluteUrl(imageUrlOf(img)))
|
|
100
137
|
.filter((img) => Boolean(img))
|
|
101
138
|
.filter((img, i, arr) => arr.indexOf(img) === i);
|
|
102
139
|
return {
|