@mohasinac/appkit 4.54.1 → 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.
Files changed (34) hide show
  1. package/dist/.build-stamp.json +3 -3
  2. package/dist/_internal/server/features/checkout/actions.js +61 -2
  3. package/dist/_internal/shared/features/checkout/config.d.ts +1 -1
  4. package/dist/_internal/shared/features/checkout/config.js +1 -1
  5. package/dist/_internal/shared/features/site-settings/fees.d.ts +1 -1
  6. package/dist/_internal/shared/features/site-settings/fees.js +1 -1
  7. package/dist/_internal/shared/fees/calculator.d.ts +35 -3
  8. package/dist/_internal/shared/fees/calculator.js +34 -4
  9. package/dist/errors/error-codes.d.ts +15 -0
  10. package/dist/errors/error-codes.js +15 -0
  11. package/dist/errors/index.d.ts +1 -1
  12. package/dist/errors/index.js +1 -1
  13. package/dist/features/account/components/UserBidsView.js +4 -2
  14. package/dist/features/admin/components/AdminSiteSettingsView.js +18 -2
  15. package/dist/features/admin/schemas/firestore.d.ts +37 -2
  16. package/dist/features/admin/schemas/firestore.js +12 -1
  17. package/dist/features/cart/components/CartPriceBreakdown.js +1 -1
  18. package/dist/features/checkout/schemas/firestore.d.ts +12 -1
  19. package/dist/features/layout/NavbarLayout.js +139 -32
  20. package/dist/features/orders/schemas/firestore.d.ts +1 -1
  21. package/dist/features/products/components/ProductDetailPageView.js +2 -25
  22. package/dist/features/products/components/ProductOwnerQuickActions.d.ts +11 -0
  23. package/dist/features/products/components/ProductOwnerQuickActions.js +120 -0
  24. package/dist/features/products/components/ProductsIndexListing.js +35 -5
  25. package/dist/features/products/components/ProductsIndexPageView.js +40 -7
  26. package/dist/providers/shipping-shiprocket/index.d.ts +13 -4
  27. package/dist/providers/shipping-shiprocket/index.js +34 -10
  28. package/dist/seed/faq-seed-data.js +1 -1
  29. package/dist/seed/site-settings-seed-data.js +1 -1
  30. package/dist/seo/json-ld.d.ts +8 -1
  31. package/dist/seo/json-ld.js +39 -2
  32. package/dist/styles.css +1 -1
  33. package/dist/tailwind-utilities.css +1 -1
  34. package/package.json +1 -1
@@ -12,10 +12,19 @@
12
12
  * `loginResponse` requires — the rest are ignored, as Zod ignores unknown
13
13
  * keys by default.
14
14
  *
15
- * 🛑 NOT VERIFIED: every other endpoint. Order creation, AWB assignment,
16
- * tracking, serviceability and label generation all MUTATE or bill against
17
- * a real carrier account, so none was called. Their field names below are
18
- * claims taken from the published docs, not facts read off a payload.
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
- * 🛑 `pickup_postcode` is required by their API and we do not have it on
316
- * this contract. Taken from the registered pickup location's name is NOT
317
- * possible — it is a nickname, not a pincode — so this returns
318
- * "unserviceable" rather than guessing a origin pincode, which would
319
- * produce confidently wrong rates.
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: SHIPROCKET_PICKUP_PINCODE unset — serviceability cannot be checked", { pincode });
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 {
@@ -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 as soon as the token payment is confirmed — you don't wait for the full amount to be paid.</p>`),
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
- codHandlingFeeMin: 200,
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.
@@ -29,7 +29,14 @@ export interface ProductJsonLdInput {
29
29
  price: number;
30
30
  currency: string;
31
31
  mainImage?: string;
32
- images?: string[];
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;
@@ -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
- const v = value?.trim();
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 {