@mohasinac/appkit 4.56.0 → 4.58.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 +4 -4
- package/dist/_internal/server/features/beys/data.d.ts +44 -0
- package/dist/_internal/server/features/beys/data.js +107 -0
- package/dist/_internal/server/features/beys/og.d.ts +42 -0
- package/dist/_internal/server/features/beys/og.js +89 -0
- package/dist/_internal/server/features/checkout/actions.d.ts +31 -0
- package/dist/_internal/server/features/checkout/actions.js +117 -5
- package/dist/_internal/server/features/orders/actions.d.ts +30 -0
- package/dist/_internal/server/features/orders/actions.js +103 -0
- package/dist/_internal/server/features/orders/adapters.js +7 -0
- package/dist/_internal/server/features/parts/data.js +3 -2
- package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
- package/dist/_internal/shared/actions/action-registry.js +139 -0
- package/dist/_internal/shared/shipping/quote.js +5 -6
- package/dist/client.d.ts +23 -0
- package/dist/client.js +3 -0
- package/dist/constants/api-endpoints.d.ts +12 -0
- package/dist/constants/api-endpoints.js +10 -0
- package/dist/constants/field-names.d.ts +46 -0
- package/dist/constants/field-names.js +46 -0
- package/dist/features/about/components/PublicProfileView.js +2 -1
- package/dist/features/account/components/UserOrdersView.js +25 -3
- package/dist/features/admin/components/AdminBeysView.d.ts +27 -0
- package/dist/features/admin/components/AdminBeysView.js +178 -0
- package/dist/features/admin/components/AdminPartsView.d.ts +27 -0
- package/dist/features/admin/components/AdminPartsView.js +160 -0
- package/dist/features/admin/components/OwnerQuickActions.d.ts +19 -2
- package/dist/features/admin/components/OwnerQuickActions.js +83 -8
- package/dist/features/admin/components/RecordOwnerQuickActions.d.ts +31 -0
- package/dist/features/admin/components/RecordOwnerQuickActions.js +128 -0
- package/dist/features/admin/components/ReferenceRecordEditor.d.ts +45 -0
- package/dist/features/admin/components/ReferenceRecordEditor.js +153 -0
- package/dist/features/admin/schemas/firestore.d.ts +1 -1
- package/dist/features/auctions/components/MarketplaceAuctionCard.js +15 -2
- package/dist/features/auth/permissions/constants.d.ts +1 -1
- package/dist/features/auth/permissions/constants.js +12 -1
- package/dist/features/beys/components/BeyDetailPageView.d.ts +50 -0
- package/dist/features/beys/components/BeyDetailPageView.js +98 -0
- package/dist/features/beys/schemas/firestore.d.ts +417 -0
- package/dist/features/beys/schemas/firestore.js +65 -0
- package/dist/features/blog/components/BlogFeaturedCard.js +15 -2
- package/dist/features/blog/components/BlogPostView.js +2 -1
- package/dist/features/cart/components/CartPriceBreakdown.d.ts +2 -0
- package/dist/features/cart/components/CartPriceBreakdown.js +1 -1
- package/dist/features/categories/components/BrandDetailPageView.js +4 -2
- package/dist/features/categories/components/BundleDetailView.js +2 -2
- package/dist/features/categories/components/CategoryDetailPageView.js +36 -3
- package/dist/features/categories/components/CategoryGrid.js +24 -2
- package/dist/features/categories/components/FranchiseDetailPageView.js +2 -1
- package/dist/features/events/components/EventCard.js +15 -2
- package/dist/features/faq/components/FAQPageContent.js +17 -4
- package/dist/features/faq/constants/help-guides.d.ts +36 -0
- package/dist/features/faq/constants/help-guides.js +64 -0
- package/dist/features/grouped/components/GroupedListingDetailView.js +2 -3
- package/dist/features/homepage/components/CustomCardsSection.js +67 -50
- package/dist/features/homepage/components/HeroCarousel.js +3 -1
- package/dist/features/homepage/components/ShopByCategorySection.js +23 -10
- package/dist/features/homepage/lib/section-renderer.js +2 -1
- package/dist/features/homepage/schemas/index.d.ts +10 -10
- package/dist/features/orders/components/EmiInstallmentsPanel.d.ts +19 -0
- package/dist/features/orders/components/EmiInstallmentsPanel.js +57 -0
- package/dist/features/orders/components/index.d.ts +1 -0
- package/dist/features/orders/components/index.js +1 -0
- package/dist/features/orders/schemas/index.d.ts +2 -2
- package/dist/features/orders/types/index.d.ts +15 -0
- package/dist/features/parts/components/PartDetailPageView.js +5 -3
- package/dist/features/parts/components/PartsSearchBox.js +79 -4
- package/dist/features/parts/schemas/admin-edit.d.ts +81 -0
- package/dist/features/parts/schemas/admin-edit.js +89 -0
- package/dist/features/parts/schemas/firestore.d.ts +2 -1
- package/dist/features/points/repository/points.repository.js +5 -5
- package/dist/features/points/schemas/firestore.d.ts +16 -0
- package/dist/features/points/schemas/firestore.js +17 -0
- package/dist/features/pre-orders/components/MarketplacePreorderCard.js +15 -2
- package/dist/features/products/components/MarketplaceBundleCard.js +15 -2
- package/dist/features/products/components/MarketplacePrizeDrawCard.js +15 -2
- package/dist/features/products/components/ProductGrid.js +18 -2
- package/dist/features/products/schemas/firestore.d.ts +18 -0
- package/dist/features/reviews/components/ReviewDetailPageView.js +3 -2
- package/dist/features/seller/components/CategoryInlineSelect.d.ts +10 -1
- package/dist/features/seller/components/CategoryInlineSelect.js +53 -5
- package/dist/features/seller/components/SellerProductShell.d.ts +39 -0
- package/dist/features/seller/components/SellerProductShell.js +31 -3
- package/dist/features/stores/components/StoreHeader.js +3 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.js +13 -0
- package/dist/next/routing/route-map.d.ts +17 -1
- package/dist/next/routing/route-map.js +39 -0
- package/dist/schemas/registry.d.ts +8 -8
- package/dist/seed/_helpers/category-models/chunk0.d.ts +2 -0
- package/dist/seed/_helpers/category-models/chunk0.js +2648 -0
- package/dist/seed/_helpers/category-models/chunk1.d.ts +2 -0
- package/dist/seed/_helpers/category-models/chunk1.js +2318 -0
- package/dist/seed/_helpers/category-models/chunk2.d.ts +2 -0
- package/dist/seed/_helpers/category-models/chunk2.js +2560 -0
- package/dist/seed/_helpers/category-models/chunk3.d.ts +2 -0
- package/dist/seed/_helpers/category-models/chunk3.js +2196 -0
- package/dist/seed/_helpers/category-models/chunk4.d.ts +2 -0
- package/dist/seed/_helpers/category-models/chunk4.js +1732 -0
- package/dist/seed/_helpers/category-models.d.ts +9 -2
- package/dist/seed/_helpers/category-models.js +19 -1368
- package/dist/seed/_helpers/category-parts-glossary-map.js +38 -38
- package/dist/seed/_helpers/category-parts.js +39 -39
- package/dist/seed/bey-products-seed-data.d.ts +18 -0
- package/dist/seed/bey-products-seed-data.js +18011 -0
- package/dist/seed/beys/chunk0.d.ts +17 -0
- package/dist/seed/beys/chunk0.js +36733 -0
- package/dist/seed/beys/chunk1.d.ts +17 -0
- package/dist/seed/beys/chunk1.js +35728 -0
- package/dist/seed/beys/chunk2.d.ts +17 -0
- package/dist/seed/beys/chunk2.js +35857 -0
- package/dist/seed/beys/chunk3.d.ts +17 -0
- package/dist/seed/beys/chunk3.js +34476 -0
- package/dist/seed/beys-seed-data.d.ts +18 -0
- package/dist/seed/beys-seed-data.js +11 -0
- package/dist/seed/homepage-sections-seed-data.js +16 -0
- package/dist/seed/index.d.ts +2 -0
- package/dist/seed/index.js +3 -0
- package/dist/seed/media-assets-seed-data.js +4 -4
- package/dist/seed/products-catalogue/chunk0.d.ts +12 -0
- package/dist/seed/products-catalogue/chunk0.js +7262 -0
- package/dist/seed/products-catalogue/chunk1.d.ts +12 -0
- package/dist/seed/products-catalogue/chunk1.js +7290 -0
- package/dist/seed/products-catalogue/chunk10.d.ts +12 -0
- package/dist/seed/products-catalogue/chunk10.js +7290 -0
- package/dist/seed/products-catalogue/chunk11.d.ts +12 -0
- package/dist/seed/products-catalogue/chunk11.js +7005 -0
- package/dist/seed/products-catalogue/chunk12.d.ts +12 -0
- package/dist/seed/products-catalogue/chunk12.js +7002 -0
- package/dist/seed/products-catalogue/chunk13.d.ts +12 -0
- package/dist/seed/products-catalogue/chunk13.js +7002 -0
- package/dist/seed/products-catalogue/chunk14.d.ts +12 -0
- package/dist/seed/products-catalogue/chunk14.js +7002 -0
- package/dist/seed/products-catalogue/chunk15.d.ts +12 -0
- package/dist/seed/products-catalogue/chunk15.js +7002 -0
- package/dist/seed/products-catalogue/chunk16.d.ts +12 -0
- package/dist/seed/products-catalogue/chunk16.js +7002 -0
- package/dist/seed/products-catalogue/chunk17.d.ts +12 -0
- package/dist/seed/products-catalogue/chunk17.js +7002 -0
- package/dist/seed/products-catalogue/chunk18.d.ts +12 -0
- package/dist/seed/products-catalogue/chunk18.js +4398 -0
- package/dist/seed/products-catalogue/chunk2.d.ts +12 -0
- package/dist/seed/products-catalogue/chunk2.js +7268 -0
- package/dist/seed/products-catalogue/chunk3.d.ts +12 -0
- package/dist/seed/products-catalogue/chunk3.js +7264 -0
- package/dist/seed/products-catalogue/chunk4.d.ts +12 -0
- package/dist/seed/products-catalogue/chunk4.js +7268 -0
- package/dist/seed/products-catalogue/chunk5.d.ts +12 -0
- package/dist/seed/products-catalogue/chunk5.js +7258 -0
- package/dist/seed/products-catalogue/chunk6.d.ts +12 -0
- package/dist/seed/products-catalogue/chunk6.js +7268 -0
- package/dist/seed/products-catalogue/chunk7.d.ts +12 -0
- package/dist/seed/products-catalogue/chunk7.js +7311 -0
- package/dist/seed/products-catalogue/chunk8.d.ts +12 -0
- package/dist/seed/products-catalogue/chunk8.js +7304 -0
- package/dist/seed/products-catalogue/chunk9.d.ts +12 -0
- package/dist/seed/products-catalogue/chunk9.js +7309 -0
- package/dist/seed/products-catalogue-seed-data.d.ts +2 -22
- package/dist/seed/products-catalogue-seed-data.js +58 -86382
- package/dist/seed/seed-purpose.js +9 -0
- package/dist/server.d.ts +5 -1
- package/dist/server.js +19 -0
- package/dist/styles.css +468 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/PosterCard.d.ts +99 -0
- package/dist/ui/components/PosterCard.js +109 -0
- package/dist/ui/components/PosterCard.style.css +466 -0
- package/dist/ui/components/index.style.css +1 -0
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.js +2 -0
- package/package.json +1 -1
- package/scripts/seed-cli.mjs +108 -1
|
@@ -412,3 +412,106 @@ export async function raiseOrderDisputeAction(orderId, reason) {
|
|
|
412
412
|
});
|
|
413
413
|
});
|
|
414
414
|
}
|
|
415
|
+
/**
|
|
416
|
+
* A buyer submits payment for ONE of their own EMI instalments.
|
|
417
|
+
*
|
|
418
|
+
* 🛑 Why this exists at all: there was no buyer-side route. The only writer
|
|
419
|
+
* was the seller's `markEmiInstallmentPaid`, so a buyer on an EMI plan could
|
|
420
|
+
* not pay an instalment — they waited for the seller to record it. The
|
|
421
|
+
* schema had anticipated them all along (`EmiInstallment.transactionId` and
|
|
422
|
+
* `.proofUrl` are documented as "buyer-uploaded"), which is the tell that the
|
|
423
|
+
* surface, not the model, was missing.
|
|
424
|
+
*
|
|
425
|
+
* 🛑 **ANY unpaid instalment, at ANY time — deliberately unbounded.** There is
|
|
426
|
+
* no due-date check and none should be added. A buyer who wants to clear the
|
|
427
|
+
* plan early is reducing our exposure, not breaking a rule, and the dueDate is
|
|
428
|
+
* the date we will CHASE them on, never the first date they may pay. Paying
|
|
429
|
+
* ahead also keeps `emiComplete` honest: that flag is what
|
|
430
|
+
* `assertEmiShippable` reads, so early settlement should be able to release a
|
|
431
|
+
* shipment early rather than waiting out a calendar.
|
|
432
|
+
*
|
|
433
|
+
* It mirrors the manual-payment model rather than inventing a second one: the
|
|
434
|
+
* buyer supplies a UTR and a proof, the seller or an admin confirms. The
|
|
435
|
+
* status therefore moves to `pending` review here, NOT to `paid` — a buyer
|
|
436
|
+
* must never be able to mark their own money received. `markEmiInstallmentPaid`
|
|
437
|
+
* stays the only writer of `paid`, and so stays the only way `emiComplete`
|
|
438
|
+
* can flip.
|
|
439
|
+
*/
|
|
440
|
+
export async function submitEmiInstallmentPaymentAction(orderId, input) {
|
|
441
|
+
return wrapAction(async () => {
|
|
442
|
+
const user = await requireRoleUser(["user", "buyer", "seller", "admin"]);
|
|
443
|
+
const order = await orderRepository.findById(orderId);
|
|
444
|
+
if (!order)
|
|
445
|
+
throw new OrderNotFoundError(orderId);
|
|
446
|
+
if (!isAdminUser(user) && order.userId !== user.uid)
|
|
447
|
+
throw new OrderOwnershipError(orderId);
|
|
448
|
+
if (!order.emiEnabled)
|
|
449
|
+
throw new ValidationError("This order is not on an EMI plan");
|
|
450
|
+
const installments = order.emiInstallments ?? [];
|
|
451
|
+
const target = installments.find((i) => i.index === input.installmentIndex);
|
|
452
|
+
if (!target) {
|
|
453
|
+
throw new ValidationError(`Installment ${input.installmentIndex} not found on this order`);
|
|
454
|
+
}
|
|
455
|
+
if (target.status === "paid") {
|
|
456
|
+
throw new ValidationError("This installment is already paid");
|
|
457
|
+
}
|
|
458
|
+
// A second submission would overwrite the reference the reviewer is
|
|
459
|
+
// currently looking at, which reads to them as the buyer changing their
|
|
460
|
+
// story. Re-submitting needs the reviewer to reject the first one.
|
|
461
|
+
if (target.transactionId) {
|
|
462
|
+
throw new ValidationError("A payment reference is already under review for this installment");
|
|
463
|
+
}
|
|
464
|
+
/*
|
|
465
|
+
* 🛑 `status` is NOT written here, and writing it would be a lie dressed
|
|
466
|
+
* as a state change.
|
|
467
|
+
*
|
|
468
|
+
* A new instalment is created `status: "pending"` (see
|
|
469
|
+
* `createOrderForGroup`), so setting `"pending"` again changes nothing
|
|
470
|
+
* while reading like a transition — and it would leave "nobody has paid"
|
|
471
|
+
* and "the buyer has reported paying" indistinguishable, which is exactly
|
|
472
|
+
* the state a reviewer needs to tell apart.
|
|
473
|
+
*
|
|
474
|
+
* "Awaiting review" is DERIVED: `transactionId` set and `status !== "paid"`.
|
|
475
|
+
* Same rule as the manual-payment queue, which derives its four states
|
|
476
|
+
* from `paymentProofUrl` + `paymentReviewOutcome` rather than storing a
|
|
477
|
+
* mirror field that any write path could forget to update (Root Cause
|
|
478
|
+
* #42). Only `markEmiInstallmentPaid` writes `paid`, so a buyer can never
|
|
479
|
+
* mark their own money received, and `emiComplete` keeps its single writer.
|
|
480
|
+
*/
|
|
481
|
+
const updatedInstallments = installments.map((i) => i.index === input.installmentIndex
|
|
482
|
+
? { ...i, transactionId: input.transactionId, proofUrl: input.proofUrl }
|
|
483
|
+
: i);
|
|
484
|
+
await orderRepository.update(orderId, { emiInstallments: updatedInstallments });
|
|
485
|
+
/*
|
|
486
|
+
* Tell the seller there is something to confirm. Best-effort: a
|
|
487
|
+
* notification that fails must not lose the buyer's submitted reference,
|
|
488
|
+
* which is the only record that they paid.
|
|
489
|
+
*/
|
|
490
|
+
const store = order.storeId ? await storeRepository.findById(order.storeId) : null;
|
|
491
|
+
if (store?.ownerId) {
|
|
492
|
+
await sendNotification({
|
|
493
|
+
userId: store.ownerId,
|
|
494
|
+
type: "payment_review",
|
|
495
|
+
/*
|
|
496
|
+
* `high`, matching the other `payment_review` send in this file. The
|
|
497
|
+
* two differ in urgency — that one has a 15-minute re-upload window
|
|
498
|
+
* and this one has no deadline — but a single notification TYPE whose
|
|
499
|
+
* priority disagrees between its call sites is the drift this codebase
|
|
500
|
+
* keeps paying for, and unconfirmed money is a seller action item
|
|
501
|
+
* either way.
|
|
502
|
+
*/
|
|
503
|
+
priority: "high",
|
|
504
|
+
title: "EMI instalment payment reported",
|
|
505
|
+
message: `A buyer has submitted payment for instalment ${input.installmentIndex} on order ${orderId}.`,
|
|
506
|
+
relatedId: orderId,
|
|
507
|
+
relatedType: "order",
|
|
508
|
+
}).catch((err) => {
|
|
509
|
+
void normalizeError(err);
|
|
510
|
+
serverLogger.error("Failed to notify seller of EMI instalment payment (non-fatal)", {
|
|
511
|
+
orderId,
|
|
512
|
+
installmentIndex: input.installmentIndex,
|
|
513
|
+
});
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
}
|
|
@@ -170,6 +170,13 @@ export function orderDocumentToOrder(doc) {
|
|
|
170
170
|
// the packer needs giftWrapMessage in hand. An
|
|
171
171
|
// adapter that drops them makes every downstream surface render its
|
|
172
172
|
// "not applicable" fallback instead (Root Cause #57).
|
|
173
|
+
// EMI, so the buyer can see and pay their own schedule. Mapped with the
|
|
174
|
+
// add-ons above for the same reason recorded there: an adapter that drops
|
|
175
|
+
// a field makes every downstream surface render its not-applicable
|
|
176
|
+
// fallback (Root Cause #57).
|
|
177
|
+
emiEnabled: doc.emiEnabled,
|
|
178
|
+
emiInstallments: doc.emiInstallments,
|
|
179
|
+
emiRemainingBalance: doc.emiRemainingBalance,
|
|
173
180
|
giftWrapAddon: doc.giftWrapAddon,
|
|
174
181
|
giftWrapFee: doc.giftWrapFee,
|
|
175
182
|
giftWrapMessage: doc.giftWrapMessage,
|
|
@@ -11,6 +11,7 @@ import { PART_GLOSSARY_COLLECTION, MODEL_COMPOSITION_COLLECTION, partGlossaryId,
|
|
|
11
11
|
import { getAdminDb } from "../../../../providers/db-firebase/index.js";
|
|
12
12
|
import { serverLogger } from "../../../../monitoring/server-logger.js";
|
|
13
13
|
import { normalizeError } from "../../../../errors/normalize.js";
|
|
14
|
+
import { MODEL_COMPOSITION_FIELDS, PART_GLOSSARY_FIELDS, } from "../../../../constants/field-names.js";
|
|
14
15
|
/** The bucket cap. 87 is the largest real classification, so this clears it. */
|
|
15
16
|
export const PART_SLOT_PAGE_SIZE = 120;
|
|
16
17
|
/** One part, by `(slot, code|name)` — the only key this collection has. */
|
|
@@ -125,7 +126,7 @@ export const getCompositionsForCategory = cache(async (categoryId) => {
|
|
|
125
126
|
try {
|
|
126
127
|
const snap = await getAdminDb()
|
|
127
128
|
.collection(MODEL_COMPOSITION_COLLECTION)
|
|
128
|
-
.where(
|
|
129
|
+
.where(MODEL_COMPOSITION_FIELDS.CATEGORY_ID, "==", categoryId)
|
|
129
130
|
.limit(8)
|
|
130
131
|
.get();
|
|
131
132
|
return snap.docs.map((d) => ({ id: d.id, ...d.data() }));
|
|
@@ -154,7 +155,7 @@ export const listPartsBySlot = cache(async (slot) => {
|
|
|
154
155
|
try {
|
|
155
156
|
const snap = await getAdminDb()
|
|
156
157
|
.collection(PART_GLOSSARY_COLLECTION)
|
|
157
|
-
.where(
|
|
158
|
+
.where(PART_GLOSSARY_FIELDS.SLOT, "==", slot)
|
|
158
159
|
.limit(PART_SLOT_PAGE_SIZE)
|
|
159
160
|
.get();
|
|
160
161
|
const parts = snap.docs
|
|
@@ -29,7 +29,7 @@ import type { IconKey } from "../../../ui/icons/icon-registry";
|
|
|
29
29
|
/** What KIND of action — drives default UI affordance (link vs button vs danger). */
|
|
30
30
|
export type ActionKind = "primary" | "secondary" | "danger" | "ghost" | "link" | "chip";
|
|
31
31
|
/** Resource bucket — keyed top-level in the ACTIONS tree. */
|
|
32
|
-
export type ActionResource = "PRODUCT" | "AUCTION" | "PRE_ORDER" | "PRIZE_DRAW" | "DIGITAL_CODE" | "BUNDLE" | "GROUP" | "CATEGORY" | "BRAND" | "SUBLISTING" | "STORE" | "BLOG" | "EVENT" | "REVIEW" | "SHIPMENT" | "CATALOGUE" | "USER" | "SELLER" | "ADMIN" | "CART" | "CHECKOUT" | "NAV" | "MEDIA" | "SUPPORT" | "LOTTERY" | "TESTER";
|
|
32
|
+
export type ActionResource = "PRODUCT" | "AUCTION" | "PRE_ORDER" | "PRIZE_DRAW" | "DIGITAL_CODE" | "BUNDLE" | "GROUP" | "CATEGORY" | "BRAND" | "PART" | "BEY" | "SUBLISTING" | "STORE" | "BLOG" | "EVENT" | "REVIEW" | "SHIPMENT" | "CATALOGUE" | "USER" | "SELLER" | "ADMIN" | "CART" | "CHECKOUT" | "NAV" | "MEDIA" | "SUPPORT" | "LOTTERY" | "TESTER";
|
|
33
33
|
/** Confirmation modal config — when set, <Button action={...}> opens
|
|
34
34
|
* a confirm dialog before firing the actual handler. */
|
|
35
35
|
export interface ActionConfirmation {
|
|
@@ -432,6 +432,92 @@ export const ACTIONS = {
|
|
|
432
432
|
},
|
|
433
433
|
},
|
|
434
434
|
},
|
|
435
|
+
/*
|
|
436
|
+
* ── 🛑 THE TWO GENERATED REFERENCE DATABASES ─────────────────────────────
|
|
437
|
+
*
|
|
438
|
+
* `partGlossary` (2,488) and `beys` (1,988) are built by committed
|
|
439
|
+
* generators from a crawled corpus. That shapes every leaf below:
|
|
440
|
+
*
|
|
441
|
+
* · NO `create`. A row authored by hand has no source, vanishes on the
|
|
442
|
+
* next regeneration, and makes "this dataset rebuilds from the repo"
|
|
443
|
+
* false. Adding a part is a change to the generator, not a button.
|
|
444
|
+
* · `edit` is CORRECT-A-WRONG-EXTRACTION, and says so, because that is
|
|
445
|
+
* what it is for — the corpus was measurably wrong five times in one
|
|
446
|
+
* session on mode data alone.
|
|
447
|
+
* · `delete`'s confirmation does NOT say "cannot be undone", because that
|
|
448
|
+
* would be a lie: the row is in a committed seed file and the next
|
|
449
|
+
* reseed restores it. It says what actually happens.
|
|
450
|
+
*/
|
|
451
|
+
PART: {
|
|
452
|
+
view: {
|
|
453
|
+
iconKey: "view",
|
|
454
|
+
id: "part.view",
|
|
455
|
+
label: "View",
|
|
456
|
+
ariaLabel: "View part",
|
|
457
|
+
description: "Open the part reference page.",
|
|
458
|
+
kind: "secondary",
|
|
459
|
+
},
|
|
460
|
+
edit: {
|
|
461
|
+
iconKey: "edit",
|
|
462
|
+
id: "part.edit",
|
|
463
|
+
label: "Edit",
|
|
464
|
+
ariaLabel: "Edit part",
|
|
465
|
+
description: "Correct this part's recorded details. The correction is kept when the database is regenerated.",
|
|
466
|
+
kind: "secondary",
|
|
467
|
+
permissions: ["admin"],
|
|
468
|
+
},
|
|
469
|
+
delete: {
|
|
470
|
+
iconKey: "delete",
|
|
471
|
+
id: "part.delete",
|
|
472
|
+
label: "Delete",
|
|
473
|
+
ariaLabel: "Delete part",
|
|
474
|
+
description: "Remove this part from the live database.",
|
|
475
|
+
kind: "danger",
|
|
476
|
+
permissions: ["admin"],
|
|
477
|
+
confirmation: {
|
|
478
|
+
title: "Remove this part from the live database?",
|
|
479
|
+
body: "Beyblades built from it keep their parts list and will show its name without a link. " +
|
|
480
|
+
"This part is also in the committed seed data, so regenerating the database will bring it " +
|
|
481
|
+
"back — to remove it for good, take it out of the generator.",
|
|
482
|
+
confirmLabel: "Remove",
|
|
483
|
+
},
|
|
484
|
+
},
|
|
485
|
+
},
|
|
486
|
+
BEY: {
|
|
487
|
+
view: {
|
|
488
|
+
iconKey: "view",
|
|
489
|
+
id: "bey.view",
|
|
490
|
+
label: "View",
|
|
491
|
+
ariaLabel: "View Beyblade",
|
|
492
|
+
description: "Open the Beyblade reference page.",
|
|
493
|
+
kind: "secondary",
|
|
494
|
+
},
|
|
495
|
+
edit: {
|
|
496
|
+
iconKey: "edit",
|
|
497
|
+
id: "bey.edit",
|
|
498
|
+
label: "Edit",
|
|
499
|
+
ariaLabel: "Edit Beyblade",
|
|
500
|
+
description: "Correct this Beyblade's recorded details. The correction is kept when the database is regenerated.",
|
|
501
|
+
kind: "secondary",
|
|
502
|
+
permissions: ["admin"],
|
|
503
|
+
},
|
|
504
|
+
delete: {
|
|
505
|
+
iconKey: "delete",
|
|
506
|
+
id: "bey.delete",
|
|
507
|
+
label: "Delete",
|
|
508
|
+
ariaLabel: "Delete Beyblade",
|
|
509
|
+
description: "Remove this Beyblade from the live database.",
|
|
510
|
+
kind: "danger",
|
|
511
|
+
permissions: ["admin"],
|
|
512
|
+
confirmation: {
|
|
513
|
+
title: "Remove this Beyblade from the live database?",
|
|
514
|
+
body: "Its category page and any listings under it are not touched, and boxed sets that include " +
|
|
515
|
+
"it keep their contents list. It is also in the committed seed data, so regenerating the " +
|
|
516
|
+
"database will bring it back — to remove it for good, take it out of the generator.",
|
|
517
|
+
confirmLabel: "Remove",
|
|
518
|
+
},
|
|
519
|
+
},
|
|
520
|
+
},
|
|
435
521
|
CATEGORY: {
|
|
436
522
|
view: {
|
|
437
523
|
iconKey: "view",
|
|
@@ -1039,6 +1125,21 @@ export const ACTIONS = {
|
|
|
1039
1125
|
},
|
|
1040
1126
|
},
|
|
1041
1127
|
BLOG: {
|
|
1128
|
+
/*
|
|
1129
|
+
* Clone, admin-only, backed by POST /api/admin/blog/[id]/duplicate. A
|
|
1130
|
+
* templated post is the real use case: same structure and category, new
|
|
1131
|
+
* subject. The copy starts as a DRAFT with a freshly derived slug — blog is
|
|
1132
|
+
* a pure-slug resource, so reusing the source slug would overwrite it.
|
|
1133
|
+
*/
|
|
1134
|
+
"duplicate-post": {
|
|
1135
|
+
iconKey: "duplicate",
|
|
1136
|
+
id: "blog.duplicate-post",
|
|
1137
|
+
label: "Duplicate",
|
|
1138
|
+
ariaLabel: "Duplicate this post as a draft",
|
|
1139
|
+
description: "Copy this post into a new draft, without its views or featured flag.",
|
|
1140
|
+
kind: "secondary",
|
|
1141
|
+
permissions: ["admin"],
|
|
1142
|
+
},
|
|
1042
1143
|
"create-post": {
|
|
1043
1144
|
iconKey: "create",
|
|
1044
1145
|
id: "blog.create-post",
|
|
@@ -1113,6 +1214,23 @@ export const ACTIONS = {
|
|
|
1113
1214
|
},
|
|
1114
1215
|
},
|
|
1115
1216
|
EVENT: {
|
|
1217
|
+
/*
|
|
1218
|
+
* Clone, admin-only, backed by POST /api/admin/events/[id]/duplicate. A
|
|
1219
|
+
* recurring sale is the real use case: same copy, same prizes, new dates.
|
|
1220
|
+
*
|
|
1221
|
+
* 🛑 The endpoint rebuilds every lottery slot from its prize definition and
|
|
1222
|
+
* resets the booking half — a spread would attribute a real buyer’s
|
|
1223
|
+
* purchased slot to a brand-new event.
|
|
1224
|
+
*/
|
|
1225
|
+
duplicate: {
|
|
1226
|
+
iconKey: "duplicate",
|
|
1227
|
+
id: "event.duplicate",
|
|
1228
|
+
label: "Duplicate",
|
|
1229
|
+
ariaLabel: "Duplicate this event as a draft",
|
|
1230
|
+
description: "Copy this event into a new draft, without its entries, winner or slot bookings.",
|
|
1231
|
+
kind: "secondary",
|
|
1232
|
+
permissions: ["admin"],
|
|
1233
|
+
},
|
|
1116
1234
|
/*
|
|
1117
1235
|
* This bucket had only register / cancel-registration / draw-raffle-winner
|
|
1118
1236
|
* — no edit, no delete — so the owner affordance on an event card or detail
|
|
@@ -2208,6 +2326,27 @@ export const ACTIONS = {
|
|
|
2208
2326
|
kind: "ghost",
|
|
2209
2327
|
permissions: ["admin"],
|
|
2210
2328
|
},
|
|
2329
|
+
/*
|
|
2330
|
+
* Homepage sections have no bucket of their own, so their edit leaf lives
|
|
2331
|
+
* here beside the carousel pair for the same reason — both are homepage
|
|
2332
|
+
* chrome an admin edits from a list rather than from a detail page.
|
|
2333
|
+
*
|
|
2334
|
+
* 🛑 There is deliberately NO `delete-section` counterpart. Section `order`
|
|
2335
|
+
* is POSITIONAL, so removing one silently renumbers its neighbours — not
|
|
2336
|
+
* something to offer from the public homepage, where the only confirmation
|
|
2337
|
+
* a person gets is a dialog. `OwnerQuickActions` enforces the absence by
|
|
2338
|
+
* omitting `remove` from the SECTION mapping, so no call site can pass a
|
|
2339
|
+
* delete handler and have it render.
|
|
2340
|
+
*/
|
|
2341
|
+
"edit-section": {
|
|
2342
|
+
iconKey: "edit",
|
|
2343
|
+
id: "admin.edit-section",
|
|
2344
|
+
label: "Edit Section",
|
|
2345
|
+
ariaLabel: "Edit this homepage section",
|
|
2346
|
+
description: "Open the homepage sections list, which owns this section's config.",
|
|
2347
|
+
kind: "ghost",
|
|
2348
|
+
permissions: ["admin"],
|
|
2349
|
+
},
|
|
2211
2350
|
"delete-carousel": {
|
|
2212
2351
|
iconKey: "delete",
|
|
2213
2352
|
id: "admin.delete-carousel",
|
|
@@ -78,7 +78,7 @@ export function roundShippingUp(rawRate) {
|
|
|
78
78
|
export function reconcileShipping(quote, actual) {
|
|
79
79
|
const charged = Number.isFinite(quote.chargedRate) ? Math.max(0, quote.chargedRate) : 0;
|
|
80
80
|
const spent = Number.isFinite(actual.actualRate) ? Math.max(0, actual.actualRate) : 0;
|
|
81
|
-
const delta =
|
|
81
|
+
const delta = roundMoney(charged - spent);
|
|
82
82
|
return {
|
|
83
83
|
sellerAbsorbs: delta < 0 ? Math.abs(delta) : 0,
|
|
84
84
|
buyerPoints: delta > 0 ? delta : 0,
|
|
@@ -88,12 +88,11 @@ export function reconcileShipping(quote, actual) {
|
|
|
88
88
|
/**
|
|
89
89
|
* Two decimal places.
|
|
90
90
|
*
|
|
91
|
-
* Money
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* balance.
|
|
91
|
+
* Money is stored as decimal rupees (Root Cause #31), so a subtraction can
|
|
92
|
+
* leave a float artefact — `100 - 47.1` is `52.900000000000006`, and that would
|
|
93
|
+
* become a points credit with eleven decimal places on a user's balance.
|
|
95
94
|
*/
|
|
96
|
-
function
|
|
95
|
+
function roundMoney(value) {
|
|
97
96
|
return Math.round(value * 100) / 100;
|
|
98
97
|
}
|
|
99
98
|
/**
|
package/dist/client.d.ts
CHANGED
|
@@ -387,6 +387,8 @@ export type { UserAccountHubViewProps, UserAccountHubViewLabels, UserOrdersViewP
|
|
|
387
387
|
export { useOrders, useOrder, OrdersList } from "./features/orders/index";
|
|
388
388
|
export { OrderStatusTimeline } from "./features/orders/components/OrderStatusTimeline";
|
|
389
389
|
export type { OrderStatusTimelineProps } from "./features/orders/components/OrderStatusTimeline";
|
|
390
|
+
export { EmiInstallmentsPanel } from "./features/orders/components/EmiInstallmentsPanel";
|
|
391
|
+
export type { EmiInstallmentsPanelProps } from "./features/orders/components/EmiInstallmentsPanel";
|
|
390
392
|
export { CountdownDisplay } from "./ui/components/CountdownDisplay";
|
|
391
393
|
export type { CountdownDisplayProps } from "./ui/components/CountdownDisplay";
|
|
392
394
|
export { OrderAddonBadges } from "./features/orders/components/OrderAddonBadges";
|
|
@@ -435,6 +437,8 @@ export type { ListingTypeCapability } from "./_internal/shared/listing-types/cap
|
|
|
435
437
|
export { LISTING_TYPE_REGISTRY, pluginFor, detectListingTypeFromSlug } from "./_internal/shared/listing-types/_registry";
|
|
436
438
|
export { useStoreListingEntitlements } from "./features/seller/hooks/useStoreListingEntitlements";
|
|
437
439
|
export { OwnerQuickActions } from "./features/admin/components/OwnerQuickActions";
|
|
440
|
+
export { RecordOwnerQuickActions } from "./features/admin/components/RecordOwnerQuickActions";
|
|
441
|
+
export type { RecordOwnerQuickActionsProps, RecordQuickActionResource } from "./features/admin/components/RecordOwnerQuickActions";
|
|
438
442
|
export { YourPurchaseStrip } from "./features/orders/components/YourPurchaseStrip";
|
|
439
443
|
export { ReviewFormDrawer, reviewFormSchema } from "./features/reviews/components/ReviewFormDrawer";
|
|
440
444
|
export type { ReviewFormDrawerProps, ReviewFormValues } from "./features/reviews/components/ReviewFormDrawer";
|
|
@@ -584,3 +588,22 @@ export { PriceIndexToolView } from "./features/price-index/components/PriceIndex
|
|
|
584
588
|
export type { PriceIndexToolViewProps, PriceIndexToolResponse, PriceIndexResultRow, PriceIndexBandRow, PriceIndexConditionRow, } from "./features/price-index/components/PriceIndexToolView";
|
|
585
589
|
export { COURIER_RATING_MIN_CONFIDENT_SAMPLES, courierRatingDisplay, isValidCourierVote, courierVoteId, rollUpCourierVotes, } from "./_internal/shared/features/shipping/courier-rating";
|
|
586
590
|
export type { CourierRatingPair, CourierRatingDisplay, CourierRatingVote, CourierStats, } from "./_internal/shared/features/shipping/courier-rating";
|
|
591
|
+
/**
|
|
592
|
+
* The `/api/checkout/pricing-preview` RESPONSE CONTRACT.
|
|
593
|
+
*
|
|
594
|
+
* 🛑 `export type`, from a `_internal/server` module, deliberately. A type-only
|
|
595
|
+
* re-export is fully erased at compile time — `audit-client-server-only-leak`
|
|
596
|
+
* states that explicitly — so this pulls no server chain into the client
|
|
597
|
+
* bundle. It is the one exception worth making for this module.
|
|
598
|
+
*
|
|
599
|
+
* The consumer had been keeping a HAND-WRITTEN copy of both shapes in
|
|
600
|
+
* `src/lib/api/payment-client.ts`, under a comment reading "mirrors
|
|
601
|
+
* `CheckoutPricingPreviewStore` on the server". It drifted exactly as Root
|
|
602
|
+
* Cause #42 predicts: the server gained `emiSurcharge`, the mirror did not, and
|
|
603
|
+
* the preview the cart rendered could not be assigned to the breakdown
|
|
604
|
+
* component that displays it. Four `tsc` errors across the cart and checkout
|
|
605
|
+
* clients, all from one missing field in a second definition of one shape.
|
|
606
|
+
*
|
|
607
|
+
* A response contract has exactly one owner — the handler that produces it.
|
|
608
|
+
*/
|
|
609
|
+
export type { CheckoutPricingPreview, CheckoutPricingPreviewStore, } from "./_internal/server/features/checkout/actions";
|
package/dist/client.js
CHANGED
|
@@ -405,6 +405,8 @@ export { couponDraftToPayload } from "./features/seller/schemas/coupon-form.js";
|
|
|
405
405
|
export { UserAccountHubView, UserOrdersView, UserBidsView, OrderDetailView, UserReturnsView, useNotifications } from "./features/account/index.js";
|
|
406
406
|
export { useOrders, useOrder, OrdersList } from "./features/orders/index.js";
|
|
407
407
|
export { OrderStatusTimeline } from "./features/orders/components/OrderStatusTimeline.js";
|
|
408
|
+
// Buyer-facing EMI schedule with a pay-any-instalment action.
|
|
409
|
+
export { EmiInstallmentsPanel } from "./features/orders/components/EmiInstallmentsPanel.js";
|
|
408
410
|
export { CountdownDisplay } from "./ui/components/CountdownDisplay.js";
|
|
409
411
|
export { OrderAddonBadges } from "./features/orders/components/OrderAddonBadges.js";
|
|
410
412
|
// Manual-payment (cash / UPI / EMI) shared constants — pure data + predicates,
|
|
@@ -458,6 +460,7 @@ export { LISTING_TYPE_REGISTRY, pluginFor, detectListingTypeFromSlug } from "./_
|
|
|
458
460
|
export { useStoreListingEntitlements } from "./features/seller/hooks/useStoreListingEntitlements.js";
|
|
459
461
|
// Pencil / clone / bin for whoever owns the record, on a PUBLIC surface.
|
|
460
462
|
export { OwnerQuickActions } from "./features/admin/components/OwnerQuickActions.js";
|
|
463
|
+
export { RecordOwnerQuickActions } from "./features/admin/components/RecordOwnerQuickActions.js";
|
|
461
464
|
// "You ordered this - track it" / "your review - edit it", on a product page.
|
|
462
465
|
export { YourPurchaseStrip } from "./features/orders/components/YourPurchaseStrip.js";
|
|
463
466
|
// The first review form this app has had - see the component's header.
|
|
@@ -126,6 +126,10 @@ export declare const ADMIN_ENDPOINTS: {
|
|
|
126
126
|
readonly BUNDLE_REBUILD: (id: string) => string;
|
|
127
127
|
readonly CATEGORIES: "/api/admin/categories";
|
|
128
128
|
readonly CATEGORY_BY_ID: (id: string) => string;
|
|
129
|
+
readonly PARTS: "/api/admin/parts";
|
|
130
|
+
readonly PART_BY_ID: (id: string) => string;
|
|
131
|
+
readonly BEYS: "/api/admin/beys";
|
|
132
|
+
readonly BEY_BY_ID: (id: string) => string;
|
|
129
133
|
readonly BRANDS: "/api/admin/brands";
|
|
130
134
|
readonly BRAND_BY_ID: (id: string) => string;
|
|
131
135
|
readonly FAQS: "/api/admin/faqs";
|
|
@@ -690,6 +694,10 @@ export declare const API_ENDPOINTS: {
|
|
|
690
694
|
readonly BUNDLE_REBUILD: (id: string) => string;
|
|
691
695
|
readonly CATEGORIES: "/api/admin/categories";
|
|
692
696
|
readonly CATEGORY_BY_ID: (id: string) => string;
|
|
697
|
+
readonly PARTS: "/api/admin/parts";
|
|
698
|
+
readonly PART_BY_ID: (id: string) => string;
|
|
699
|
+
readonly BEYS: "/api/admin/beys";
|
|
700
|
+
readonly BEY_BY_ID: (id: string) => string;
|
|
693
701
|
readonly BRANDS: "/api/admin/brands";
|
|
694
702
|
readonly BRAND_BY_ID: (id: string) => string;
|
|
695
703
|
readonly FAQS: "/api/admin/faqs";
|
|
@@ -1219,6 +1227,10 @@ export declare const API_ROUTES: {
|
|
|
1219
1227
|
readonly BUNDLE_REBUILD: (id: string) => string;
|
|
1220
1228
|
readonly CATEGORIES: "/api/admin/categories";
|
|
1221
1229
|
readonly CATEGORY_BY_ID: (id: string) => string;
|
|
1230
|
+
readonly PARTS: "/api/admin/parts";
|
|
1231
|
+
readonly PART_BY_ID: (id: string) => string;
|
|
1232
|
+
readonly BEYS: "/api/admin/beys";
|
|
1233
|
+
readonly BEY_BY_ID: (id: string) => string;
|
|
1222
1234
|
readonly BRANDS: "/api/admin/brands";
|
|
1223
1235
|
readonly BRAND_BY_ID: (id: string) => string;
|
|
1224
1236
|
readonly FAQS: "/api/admin/faqs";
|
|
@@ -157,6 +157,16 @@ export const ADMIN_ENDPOINTS = {
|
|
|
157
157
|
BUNDLE_REBUILD: (id) => `/api/admin/bundles/${id}/rebuild`,
|
|
158
158
|
CATEGORIES: "/api/admin/categories",
|
|
159
159
|
CATEGORY_BY_ID: (id) => `/api/admin/categories/${id}`,
|
|
160
|
+
/*
|
|
161
|
+
* The generated reference databases. PATCH corrects a wrong extraction;
|
|
162
|
+
* DELETE removes a row that should not exist. No POST — see the note on
|
|
163
|
+
* `ROUTES.ADMIN.PARTS` for why creating one by hand is not a supported
|
|
164
|
+
* operation on generated data.
|
|
165
|
+
*/
|
|
166
|
+
PARTS: "/api/admin/parts",
|
|
167
|
+
PART_BY_ID: (id) => `/api/admin/parts/${id}`,
|
|
168
|
+
BEYS: "/api/admin/beys",
|
|
169
|
+
BEY_BY_ID: (id) => `/api/admin/beys/${id}`,
|
|
160
170
|
BRANDS: "/api/admin/brands",
|
|
161
171
|
BRAND_BY_ID: (id) => `/api/admin/brands/${id}`,
|
|
162
172
|
FAQS: "/api/admin/faqs",
|
|
@@ -509,6 +509,52 @@ export declare const STORE_FIELDS: {
|
|
|
509
509
|
readonly TOTAL_PRODUCTS: "stats.totalProducts";
|
|
510
510
|
};
|
|
511
511
|
};
|
|
512
|
+
/**
|
|
513
|
+
* The two GENERATED reference databases.
|
|
514
|
+
*
|
|
515
|
+
* 🛑 NOT reused from `CATEGORY_FIELDS`. `audit-route-nav-field-constants`
|
|
516
|
+
* suggested `CATEGORY_FIELDS.NAME` for a query against `partGlossary`, and the
|
|
517
|
+
* string would have been identical — which is exactly why that would be the
|
|
518
|
+
* wrong fix. A constant's job is to say WHICH collection's field is meant, so
|
|
519
|
+
* that renaming one collection's field cannot silently retarget another's
|
|
520
|
+
* query. Same bytes, different claim.
|
|
521
|
+
*/
|
|
522
|
+
/**
|
|
523
|
+
* `modelComposition` — which parts a model is built from.
|
|
524
|
+
*
|
|
525
|
+
* 🛑 ITS OWN `CATEGORY_ID`, not `BEY_FIELDS.CATEGORY_ID`. The audit suggested
|
|
526
|
+
* the bey constant for a query against THIS collection, and the string is
|
|
527
|
+
* identical — which is precisely the substitution the comment on
|
|
528
|
+
* `PART_GLOSSARY_FIELDS` warns about. A composition row's `categoryId` points
|
|
529
|
+
* at the model leaf it describes; a bey's points at its own paired leaf. Same
|
|
530
|
+
* bytes, two different facts, and only one of them moves if either is renamed.
|
|
531
|
+
*/
|
|
532
|
+
export declare const MODEL_COMPOSITION_FIELDS: {
|
|
533
|
+
readonly ID: "id";
|
|
534
|
+
readonly CATEGORY_ID: "categoryId";
|
|
535
|
+
readonly SLOT: "slot";
|
|
536
|
+
};
|
|
537
|
+
export declare const PART_GLOSSARY_FIELDS: {
|
|
538
|
+
readonly ID: "id";
|
|
539
|
+
readonly NAME: "name";
|
|
540
|
+
readonly SLOT: "slot";
|
|
541
|
+
readonly CLASSIFICATION: "classification";
|
|
542
|
+
readonly CODE: "code";
|
|
543
|
+
readonly WEIGHT_G: "weightG";
|
|
544
|
+
/** Set by `applyAdminEdit`; the seed loader queries on it. */
|
|
545
|
+
readonly ADMIN_EDITED: "adminEdited";
|
|
546
|
+
readonly ADMIN_EDITED_FIELDS: "adminEditedFields";
|
|
547
|
+
};
|
|
548
|
+
export declare const BEY_FIELDS: {
|
|
549
|
+
readonly ID: "id";
|
|
550
|
+
readonly SLUG: "slug";
|
|
551
|
+
readonly NAME: "name";
|
|
552
|
+
readonly TYPE: "type";
|
|
553
|
+
readonly SYSTEM: "system";
|
|
554
|
+
readonly CATEGORY_ID: "categoryId";
|
|
555
|
+
readonly ADMIN_EDITED: "adminEdited";
|
|
556
|
+
readonly ADMIN_EDITED_FIELDS: "adminEditedFields";
|
|
557
|
+
};
|
|
512
558
|
export declare const CATEGORY_FIELDS: {
|
|
513
559
|
readonly ID: "id";
|
|
514
560
|
readonly NAME: "name";
|
|
@@ -613,6 +613,52 @@ export const STORE_FIELDS = {
|
|
|
613
613
|
// ============================================================================
|
|
614
614
|
// CATEGORY FIELDS
|
|
615
615
|
// ============================================================================
|
|
616
|
+
/**
|
|
617
|
+
* The two GENERATED reference databases.
|
|
618
|
+
*
|
|
619
|
+
* 🛑 NOT reused from `CATEGORY_FIELDS`. `audit-route-nav-field-constants`
|
|
620
|
+
* suggested `CATEGORY_FIELDS.NAME` for a query against `partGlossary`, and the
|
|
621
|
+
* string would have been identical — which is exactly why that would be the
|
|
622
|
+
* wrong fix. A constant's job is to say WHICH collection's field is meant, so
|
|
623
|
+
* that renaming one collection's field cannot silently retarget another's
|
|
624
|
+
* query. Same bytes, different claim.
|
|
625
|
+
*/
|
|
626
|
+
/**
|
|
627
|
+
* `modelComposition` — which parts a model is built from.
|
|
628
|
+
*
|
|
629
|
+
* 🛑 ITS OWN `CATEGORY_ID`, not `BEY_FIELDS.CATEGORY_ID`. The audit suggested
|
|
630
|
+
* the bey constant for a query against THIS collection, and the string is
|
|
631
|
+
* identical — which is precisely the substitution the comment on
|
|
632
|
+
* `PART_GLOSSARY_FIELDS` warns about. A composition row's `categoryId` points
|
|
633
|
+
* at the model leaf it describes; a bey's points at its own paired leaf. Same
|
|
634
|
+
* bytes, two different facts, and only one of them moves if either is renamed.
|
|
635
|
+
*/
|
|
636
|
+
export const MODEL_COMPOSITION_FIELDS = {
|
|
637
|
+
ID: "id",
|
|
638
|
+
CATEGORY_ID: "categoryId",
|
|
639
|
+
SLOT: "slot",
|
|
640
|
+
};
|
|
641
|
+
export const PART_GLOSSARY_FIELDS = {
|
|
642
|
+
ID: "id",
|
|
643
|
+
NAME: "name",
|
|
644
|
+
SLOT: "slot",
|
|
645
|
+
CLASSIFICATION: "classification",
|
|
646
|
+
CODE: "code",
|
|
647
|
+
WEIGHT_G: "weightG",
|
|
648
|
+
/** Set by `applyAdminEdit`; the seed loader queries on it. */
|
|
649
|
+
ADMIN_EDITED: "adminEdited",
|
|
650
|
+
ADMIN_EDITED_FIELDS: "adminEditedFields",
|
|
651
|
+
};
|
|
652
|
+
export const BEY_FIELDS = {
|
|
653
|
+
ID: "id",
|
|
654
|
+
SLUG: "slug",
|
|
655
|
+
NAME: "name",
|
|
656
|
+
TYPE: "type",
|
|
657
|
+
SYSTEM: "system",
|
|
658
|
+
CATEGORY_ID: "categoryId",
|
|
659
|
+
ADMIN_EDITED: "adminEdited",
|
|
660
|
+
ADMIN_EDITED_FIELDS: "adminEditedFields",
|
|
661
|
+
};
|
|
616
662
|
export const CATEGORY_FIELDS = {
|
|
617
663
|
ID: "id",
|
|
618
664
|
NAME: "name",
|
|
@@ -16,6 +16,7 @@ import { pluginFor } from "../../../_internal/shared/listing-types/_registry.js"
|
|
|
16
16
|
import { normalizeListingType } from "../../products/utils/listing-type.js";
|
|
17
17
|
import { isAdminUser, isSellerUser } from "../../auth/role-predicates.js";
|
|
18
18
|
import { User, Star, ShoppingBag, Package, Trophy, Globe, MapPin, ExternalLink } from "lucide-react";
|
|
19
|
+
import { OwnerQuickActions } from "../../admin/components/OwnerQuickActions.js";
|
|
19
20
|
const __P = {
|
|
20
21
|
p4: "p-[var(--appkit-space-4)]",
|
|
21
22
|
p6: "p-[var(--appkit-space-6)]",
|
|
@@ -120,7 +121,7 @@ export async function PublicProfileView({ userId, tab, }) {
|
|
|
120
121
|
const profileHeroCtx = { displayName, photoURL, memberSince, isSeller, storeSlug, flex, page };
|
|
121
122
|
const statItems = buildProfileStatItems(t, { listingCount, reviewCount, itemsSold, auctionsWon, totalOrders, isSeller });
|
|
122
123
|
const activeTab = tab === "catalogue" ? "catalogue" : "overview";
|
|
123
|
-
return (_jsxs(Div, { className: "-mx-4 md:-mx-6 lg:-mx-8 -mt-6 sm:-mt-8 lg:-mt-10", children: [renderProfileHero(t, profileHeroCtx), _jsxs(Stack, { gap: "2xl", className: `${page.container.md}`, paddingY: "y-3xl", children: [_jsxs(Row, { gap: "md", padding: "t-xs", border: "bottom", children: [_jsx(TextLink, { href: `/profile/${userId}/overview`, variant: activeTab === "overview" ? "default" : "muted", weight: activeTab === "overview" ? "semibold" : "normal", children: "Overview" }), _jsx(TextLink, { href: `/profile/${userId}/catalogue`, variant: activeTab === "catalogue" ? "default" : "muted", weight: activeTab === "catalogue" ? "semibold" : "normal", children: "Catalogue" })] }), activeTab === "catalogue" ? (_jsx(PublicCatalogueView, { ownerSlug: userId })) : (_jsxs(_Fragment, { children: [renderProfileStatsRow(flex, statItems), renderProfileBioSection(pub), renderStoreDescriptionSection(isSeller, storeSlug ?? null, storeDescription ?? null, storeName, t), renderProfileListingsSection(t, products, storeSlug ?? null), renderAuthoredReviewsSection(t, reviewsAuthored, displayName), reviewsReceivedAsBuyer.length > 0 && renderBuyerReceivedReviewsSection(t, reviewsReceivedAsBuyer)] })), _jsx(Row, { justify: "center", padding: "t-xs", children: _jsxs(Link, { href: String(ROUTES.HOME), className: "text-[length:var(--appkit-text-sm)] text-[var(--appkit-color-text-faint)] hover:text-[var(--appkit-color-text-muted)]", children: ["\u2190 ", t("backHome")] }) })] })] }));
|
|
124
|
+
return (_jsxs(Div, { className: "-mx-4 md:-mx-6 lg:-mx-8 -mt-6 sm:-mt-8 lg:-mt-10", children: [renderProfileHero(t, profileHeroCtx), _jsxs(Stack, { gap: "2xl", className: `${page.container.md}`, paddingY: "y-3xl", children: [_jsx(Row, { justify: "end", children: _jsx(OwnerQuickActions, { resource: "USER", placement: "detail", recordLabel: displayName, editHref: String(ROUTES.ADMIN.USER_EDIT(userId)) }) }), _jsxs(Row, { gap: "md", padding: "t-xs", border: "bottom", children: [_jsx(TextLink, { href: `/profile/${userId}/overview`, variant: activeTab === "overview" ? "default" : "muted", weight: activeTab === "overview" ? "semibold" : "normal", children: "Overview" }), _jsx(TextLink, { href: `/profile/${userId}/catalogue`, variant: activeTab === "catalogue" ? "default" : "muted", weight: activeTab === "catalogue" ? "semibold" : "normal", children: "Catalogue" })] }), activeTab === "catalogue" ? (_jsx(PublicCatalogueView, { ownerSlug: userId })) : (_jsxs(_Fragment, { children: [renderProfileStatsRow(flex, statItems), renderProfileBioSection(pub), renderStoreDescriptionSection(isSeller, storeSlug ?? null, storeDescription ?? null, storeName, t), renderProfileListingsSection(t, products, storeSlug ?? null), renderAuthoredReviewsSection(t, reviewsAuthored, displayName), reviewsReceivedAsBuyer.length > 0 && renderBuyerReceivedReviewsSection(t, reviewsReceivedAsBuyer)] })), _jsx(Row, { justify: "center", padding: "t-xs", children: _jsxs(Link, { href: String(ROUTES.HOME), className: "text-[length:var(--appkit-text-sm)] text-[var(--appkit-color-text-faint)] hover:text-[var(--appkit-color-text-muted)]", children: ["\u2190 ", t("backHome")] }) })] })] }));
|
|
124
125
|
}
|
|
125
126
|
function buildProfileStatItems(t, ctx) {
|
|
126
127
|
const { listingCount, reviewCount, itemsSold, auctionsWon, totalOrders, isSeller } = ctx;
|