@mohasinac/appkit 4.43.0 → 4.44.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/server/features/categories/adapters.d.ts +47 -0
- package/dist/_internal/server/features/categories/adapters.js +235 -0
- package/dist/_internal/server/features/checkout/actions.js +161 -22
- package/dist/_internal/server/features/checkout/expand-order-items.js +34 -0
- package/dist/_internal/server/features/orders/adapters.js +21 -0
- package/dist/_internal/server/features/refunds/actions.js +1 -1
- package/dist/_internal/server/features/seo/sitemap.js +54 -15
- package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
- package/dist/_internal/server/features/stores/adapters.js +6 -0
- package/dist/_internal/server/functions/scheduled.d.ts +4 -2
- package/dist/_internal/server/functions/scheduled.js +64 -6
- package/dist/_internal/server/jobs/core/adminAnalytics.d.ts +0 -1
- package/dist/_internal/server/jobs/core/adminAnalytics.js +9 -1
- package/dist/_internal/server/jobs/core/analyticsAlertEvaluate.d.ts +47 -0
- package/dist/_internal/server/jobs/core/analyticsAlertEvaluate.js +468 -0
- package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +14 -1
- package/dist/_internal/server/jobs/core/countersReconcile.js +24 -8
- package/dist/_internal/server/jobs/core/onCategoryWrite.js +54 -0
- package/dist/_internal/server/jobs/core/priceIndexRollup.d.ts +47 -0
- package/dist/_internal/server/jobs/core/priceIndexRollup.js +234 -0
- package/dist/_internal/server/jobs/core/{testerSandboxCleanup.d.ts → testDataCleanup.d.ts} +3 -3
- package/dist/_internal/server/jobs/core/{testerSandboxCleanup.js → testDataCleanup.js} +3 -3
- package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +5 -1
- package/dist/_internal/server/jobs/handlers/analyticsAlertEvaluate.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/analyticsAlertEvaluate.js +2 -0
- package/dist/_internal/server/jobs/handlers/index.d.ts +3 -1
- package/dist/_internal/server/jobs/handlers/index.js +3 -1
- package/dist/_internal/server/jobs/handlers/messages.d.ts +5 -2
- package/dist/_internal/server/jobs/handlers/messages.js +5 -2
- package/dist/_internal/server/jobs/handlers/priceIndexRollup.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/priceIndexRollup.js +2 -0
- package/dist/_internal/server/jobs/handlers/testDataCleanup.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/testDataCleanup.js +2 -0
- package/dist/_internal/shared/actions/action-registry.js +34 -0
- package/dist/_internal/shared/checkout/order-math.d.ts +113 -3
- package/dist/_internal/shared/checkout/order-math.js +194 -7
- package/dist/_internal/shared/features/categories/price-guidance.d.ts +110 -0
- package/dist/_internal/shared/features/categories/price-guidance.js +136 -0
- package/dist/_internal/shared/features/site-settings/gst-registration.d.ts +90 -0
- package/dist/_internal/shared/features/site-settings/gst-registration.js +126 -0
- package/dist/_internal/shared/fees/calculator.d.ts +105 -0
- package/dist/_internal/shared/fees/calculator.js +143 -0
- package/dist/_internal/shared/listing-types/art/config.d.ts +0 -6
- package/dist/_internal/shared/listing-types/classified/config.d.ts +0 -6
- package/dist/_internal/shared/listing-types/digital-code/config.d.ts +0 -6
- package/dist/_internal/shared/listing-types/live/config.d.ts +0 -6
- package/dist/_internal/shared/listing-types/standard/config.d.ts +0 -6
- package/dist/_internal/shared/listing-types/stickers/config.d.ts +0 -6
- package/dist/_internal/shared/templating/placeholders.d.ts +70 -0
- package/dist/_internal/shared/templating/placeholders.js +73 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.js +1 -0
- package/dist/constants/api-endpoints.d.ts +42 -0
- package/dist/constants/api-endpoints.js +14 -0
- package/dist/features/admin/actions/admin-actions.d.ts +1 -1
- package/dist/features/admin/actions/admin-actions.js +1 -1
- package/dist/features/admin/components/AdminCategoryEditorView.js +269 -3
- package/dist/features/admin/components/AdminProductEditorView.js +8 -1
- package/dist/features/admin/components/AdminProductsView.js +78 -7
- package/dist/features/admin/components/AdminSiteSettingsView.js +20 -1
- package/dist/features/admin/components/AdminTaxCodeEditorView.d.ts +10 -0
- package/dist/features/admin/components/AdminTaxCodeEditorView.js +160 -0
- package/dist/features/admin/components/AdminTaxCodesView.d.ts +13 -0
- package/dist/features/admin/components/AdminTaxCodesView.js +86 -0
- package/dist/features/admin/components/AdminTesterChecklistView.js +1 -1
- package/dist/features/admin/components/analytics/AdminTopProductsTable.js +1 -1
- package/dist/features/auth/schemas/firestore.d.ts +0 -1
- package/dist/features/blog/schemas/firestore.d.ts +2 -2
- package/dist/features/catalogue/components/CatalogueItemEditorView.js +14 -3
- package/dist/features/categories/api/[id]/description-template/route.d.ts +45 -0
- package/dist/features/categories/api/[id]/description-template/route.js +252 -0
- package/dist/features/categories/api/route.js +156 -11
- package/dist/features/categories/components/CategoryDetailPageView.js +43 -10
- package/dist/features/categories/components/CategoryTemplatePicker.d.ts +65 -0
- package/dist/features/categories/components/CategoryTemplatePicker.js +170 -0
- package/dist/features/categories/constants/listing-categories.d.ts +17 -0
- package/dist/features/categories/constants/listing-categories.js +53 -0
- package/dist/features/categories/hooks/useCategoryTree.js +28 -2
- package/dist/features/categories/repository/categories.repository.d.ts +62 -11
- package/dist/features/categories/repository/categories.repository.js +178 -35
- package/dist/features/categories/repository/price-index.repository.d.ts +115 -0
- package/dist/features/categories/repository/price-index.repository.js +85 -0
- package/dist/features/categories/schemas/category-content.d.ts +206 -0
- package/dist/features/categories/schemas/category-content.js +28 -0
- package/dist/features/categories/schemas/firestore.d.ts +74 -5
- package/dist/features/categories/schemas/firestore.js +74 -3
- package/dist/features/categories/schemas/index.d.ts +1047 -0
- package/dist/features/categories/schemas/index.js +96 -0
- package/dist/features/categories/server.d.ts +1 -0
- package/dist/features/categories/server.js +7 -0
- package/dist/features/categories/types/index.d.ts +9 -0
- package/dist/features/events/schemas/firestore.d.ts +2 -2
- package/dist/features/faq/schemas/firestore.d.ts +2 -1
- package/dist/features/faq/schemas/firestore.js +20 -8
- package/dist/features/orders/schemas/firestore.d.ts +70 -0
- package/dist/features/orders/types/index.d.ts +55 -0
- package/dist/features/products/columns/index.d.ts +0 -1
- package/dist/features/products/columns/index.js +5 -1
- package/dist/features/products/components/ProductFilters.d.ts +0 -9
- package/dist/features/products/components/ProductForm.d.ts +27 -1
- package/dist/features/products/components/ProductForm.js +26 -12
- package/dist/features/products/constants/sieve.d.ts +0 -6
- package/dist/features/products/constants/sieve.js +35 -2
- package/dist/features/products/repository/products.repository.d.ts +115 -1
- package/dist/features/products/repository/products.repository.js +491 -39
- package/dist/features/products/schemas/firestore.d.ts +65 -5
- package/dist/features/products/schemas/firestore.js +9 -0
- package/dist/features/products/schemas/product-features.d.ts +140 -2
- package/dist/features/products/schemas/product-features.js +62 -1
- package/dist/features/promotions/repository/coupons.repository.js +6 -1
- package/dist/features/promotions/schemas/firestore.d.ts +44 -1
- package/dist/features/promotions/schemas/firestore.js +76 -2
- package/dist/features/seller/actions/seller-actions.js +17 -2
- package/dist/features/seller/components/PrintCenterView.d.ts +3 -2
- package/dist/features/seller/components/PrintCenterView.js +3 -2
- package/dist/features/seller/components/SellerAnalyticsAlertsView.js +21 -11
- package/dist/features/seller/components/SellerOrdersView.js +6 -1
- package/dist/features/seller/components/SellerPayoutSettingsView.js +19 -3
- package/dist/features/seller/components/SellerProductShell.d.ts +50 -2
- package/dist/features/seller/components/SellerProductShell.js +101 -9
- package/dist/features/seller/components/SellerProductsFilterDrawer.d.ts +13 -1
- package/dist/features/seller/components/SellerProductsFilterDrawer.js +2 -2
- package/dist/features/seller/components/SellerProductsView.js +108 -3
- package/dist/features/seller/types/index.d.ts +17 -0
- package/dist/features/seller/utils/product-draft-mapping.d.ts +13 -0
- package/dist/features/seller/utils/product-draft-mapping.js +17 -0
- package/dist/features/store-extensions/constants/alert-metrics.d.ts +86 -0
- package/dist/features/store-extensions/constants/alert-metrics.js +142 -0
- package/dist/features/store-extensions/schemas/firestore.d.ts +19 -1
- package/dist/features/stores/schemas/firestore.d.ts +31 -2
- package/dist/features/tax-codes/repository/tax-codes.repository.d.ts +55 -0
- package/dist/features/tax-codes/repository/tax-codes.repository.js +131 -0
- package/dist/features/tax-codes/schemas/firestore.d.ts +125 -0
- package/dist/features/tax-codes/schemas/firestore.js +82 -0
- package/dist/features/tax-codes/schemas/tax-code-form.d.ts +63 -0
- package/dist/features/tax-codes/schemas/tax-code-form.js +152 -0
- package/dist/features/tax-codes/schemas/tax-codes.validators.d.ts +107 -0
- package/dist/features/tax-codes/schemas/tax-codes.validators.js +85 -0
- package/dist/features/tester/actions/index.d.ts +0 -1
- package/dist/features/tester/actions/index.js +0 -1
- package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +1 -1
- package/dist/features/tester/components/AdminTesterFeedbackListView.js +1 -1
- package/dist/features/tester/components/index.d.ts +0 -4
- package/dist/features/tester/components/index.js +0 -2
- package/dist/features/tester/repository/tester-checklist-item.repository.d.ts +1 -20
- package/dist/features/tester/repository/tester-checklist-item.repository.js +0 -52
- package/dist/features/tester/schemas/firestore.d.ts +0 -6
- package/dist/features/tester/seed-data/authored/admin__admin-detail-round-trips.js +3 -3
- package/dist/features/tester/seed-data/authored/admin__site-system.js +4 -5
- package/dist/features/tester/seed-data/authored/content-discovery__category-counts-and-rollup.js +2 -2
- package/dist/features/tester/seed-data/authored/index.js +0 -2
- package/dist/features/tester/seed-data/authored/page-wiring__reachability.js +0 -19
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +8 -27
- package/dist/index.d.ts +21 -7
- package/dist/index.js +53 -7
- package/dist/next/routing/route-map.d.ts +2 -4
- package/dist/next/routing/route-map.js +1 -2
- package/dist/repositories/index.d.ts +1 -0
- package/dist/repositories/index.js +1 -0
- package/dist/schemas/registry.d.ts +506 -0
- package/dist/seed/_helpers/brand-rows.d.ts +35 -0
- package/dist/seed/_helpers/brand-rows.js +576 -0
- package/dist/seed/_helpers/category-content.d.ts +39 -0
- package/dist/seed/_helpers/category-content.js +634 -0
- package/dist/seed/_helpers/category-forest.d.ts +2 -0
- package/dist/seed/_helpers/category-forest.js +585 -243
- package/dist/seed/_helpers/category-models.d.ts +5 -0
- package/dist/seed/_helpers/category-models.js +1364 -0
- package/dist/seed/_helpers/category-tree.js +17 -1
- package/dist/seed/_helpers/character-features.d.ts +10 -0
- package/dist/seed/_helpers/character-features.js +118 -0
- package/dist/seed/_helpers/search-txt-wrappers.d.ts +10 -0
- package/dist/seed/_helpers/search-txt-wrappers.js +12 -1
- package/dist/seed/categories-seed-data.js +147 -169
- package/dist/seed/index.d.ts +1 -0
- package/dist/seed/index.js +1 -0
- package/dist/seed/manifest.d.ts +1 -1
- package/dist/seed/manifest.js +5 -0
- package/dist/seed/product-features-seed-data.d.ts +1 -8
- package/dist/seed/product-features-seed-data.js +358 -154
- package/dist/seed/products-art-seed-data.js +16 -4
- package/dist/seed/products-auctions-seed-data.js +6 -6
- package/dist/seed/products-classifieds-seed-data.js +17 -7
- package/dist/seed/products-digital-codes-seed-data.js +12 -12
- package/dist/seed/products-preorders-seed-data.js +8 -8
- package/dist/seed/products-prize-draws-seed-data.js +25 -8
- package/dist/seed/products-standard-seed-data.js +10 -60
- package/dist/seed/products-stickers-seed-data.js +6 -6
- package/dist/seed/site-settings-seed-data.js +24 -0
- package/dist/seed/store-extensions-seed-data.js +10 -4
- package/dist/seed/tax-codes-seed-data.d.ts +34 -0
- package/dist/seed/tax-codes-seed-data.js +100 -0
- package/dist/seed/test-data-ttl.d.ts +29 -0
- package/dist/seed/test-data-ttl.js +31 -0
- package/dist/server-entry.d.ts +1 -1
- package/dist/server-entry.js +6 -1
- package/dist/server.d.ts +2 -1
- package/dist/server.js +5 -2
- package/dist/styles.css +40 -0
- package/dist/ui/components/PaginatedSelect.d.ts +14 -0
- package/dist/ui/components/PaginatedSelect.js +0 -0
- package/dist/ui/components/PaginatedSelect.style.css +40 -0
- package/dist/utils/physical-location.d.ts +23 -0
- package/dist/utils/physical-location.js +52 -0
- package/dist/utils/search-txt-builders.d.ts +20 -0
- package/dist/utils/search-txt-builders.js +36 -0
- package/package.json +1 -1
- package/scripts/seed-cli.mjs +4 -1
- package/dist/_internal/server/jobs/handlers/testerSandboxCleanup.d.ts +0 -2
- package/dist/_internal/server/jobs/handlers/testerSandboxCleanup.js +0 -2
- package/dist/features/tester/actions/leaderboard-actions.d.ts +0 -3
- package/dist/features/tester/actions/leaderboard-actions.js +0 -5
- package/dist/features/tester/components/BugHunterLeaderboardView.d.ts +0 -23
- package/dist/features/tester/components/BugHunterLeaderboardView.js +0 -18
- package/dist/features/tester/components/TesterHubView.d.ts +0 -5
- package/dist/features/tester/components/TesterHubView.js +0 -110
- package/dist/features/tester/seed-data/authored/public-pages__bug-hunters.d.ts +0 -2
- package/dist/features/tester/seed-data/authored/public-pages__bug-hunters.js +0 -78
- package/dist/features/tester/seed-data/tester-ttl.d.ts +0 -4
- package/dist/features/tester/seed-data/tester-ttl.js +0 -6
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The TTL stamp every disposable test fixture must carry.
|
|
3
|
+
*
|
|
4
|
+
* 🛑 `isTestData: true` WITHOUT `testDataExpiresAt` means PERMANENT — nothing
|
|
5
|
+
* ever reclaims it (see `jobs/handlers/_helpers.ts`, which documents the same
|
|
6
|
+
* rule from the sweep's side). That is not a theoretical hazard: until
|
|
7
|
+
* 2026-10-10 the only two `isTestData` rows in the whole seed,
|
|
8
|
+
* `product-tester-crossstore-a` / `-b`, were in exactly that state — live in
|
|
9
|
+
* the public catalogue of 72 products, never purged, and one of them was
|
|
10
|
+
* `store-letitrip-official`'s ONLY buyable standard product, so it was hidden
|
|
11
|
+
* from every real buyer by `hidePublicTestData` while being the only thing
|
|
12
|
+
* that store had to sell. Both are deleted.
|
|
13
|
+
*
|
|
14
|
+
* And the reason they were permanent is that **this module had zero callers**.
|
|
15
|
+
* The helper that enforces the rule existed, was documented as the rule that
|
|
16
|
+
* matters most, and no fixture used it. So: a fixture's three obligations are
|
|
17
|
+
* an id containing `-test-`, `isTestData: true`, and an expiry FROM HERE.
|
|
18
|
+
*
|
|
19
|
+
* Recomputed fresh on every seed run (seed files are re-imported per
|
|
20
|
+
* invocation), so re-seeding naturally refreshes the expiry rather than
|
|
21
|
+
* leaving a fixture that expired between runs.
|
|
22
|
+
*
|
|
23
|
+
* 🛑 Do NOT re-declare the day count elsewhere. It used to be declared twice —
|
|
24
|
+
* here and in `jobs/handlers/messages.ts` — with a comment as the only thing
|
|
25
|
+
* keeping them equal, which is the drift shape Root Cause #42 describes.
|
|
26
|
+
* `messages.ts` now re-exports this one.
|
|
27
|
+
*/
|
|
28
|
+
export const TEST_DATA_TTL_DAYS = 7;
|
|
29
|
+
export function testDataExpiresAt() {
|
|
30
|
+
return new Date(Date.now() + TEST_DATA_TTL_DAYS * 24 * 60 * 60 * 1000);
|
|
31
|
+
}
|
package/dist/server-entry.d.ts
CHANGED
|
@@ -67,7 +67,7 @@ export { getSearchResults, searchAction, type SearchQuery, } from "./_internal/s
|
|
|
67
67
|
export { getCategoryForDetail, listRootCategories, listFeaturedCategories, listMenuCategories, getCategoryTree, listSitemapCategories, CATEGORIES_PAGE_SIZE, CATEGORIES_ROOT_TIER, CATEGORIES_MAX_DEPTH, CATEGORIES_SITEMAP_LIMIT, CATEGORIES_FEATURED_LIMIT, CATEGORIES_MENU_LIMIT, } from "./_internal/server/features/categories/index";
|
|
68
68
|
export { getGroupedListingForDetail, getGroupedListingWithItems, listGroupedListings, listFeaturedGroupedListings, listSitemapGroupedListings, getGroupsForProduct, getGroupsWithItemsForProduct, GROUPED_LISTINGS_PAGE_SIZE, GROUPED_LISTINGS_FEATURED_LIMIT, GROUPED_LISTINGS_SITEMAP_LIMIT, buildGroupedListingMetadata, renderGroupedListingOg, type GroupedListingWithItems, type ListGroupedListingsParams, type SitemapGroupedListing, type GroupedListingMetadataOptions, } from "./_internal/server/features/grouped/index";
|
|
69
69
|
export { createCheckoutOrderAction, attachPaymentAction, verifyAndPlacePhonePeOrderAction, previewCheckoutPricing, resolveShippingCost, formatShippingAddress, CHECKOUT_DEFAULT_COMMISSIONS, CHECKOUT_PAYMENT_METHODS, type CreateCheckoutOrderInput, type VerifyAndPlacePhonePeOrderInput, type CheckoutOrderResult, type CheckoutPaymentMethod, type CheckoutPricingPreviewInput, type CheckoutPricingPreview, } from "./_internal/server/features/checkout/index";
|
|
70
|
-
export { unitPriceFor, lineTotalFor, computePreOrderDepositAmount, } from "./_internal/shared/checkout/order-math";
|
|
70
|
+
export { unitPriceFor, lineTotalFor, sumGroupGst, computePreOrderDepositAmount, type GroupGstSummary, type GstRateSlice, } from "./_internal/shared/checkout/order-math";
|
|
71
71
|
export { processRefundAction, type ProcessRefundInput, } from "./_internal/server/features/refunds/actions";
|
|
72
72
|
export { getSublistingCategoryForDetail } from "./_internal/server/features/sublisting-categories/index";
|
|
73
73
|
export { getStoreForDetail, listStoreProductsInitial, listStoreAuctionsInitial, listStorePreOrdersInitial, listSitemapStores, STORES_PAGE_SIZE, STORES_PRODUCTS_PAGE_SIZE, STORES_SITEMAP_LIMIT, STORES_FEATURED_LIMIT, } from "./_internal/server/features/stores/index";
|
package/dist/server-entry.js
CHANGED
|
@@ -101,7 +101,12 @@ export { createCheckoutOrderAction, attachPaymentAction, verifyAndPlacePhonePeOr
|
|
|
101
101
|
// consumer's Razorpay create-order route needs it: that route used to hand-roll
|
|
102
102
|
// the rule and reproduced only the bundle branch, so it captured the LIST price
|
|
103
103
|
// for an accepted offer. Pure math — no Firestore, no server-only imports.
|
|
104
|
-
|
|
104
|
+
// `sumGroupGst` joins them for exactly the same reason, one defect later: that
|
|
105
|
+
// same create-order route decides what the gateway CAPTURES and omitted product
|
|
106
|
+
// GST entirely, so the buyer was shown the tax, charged without it, and the
|
|
107
|
+
// order recorded none — with the verify-side mismatch guard omitting it too, so
|
|
108
|
+
// the three were consistently wrong and the undercharge was undetectable.
|
|
109
|
+
export { unitPriceFor, lineTotalFor, sumGroupGst, computePreOrderDepositAmount, } from "./_internal/shared/checkout/order-math";
|
|
105
110
|
// S-SBUNI-RULES: refund action
|
|
106
111
|
export { processRefundAction, } from "./_internal/server/features/refunds/actions";
|
|
107
112
|
// S3: sublisting categories data layer
|
package/dist/server.d.ts
CHANGED
|
@@ -231,6 +231,7 @@ export { POST } from "./features/categories/server";
|
|
|
231
231
|
export { categoriesGET } from "./features/categories/server";
|
|
232
232
|
export { categoryItemDELETE } from "./features/categories/server";
|
|
233
233
|
export { categoryItemGET } from "./features/categories/server";
|
|
234
|
+
export { categoryDescriptionTemplateGET } from "./features/categories/server";
|
|
234
235
|
export { categoryItemPATCH } from "./features/categories/server";
|
|
235
236
|
export { createCategory } from "./features/categories/server";
|
|
236
237
|
export { deleteCategory } from "./features/categories/server";
|
|
@@ -267,7 +268,6 @@ export { getEventPollResults } from "./features/events/server";
|
|
|
267
268
|
export type { PollResultEntry, SpinResultEntry } from "./features/events/index";
|
|
268
269
|
export { getEventSpinResults } from "./features/events/server";
|
|
269
270
|
export { getUserSpinResultsForEvent } from "./features/events/server";
|
|
270
|
-
export { getBugHunterLeaderboard } from "./features/tester/server";
|
|
271
271
|
export { getPublicEventById } from "./features/events/server";
|
|
272
272
|
export { listPublicEvents } from "./features/events/server";
|
|
273
273
|
export { getRelatedEvents } from "./features/events/server";
|
|
@@ -427,6 +427,7 @@ export { removeFromWishlist } from "./features/wishlist/server";
|
|
|
427
427
|
export { siteSettingsRepository } from "./repositories/index";
|
|
428
428
|
export { storeRepository } from "./repositories/index";
|
|
429
429
|
export { orderRepository } from "./repositories/index";
|
|
430
|
+
export { taxCodesRepository } from "./repositories/index";
|
|
430
431
|
export type { OrderDocument } from "./features/orders/index";
|
|
431
432
|
export type { OrderDocumentItem } from "./features/orders/index";
|
|
432
433
|
export { serverLogger } from "./monitoring/index";
|
package/dist/server.js
CHANGED
|
@@ -665,6 +665,8 @@ export { categoryItemDELETE } from "./features/categories/server";
|
|
|
665
665
|
// [SERVER-ONLY]-Server-only — uses Node.js, Next.js server internals, or third-party server SDKs (auth, email, payment, shipping).
|
|
666
666
|
// categoryItemGET - Shared export for category item get.
|
|
667
667
|
export { categoryItemGET } from "./features/categories/server";
|
|
668
|
+
// categoryDescriptionTemplateGET - the seller form's inherited-template read.
|
|
669
|
+
export { categoryDescriptionTemplateGET } from "./features/categories/server";
|
|
668
670
|
// [SERVER-ONLY]-Server-only — uses Node.js, Next.js server internals, or third-party server SDKs (auth, email, payment, shipping).
|
|
669
671
|
// categoryItemPATCH - Shared export for category item patch.
|
|
670
672
|
export { categoryItemPATCH } from "./features/categories/server";
|
|
@@ -780,8 +782,6 @@ export { getEventSpinResults } from "./features/events/server";
|
|
|
780
782
|
// getUserSpinResultsForEvent - The CALLER'S OWN spins for an event, unmasked. Companion to the masked public feed above.
|
|
781
783
|
export { getUserSpinResultsForEvent } from "./features/events/server";
|
|
782
784
|
// [SERVER-ONLY]-Server-only — uses Node.js, Next.js server internals, or third-party server SDKs (auth, email, payment, shipping).
|
|
783
|
-
// getBugHunterLeaderboard - Ranks testers by confirmed-bug count.
|
|
784
|
-
export { getBugHunterLeaderboard } from "./features/tester/server";
|
|
785
785
|
// [SERVER-ONLY]-Server-only — uses Node.js, Next.js server internals, or third-party server SDKs (auth, email, payment, shipping).
|
|
786
786
|
// getPublicEventById - Helper for get public event by id.
|
|
787
787
|
export { getPublicEventById } from "./features/events/server";
|
|
@@ -1248,6 +1248,9 @@ export { siteSettingsRepository } from "./repositories/index";
|
|
|
1248
1248
|
export { storeRepository } from "./repositories/index";
|
|
1249
1249
|
// orderRepository - Shared export for order repository (needed by consumer Firebase Functions, e.g. invoice PDF generation).
|
|
1250
1250
|
export { orderRepository } from "./repositories/index";
|
|
1251
|
+
// taxCodesRepository (B3) — resolved by deriveTaxonomy on every product write
|
|
1252
|
+
// that names a taxonomy field, so it is a server-only read on a hot path.
|
|
1253
|
+
export { taxCodesRepository } from "./repositories/index";
|
|
1251
1254
|
// SB-UNI-V — bundlesRepository deleted; use categoriesRepository with categoryType:"bundle".
|
|
1252
1255
|
// -- Monitoring --------------------------------------------------------------
|
|
1253
1256
|
// serverLogger - Structured server-side logger.
|
package/dist/styles.css
CHANGED
|
@@ -7369,6 +7369,46 @@
|
|
|
7369
7369
|
background-color: var(--appkit-color-slate-800);
|
|
7370
7370
|
}
|
|
7371
7371
|
|
|
7372
|
+
/* ── edit the selected option (C2c) ──
|
|
7373
|
+
Absolutely positioned inside `.appkit-ps` (which is `position: relative`)
|
|
7374
|
+
rather than being a flex sibling of the trigger: the trigger is a
|
|
7375
|
+
full-width button and the pencil has to sit ON it visually while remaining
|
|
7376
|
+
OUTSIDE it in the DOM. A button nested inside a button is invalid HTML and
|
|
7377
|
+
the browser's recovery hoists it out, after which which element receives
|
|
7378
|
+
the click is an engine decision — so the pencil would open the dropdown on
|
|
7379
|
+
some browsers and the editor on others.
|
|
7380
|
+
|
|
7381
|
+
`right` clears the ▾ caret. The hover fill uses the theme-inverting
|
|
7382
|
+
surface-hover token, never a named palette tint: `hover:bg-zinc-50` and
|
|
7383
|
+
friends are the SAME near-white in every theme, which is white-on-white
|
|
7384
|
+
under dark ink (Root Cause #79). */
|
|
7385
|
+
.appkit-ps__edit-btn {
|
|
7386
|
+
position: absolute;
|
|
7387
|
+
top: 50%;
|
|
7388
|
+
right: 2rem;
|
|
7389
|
+
transform: translateY(-50%);
|
|
7390
|
+
display: inline-flex;
|
|
7391
|
+
align-items: center;
|
|
7392
|
+
justify-content: center;
|
|
7393
|
+
width: 1.75rem;
|
|
7394
|
+
height: 1.75rem;
|
|
7395
|
+
border-radius: var(--appkit-radius-md, 0.375rem);
|
|
7396
|
+
font-size: 0.875rem;
|
|
7397
|
+
line-height: 1;
|
|
7398
|
+
color: var(--appkit-color-text-muted);
|
|
7399
|
+
transition: background-color 0.1s, color 0.1s;
|
|
7400
|
+
}
|
|
7401
|
+
|
|
7402
|
+
.appkit-ps__edit-btn:hover {
|
|
7403
|
+
background-color: var(--appkit-color-surface-hover);
|
|
7404
|
+
color: var(--appkit-color-primary);
|
|
7405
|
+
}
|
|
7406
|
+
|
|
7407
|
+
.appkit-ps__edit-btn:focus-visible {
|
|
7408
|
+
outline: 2px solid var(--appkit-color-focus-ring);
|
|
7409
|
+
outline-offset: 1px;
|
|
7410
|
+
}
|
|
7411
|
+
|
|
7372
7412
|
/* SideDrawer — accessible slide-in panel with focus trap + unsaved-changes */
|
|
7373
7413
|
|
|
7374
7414
|
.appkit-side-drawer__backdrop {
|
|
@@ -32,6 +32,20 @@ interface BaseProps<V> {
|
|
|
32
32
|
createSchema?: import("zod").ZodType<FormValues> | import("zod").ZodType<unknown>;
|
|
33
33
|
onCreateSubmit?: (values: FormValues) => Promise<PaginatedSelectOption<V>>;
|
|
34
34
|
createSubmitLabel?: string;
|
|
35
|
+
editLabel?: string;
|
|
36
|
+
editDrawerTitle?: string;
|
|
37
|
+
renderEditForm?: (props: {
|
|
38
|
+
value: V;
|
|
39
|
+
option: PaginatedSelectOption<V> | null;
|
|
40
|
+
onEdited: (option: PaginatedSelectOption<V>) => void;
|
|
41
|
+
onCancel: () => void;
|
|
42
|
+
}) => ReactNode;
|
|
43
|
+
editFields?: QuickFieldDef[];
|
|
44
|
+
editSchema?: import("zod").ZodType<FormValues> | import("zod").ZodType<unknown>;
|
|
45
|
+
/** Seed the quick form from the row being edited. */
|
|
46
|
+
onEditLoad?: (value: V) => Promise<FormValues>;
|
|
47
|
+
onEditSubmit?: (value: V, values: FormValues) => Promise<PaginatedSelectOption<V>>;
|
|
48
|
+
editSubmitLabel?: string;
|
|
35
49
|
}
|
|
36
50
|
interface SingleProps<V> extends BaseProps<V> {
|
|
37
51
|
multiple?: false;
|
|
Binary file
|
|
@@ -260,3 +260,43 @@
|
|
|
260
260
|
:is(.dark) .appkit-ps__create-btn:hover {
|
|
261
261
|
background-color: var(--appkit-color-slate-800);
|
|
262
262
|
}
|
|
263
|
+
|
|
264
|
+
/* ── edit the selected option (C2c) ──
|
|
265
|
+
Absolutely positioned inside `.appkit-ps` (which is `position: relative`)
|
|
266
|
+
rather than being a flex sibling of the trigger: the trigger is a
|
|
267
|
+
full-width button and the pencil has to sit ON it visually while remaining
|
|
268
|
+
OUTSIDE it in the DOM. A button nested inside a button is invalid HTML and
|
|
269
|
+
the browser's recovery hoists it out, after which which element receives
|
|
270
|
+
the click is an engine decision — so the pencil would open the dropdown on
|
|
271
|
+
some browsers and the editor on others.
|
|
272
|
+
|
|
273
|
+
`right` clears the ▾ caret. The hover fill uses the theme-inverting
|
|
274
|
+
surface-hover token, never a named palette tint: `hover:bg-zinc-50` and
|
|
275
|
+
friends are the SAME near-white in every theme, which is white-on-white
|
|
276
|
+
under dark ink (Root Cause #79). */
|
|
277
|
+
.appkit-ps__edit-btn {
|
|
278
|
+
position: absolute;
|
|
279
|
+
top: 50%;
|
|
280
|
+
right: 2rem;
|
|
281
|
+
transform: translateY(-50%);
|
|
282
|
+
display: inline-flex;
|
|
283
|
+
align-items: center;
|
|
284
|
+
justify-content: center;
|
|
285
|
+
width: 1.75rem;
|
|
286
|
+
height: 1.75rem;
|
|
287
|
+
border-radius: var(--appkit-radius-md, 0.375rem);
|
|
288
|
+
font-size: 0.875rem;
|
|
289
|
+
line-height: 1;
|
|
290
|
+
color: var(--appkit-color-text-muted);
|
|
291
|
+
transition: background-color 0.1s, color 0.1s;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.appkit-ps__edit-btn:hover {
|
|
295
|
+
background-color: var(--appkit-color-surface-hover);
|
|
296
|
+
color: var(--appkit-color-primary);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.appkit-ps__edit-btn:focus-visible {
|
|
300
|
+
outline: 2px solid var(--appkit-color-focus-ring);
|
|
301
|
+
outline-offset: 1px;
|
|
302
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** The parts a location can have. All optional — see the blank-parts note. */
|
|
2
|
+
export interface PhysicalLocationParts {
|
|
3
|
+
zone?: string | null;
|
|
4
|
+
shelf?: string | null;
|
|
5
|
+
bin?: string | null;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Render a location as `zone/shelf/bin`, or `undefined` when there is nothing
|
|
9
|
+
* to show.
|
|
10
|
+
*
|
|
11
|
+
* 🛑 Returns `undefined` rather than `"//"` for an all-blank location, and
|
|
12
|
+
* that case is REACHABLE, not defensive: `PhysicalLocationModal` tells the
|
|
13
|
+
* user "All fields are optional" while both bulk-location routes only check
|
|
14
|
+
* `typeof === "string"`, so `{zone:"",shelf:"",bin:""}` saves successfully.
|
|
15
|
+
* Every caller already branches on falsy before rendering, so returning
|
|
16
|
+
* `undefined` makes those rows fall through to their em-dash instead of
|
|
17
|
+
* printing punctuation with no content.
|
|
18
|
+
*
|
|
19
|
+
* Partial locations keep their separators (`A//3`) on purpose — the gap is
|
|
20
|
+
* information, and collapsing it would make "zone A, bin 3" indistinguishable
|
|
21
|
+
* from "zone A, shelf 3".
|
|
22
|
+
*/
|
|
23
|
+
export declare function formatPhysicalLocation(loc: PhysicalLocationParts | null | undefined): string | undefined;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* WHY: `physicalLocation` is a `{zone, shelf, bin}` OBJECT on both
|
|
3
|
+
* `ProductDocument` and `OrderDocument`, and three surfaces render it.
|
|
4
|
+
* Two had hand-written the same `a/b/c` interpolation
|
|
5
|
+
* (`SellerProductsView`, `SellerOrdersView`) and the third —
|
|
6
|
+
* `PrintCenterView` — declared the field as a `string` and rendered the
|
|
7
|
+
* object as a bare React child.
|
|
8
|
+
*
|
|
9
|
+
* 🛑 THAT THIRD ONE IS A CRASH. Rendering an object as a React child throws
|
|
10
|
+
* "Objects are not valid as a React child", so the Print Centre would die
|
|
11
|
+
* the first time any seller used the Set Location bulk action. It is
|
|
12
|
+
* LATENT, not live: measured on production, 0 of 72 products and 0 of 67
|
|
13
|
+
* orders carry the field, so nobody has used that feature yet. It was
|
|
14
|
+
* invisible because BOTH mappers in `PrintCenterPanel` are typed `(p: any)`
|
|
15
|
+
* and the products array is passed `as any` — so neither the wrong
|
|
16
|
+
* declaration nor the wrong value had anything to trip over.
|
|
17
|
+
*
|
|
18
|
+
* WHAT: one formatter. Pure, no imports, structural parameter so a caller can
|
|
19
|
+
* pass a `ProductDocument`'s field, an `OrderDocument`'s, or the
|
|
20
|
+
* `PhysicalLocation` client type without a cast.
|
|
21
|
+
*
|
|
22
|
+
* @tag domain:inventory
|
|
23
|
+
* @tag layer:util
|
|
24
|
+
* @tag access:isomorphic
|
|
25
|
+
* @tag sideEffects:none
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* Render a location as `zone/shelf/bin`, or `undefined` when there is nothing
|
|
29
|
+
* to show.
|
|
30
|
+
*
|
|
31
|
+
* 🛑 Returns `undefined` rather than `"//"` for an all-blank location, and
|
|
32
|
+
* that case is REACHABLE, not defensive: `PhysicalLocationModal` tells the
|
|
33
|
+
* user "All fields are optional" while both bulk-location routes only check
|
|
34
|
+
* `typeof === "string"`, so `{zone:"",shelf:"",bin:""}` saves successfully.
|
|
35
|
+
* Every caller already branches on falsy before rendering, so returning
|
|
36
|
+
* `undefined` makes those rows fall through to their em-dash instead of
|
|
37
|
+
* printing punctuation with no content.
|
|
38
|
+
*
|
|
39
|
+
* Partial locations keep their separators (`A//3`) on purpose — the gap is
|
|
40
|
+
* information, and collapsing it would make "zone A, bin 3" indistinguishable
|
|
41
|
+
* from "zone A, shelf 3".
|
|
42
|
+
*/
|
|
43
|
+
export function formatPhysicalLocation(loc) {
|
|
44
|
+
if (!loc)
|
|
45
|
+
return undefined;
|
|
46
|
+
const zone = (loc.zone ?? "").trim();
|
|
47
|
+
const shelf = (loc.shelf ?? "").trim();
|
|
48
|
+
const bin = (loc.bin ?? "").trim();
|
|
49
|
+
if (!zone && !shelf && !bin)
|
|
50
|
+
return undefined;
|
|
51
|
+
return `${zone}/${shelf}/${bin}`;
|
|
52
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ProductDocument } from "../features/products/schemas/firestore";
|
|
2
|
+
import type { CategoryDocument } from "../features/categories/schemas/firestore";
|
|
2
3
|
import type { ScammerDocument } from "../features/scams/schemas/firestore";
|
|
3
4
|
import type { StoreDocument } from "../features/stores/schemas/firestore";
|
|
4
5
|
import type { EventDocument } from "../features/events/schemas/firestore";
|
|
@@ -9,6 +10,25 @@ import type { CouponDocument } from "../features/promotions/schemas/firestore";
|
|
|
9
10
|
import type { OfferDocument } from "../features/seller/schemas/firestore";
|
|
10
11
|
/** products — title, description, brand, taxonomy, condition, card/grading, specs. */
|
|
11
12
|
export declare function buildProductSearchTxt(p: Partial<ProductDocument>): string[];
|
|
13
|
+
/**
|
|
14
|
+
* categories — name, slug, path, ancestor names, item code, editorial copy.
|
|
15
|
+
*
|
|
16
|
+
* Covers brand and bundle rows too, since they share the collection: hence
|
|
17
|
+
* `brandCountry` and `itemCode`, which only one kind ever carries.
|
|
18
|
+
*
|
|
19
|
+
* 🛑 `ancestors[].name` is in here deliberately, and it is what makes a leaf
|
|
20
|
+
* findable by its lineage — typing "burst" reaches `category-lost-longinus`
|
|
21
|
+
* because Beyblade Burst is one of its ancestors. Without it, the only way to
|
|
22
|
+
* find a tier-4 model is to already know its exact name.
|
|
23
|
+
*
|
|
24
|
+
* `description` is stripped of HTML because `contentBody`-style copy is
|
|
25
|
+
* keyword-rich markup and the tags are not search terms.
|
|
26
|
+
*
|
|
27
|
+
* NOT indexed: `createdBy` (a raw uid), `createdByStoreName` (operator
|
|
28
|
+
* identity), `position`/`subtreeSize` (internal coordinates), and the
|
|
29
|
+
* `metrics` counters (numbers nobody searches by).
|
|
30
|
+
*/
|
|
31
|
+
export declare function buildCategorySearchTxt(c: Partial<CategoryDocument>): string[];
|
|
12
32
|
/** stores — name, description, category. No payout details, no contact. */
|
|
13
33
|
export declare function buildStoreSearchTxt(s: Partial<StoreDocument>): string[];
|
|
14
34
|
/** events — title, description, type, tags. */
|
|
@@ -46,6 +46,42 @@ export function buildProductSearchTxt(p) {
|
|
|
46
46
|
p.specifications?.map((s) => `${s.name} ${s.value}`),
|
|
47
47
|
]);
|
|
48
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* categories — name, slug, path, ancestor names, item code, editorial copy.
|
|
51
|
+
*
|
|
52
|
+
* Covers brand and bundle rows too, since they share the collection: hence
|
|
53
|
+
* `brandCountry` and `itemCode`, which only one kind ever carries.
|
|
54
|
+
*
|
|
55
|
+
* 🛑 `ancestors[].name` is in here deliberately, and it is what makes a leaf
|
|
56
|
+
* findable by its lineage — typing "burst" reaches `category-lost-longinus`
|
|
57
|
+
* because Beyblade Burst is one of its ancestors. Without it, the only way to
|
|
58
|
+
* find a tier-4 model is to already know its exact name.
|
|
59
|
+
*
|
|
60
|
+
* `description` is stripped of HTML because `contentBody`-style copy is
|
|
61
|
+
* keyword-rich markup and the tags are not search terms.
|
|
62
|
+
*
|
|
63
|
+
* NOT indexed: `createdBy` (a raw uid), `createdByStoreName` (operator
|
|
64
|
+
* identity), `position`/`subtreeSize` (internal coordinates), and the
|
|
65
|
+
* `metrics` counters (numbers nobody searches by).
|
|
66
|
+
*/
|
|
67
|
+
export function buildCategorySearchTxt(c) {
|
|
68
|
+
return buildSearchTxt([
|
|
69
|
+
c.name,
|
|
70
|
+
c.slug,
|
|
71
|
+
stripHtml(c.description),
|
|
72
|
+
c.path,
|
|
73
|
+
c.itemCode,
|
|
74
|
+
c.brandCountry,
|
|
75
|
+
...(c.ancestors ?? []).map((a) => a.name),
|
|
76
|
+
/*
|
|
77
|
+
* B5 — LAST on purpose. `buildSearchTxt` truncates at 600 tokens and does
|
|
78
|
+
* it silently, so whatever sits at the end of this array is what gets
|
|
79
|
+
* dropped first. A model leaf's own name and its lineage must survive
|
|
80
|
+
* ahead of "also known as" spellings.
|
|
81
|
+
*/
|
|
82
|
+
...(c.aliases ?? []),
|
|
83
|
+
]);
|
|
84
|
+
}
|
|
49
85
|
/** stores — name, description, category. No payout details, no contact. */
|
|
50
86
|
export function buildStoreSearchTxt(s) {
|
|
51
87
|
return buildSearchTxt([
|
package/package.json
CHANGED
package/scripts/seed-cli.mjs
CHANGED
|
@@ -195,7 +195,7 @@ const {
|
|
|
195
195
|
siteSettingsSeedData, faqSeedData,
|
|
196
196
|
wishlistsSeedData, historySeedData,
|
|
197
197
|
groupedListingsSeedData,
|
|
198
|
-
scammersSeedData, productFeaturesSeedData,
|
|
198
|
+
scammersSeedData, productFeaturesSeedData, taxCodesSeedData,
|
|
199
199
|
testerChecklistSeedData, testerResponsesSeedData,
|
|
200
200
|
supportTicketsSeedData, catalogueSeedData,
|
|
201
201
|
shipmentsSeedData, shipmentLotsSeedData, shipmentItemsSeedData,
|
|
@@ -210,6 +210,7 @@ const {
|
|
|
210
210
|
CART_COLLECTION,
|
|
211
211
|
GROUPED_LISTINGS_COLLECTION, SCAMMER_COLLECTION,
|
|
212
212
|
WISHLIST_COLLECTION, HISTORY_COLLECTION, PRODUCT_FEATURES_COLLECTION,
|
|
213
|
+
TAX_CODES_COLLECTION,
|
|
213
214
|
TESTER_CHECKLIST_ITEM_COLLECTION,
|
|
214
215
|
TESTER_CHECKLIST_RESPONSE_COLLECTION,
|
|
215
216
|
// PII field/index constants (plain data, still in the main barrel — the
|
|
@@ -285,6 +286,7 @@ const COLLECTION_MAP = {
|
|
|
285
286
|
groupedListings: GROUPED_LISTINGS_COLLECTION,
|
|
286
287
|
scammerProfiles: SCAMMER_COLLECTION,
|
|
287
288
|
productFeatures: PRODUCT_FEATURES_COLLECTION,
|
|
289
|
+
taxCodes: TAX_CODES_COLLECTION,
|
|
288
290
|
testerChecklistItems: TESTER_CHECKLIST_ITEM_COLLECTION,
|
|
289
291
|
/*
|
|
290
292
|
* Tester submissions. DERIVED tier in the tester's own map (wiped at setup so
|
|
@@ -370,6 +372,7 @@ const SEED_DATA_MAP = {
|
|
|
370
372
|
groupedListings: groupedListingsSeedData,
|
|
371
373
|
scammerProfiles: scammersSeedData,
|
|
372
374
|
productFeatures: productFeaturesSeedData,
|
|
375
|
+
taxCodes: taxCodesSeedData,
|
|
373
376
|
testerChecklistItems: testerChecklistSeedData,
|
|
374
377
|
testerChecklistResponses: testerResponsesSeedData,
|
|
375
378
|
};
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import { testerChecklistItemRepository } from "../repository/tester-checklist-item.repository";
|
|
2
|
-
/** Ranks testers by confirmed-bug count. Mirrors getEventLeaderboard()'s shape. */
|
|
3
|
-
export async function getBugHunterLeaderboard(limit = 50) {
|
|
4
|
-
return testerChecklistItemRepository.getBugHunterLeaderboard(limit);
|
|
5
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import type { BugHunterLeaderboardEntry } from "../schemas/firestore";
|
|
3
|
-
export interface BugHunterLeaderboardViewProps {
|
|
4
|
-
isLoading?: boolean;
|
|
5
|
-
isEmpty?: boolean;
|
|
6
|
-
/** Render the full ranked list — caller provides entries via hook */
|
|
7
|
-
renderList?: () => React.ReactNode;
|
|
8
|
-
/** Render a single row; used when caller wants item-level control */
|
|
9
|
-
renderEntry?: (entry: BugHunterLeaderboardEntry, index: number) => React.ReactNode;
|
|
10
|
-
/** Entries — used only when renderEntry is provided */
|
|
11
|
-
entries?: BugHunterLeaderboardEntry[];
|
|
12
|
-
/** Empty state node */
|
|
13
|
-
renderEmpty?: () => React.ReactNode;
|
|
14
|
-
/** Loading spinner / skeleton */
|
|
15
|
-
renderSkeleton?: () => React.ReactNode;
|
|
16
|
-
labels?: {
|
|
17
|
-
title?: string;
|
|
18
|
-
noEntries?: string;
|
|
19
|
-
bugs?: string;
|
|
20
|
-
};
|
|
21
|
-
className?: string;
|
|
22
|
-
}
|
|
23
|
-
export declare function BugHunterLeaderboardView({ isLoading, isEmpty, renderList, renderEntry, entries, renderEmpty, renderSkeleton, labels, className, }: BugHunterLeaderboardViewProps): React.JSX.Element;
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import React from "react";
|
|
3
|
-
import { Div, Row, Span, Stack, Text } from "../../../ui";
|
|
4
|
-
export function BugHunterLeaderboardView({ isLoading = false, isEmpty = false, renderList, renderEntry, entries = [], renderEmpty, renderSkeleton, labels = {}, className = "", }) {
|
|
5
|
-
if (isLoading) {
|
|
6
|
-
if (renderSkeleton)
|
|
7
|
-
return _jsx(_Fragment, { children: renderSkeleton() });
|
|
8
|
-
return _jsx(Row, { justify: "center", padding: "y-xl", children: "Loading\u2026" });
|
|
9
|
-
}
|
|
10
|
-
if (isEmpty) {
|
|
11
|
-
if (renderEmpty)
|
|
12
|
-
return _jsx(_Fragment, { children: renderEmpty() });
|
|
13
|
-
return (_jsx(Text, { paddingY: "md", variant: "secondary", size: "sm", align: "start", children: labels.noEntries ?? "No confirmed bugs yet." }));
|
|
14
|
-
}
|
|
15
|
-
if (renderList)
|
|
16
|
-
return _jsx(Div, { className: className, children: renderList() });
|
|
17
|
-
return (_jsx(Stack, { className: `${className}`, gap: "sm", children: entries.map((entry, i) => renderEntry ? (_jsx(React.Fragment, { children: renderEntry(entry, i) }, entry.hunterId)) : (_jsxs(Div, { layout: "flex", align: "center", justify: "between", rounded: "xl", padding: "sm", className: "border", "data-section": "bughunterleaderboard-div", children: [_jsxs(Span, { weight: "medium", children: ["#", entry.rank, " ", entry.hunterName] }), _jsxs(Span, { children: [entry.bugCount, " ", labels.bugs ?? "bugs"] })] }, entry.hunterId))) }));
|
|
18
|
-
}
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import React from "react";
|
|
4
|
-
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
5
|
-
import { Alert, Details, Div, FieldInput, Heading, Stack, Summary, Text, TextLink } from "../../../ui";
|
|
6
|
-
import { apiClient } from "../../../http";
|
|
7
|
-
import { useApiMutation } from "../../../client";
|
|
8
|
-
import { useSession } from "../../../react";
|
|
9
|
-
import { ACCOUNT_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
10
|
-
import { ROUTES } from "../../../next/routing/route-map";
|
|
11
|
-
import { TesterChecklistStepRow } from "./TesterChecklistStepRow";
|
|
12
|
-
function matchesQuery(item, query) {
|
|
13
|
-
if (!query)
|
|
14
|
-
return true;
|
|
15
|
-
const haystack = `${item.label} ${item.description ?? ""} ${item.groupLabel} ${item.pageLabel} ${item.href ?? ""}`.toLowerCase();
|
|
16
|
-
return haystack.includes(query.toLowerCase());
|
|
17
|
-
}
|
|
18
|
-
function TesterChecklistPageSection({ page, testerDisplayName, onAnswer, onSaveNote }) {
|
|
19
|
-
const answeredCount = page.items.filter((i) => i.answer).length;
|
|
20
|
-
return (_jsxs(Details, { tone: "card", defaultOpen: false, children: [_jsxs(Summary, { children: [page.pageLabel, " (", answeredCount, "/", page.items.length, " answered)"] }), _jsx(Div, { padding: "t-sm", children: _jsx(Stack, { gap: "sm", children: page.items
|
|
21
|
-
.slice()
|
|
22
|
-
.sort((a, b) => a.order - b.order)
|
|
23
|
-
.map((item) => (_jsx(TesterChecklistStepRow, { testerDisplayName: testerDisplayName, item: {
|
|
24
|
-
checklistItemId: item.id,
|
|
25
|
-
label: item.label,
|
|
26
|
-
description: item.description,
|
|
27
|
-
href: item.href,
|
|
28
|
-
roles: item.roles,
|
|
29
|
-
startPage: item.startPage,
|
|
30
|
-
steps: item.steps,
|
|
31
|
-
inputs: item.inputs,
|
|
32
|
-
expectedBehaviour: item.expectedBehaviour,
|
|
33
|
-
expectedUiState: item.expectedUiState,
|
|
34
|
-
expectedData: item.expectedData,
|
|
35
|
-
endResult: item.endResult,
|
|
36
|
-
answer: item.answer,
|
|
37
|
-
comment: item.comment,
|
|
38
|
-
screenshotUrl: item.screenshotUrl,
|
|
39
|
-
}, onAnswer: onAnswer, onSaveNote: onSaveNote }, item.id))) }) })] }));
|
|
40
|
-
}
|
|
41
|
-
export function TesterHubView({ sandboxExpiresAt }) {
|
|
42
|
-
const { user } = useSession();
|
|
43
|
-
const queryClient = useQueryClient();
|
|
44
|
-
const [search, setSearch] = React.useState("");
|
|
45
|
-
const query = useQuery({
|
|
46
|
-
queryKey: ["user", "tester-checklist"],
|
|
47
|
-
queryFn: async () => {
|
|
48
|
-
const res = await apiClient.get(ACCOUNT_ENDPOINTS.TESTER_CHECKLIST);
|
|
49
|
-
return res?.data ?? res;
|
|
50
|
-
},
|
|
51
|
-
// The API route resolves isTester/canTestAdmin fresh from Firestore on
|
|
52
|
-
// every request, so retrying a 403 (a role that genuinely lacks access)
|
|
53
|
-
// wastes calls for no benefit — but a real access grant should still be
|
|
54
|
-
// visible on the very next request, not cached as a permanent failure.
|
|
55
|
-
retry: false,
|
|
56
|
-
});
|
|
57
|
-
const upsertMutation = useApiMutation({
|
|
58
|
-
mutationFn: async (vars) => apiClient.put(ACCOUNT_ENDPOINTS.TESTER_CHECKLIST_ITEM_BY_ID(vars.checklistItemId), vars.patch),
|
|
59
|
-
onSuccess: () => {
|
|
60
|
-
void queryClient.invalidateQueries({ queryKey: ["user", "tester-checklist"] });
|
|
61
|
-
},
|
|
62
|
-
});
|
|
63
|
-
// Gate on the live API response, not the client-cached session snapshot.
|
|
64
|
-
// SessionContext only refreshes role/isTester/canTestAdmin periodically
|
|
65
|
-
// (every 5 minutes) or on a hard reload/re-login — never on ordinary
|
|
66
|
-
// client-side navigation — so a flag an admin just granted would
|
|
67
|
-
// otherwise still read as "Testers only" here for up to 5 minutes even
|
|
68
|
-
// though the same request already succeeds server-side. The tester-
|
|
69
|
-
// checklist API resolves these fields fresh from Firestore on every call,
|
|
70
|
-
// so trusting its actual response is both more accurate and immediate.
|
|
71
|
-
const isForbidden = query.isError && query.error?.status === 403;
|
|
72
|
-
if (isForbidden) {
|
|
73
|
-
return (_jsx(Alert, { variant: "warning", title: "Testers only", children: "This page is only available to accounts flagged as testers (and admins). Contact an admin if you believe this is a mistake." }));
|
|
74
|
-
}
|
|
75
|
-
if (query.isLoading) {
|
|
76
|
-
return _jsx(Text, { color: "muted", children: "Loading checklist\u2026" });
|
|
77
|
-
}
|
|
78
|
-
// Items arrive pre-sorted by catalog `order` (listActive() orders by it).
|
|
79
|
-
const items = query.data?.items ?? [];
|
|
80
|
-
const filtered = items.filter((item) => matchesQuery(item, search));
|
|
81
|
-
const phases = new Map();
|
|
82
|
-
for (const item of filtered) {
|
|
83
|
-
if (!phases.has(item.phase))
|
|
84
|
-
phases.set(item.phase, new Map());
|
|
85
|
-
const groups = phases.get(item.phase);
|
|
86
|
-
if (!groups.has(item.groupKey))
|
|
87
|
-
groups.set(item.groupKey, { groupLabel: item.groupLabel, pages: new Map() });
|
|
88
|
-
const group = groups.get(item.groupKey);
|
|
89
|
-
if (!group.pages.has(item.pageKey))
|
|
90
|
-
group.pages.set(item.pageKey, { pageLabel: item.pageLabel, items: [] });
|
|
91
|
-
group.pages.get(item.pageKey).items.push(item);
|
|
92
|
-
}
|
|
93
|
-
const sortedPhaseNumbers = Array.from(phases.keys()).sort((a, b) => a - b);
|
|
94
|
-
const handleAnswer = (checklistItemId, answer) => {
|
|
95
|
-
upsertMutation.mutate({ checklistItemId, patch: { answer } });
|
|
96
|
-
};
|
|
97
|
-
const handleSaveNote = async (checklistItemId, comment, screenshotUrl) => {
|
|
98
|
-
await upsertMutation.mutateAsync({ checklistItemId, patch: { comment, screenshotUrl } });
|
|
99
|
-
};
|
|
100
|
-
return (_jsxs(Stack, { gap: "lg", children: [_jsx(Heading, { level: 1, children: "Tester Hub" }), _jsx(Text, { color: "muted", children: "Work through the checklist below and answer Yes/No for each test case. Add a comment and screenshot wherever something looks off \u2014 colors, styles, readability, bugs. Your answers save automatically." }), _jsx(TextLink, { href: String(ROUTES.PUBLIC.BUG_HUNTERS), variant: "underline", weight: "medium", children: "View Bug Hunters Leaderboard \u2192" }), sandboxExpiresAt && (_jsxs(Alert, { variant: "info", title: "Shared test sandbox", children: ["The shared test store/products/categories expire on ", new Date(sandboxExpiresAt).toLocaleDateString(), "."] })), _jsx(FieldInput, { name: "checklist-search", label: "Search test cases", value: search, onChange: setSearch, placeholder: "Search by title or route, e.g. checkout, /store/payouts..." }), sortedPhaseNumbers.map((phaseNumber) => {
|
|
101
|
-
const groups = phases.get(phaseNumber);
|
|
102
|
-
const phaseItemCount = Array.from(groups.values())
|
|
103
|
-
.flatMap((g) => Array.from(g.pages.values()))
|
|
104
|
-
.reduce((sum, page) => sum + page.items.length, 0);
|
|
105
|
-
const phaseAnswered = Array.from(groups.values())
|
|
106
|
-
.flatMap((g) => Array.from(g.pages.values()))
|
|
107
|
-
.reduce((sum, page) => sum + page.items.filter((i) => i.answer).length, 0);
|
|
108
|
-
return (_jsxs(Details, { tone: "card", defaultOpen: false, children: [_jsxs(Summary, { size: "lg", weight: "bold", children: ["Phase ", phaseNumber, " (", phaseAnswered, "/", phaseItemCount, " answered)"] }), _jsx(Div, { padding: "t-sm", children: _jsx(Stack, { gap: "md", children: Array.from(groups.entries()).map(([groupKey, group]) => (_jsxs(Stack, { gap: "sm", children: [_jsx(Heading, { level: 4, children: group.groupLabel }), Array.from(group.pages.entries()).map(([pageKey, page]) => (_jsx(TesterChecklistPageSection, { page: page, testerDisplayName: user?.displayName ?? user?.email ?? "tester", onAnswer: handleAnswer, onSaveNote: handleSaveNote }, pageKey)))] }, groupKey))) }) })] }, phaseNumber));
|
|
109
|
-
})] }));
|
|
110
|
-
}
|