@mohasinac/appkit 4.42.18 → 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/client/features/lottery/LotteryDetailView.js +22 -1
- 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/products/list-public.d.ts +24 -0
- package/dist/_internal/server/features/products/list-public.js +10 -2
- 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 +59 -0
- package/dist/_internal/server/jobs/core/onProductWrite.js +31 -0
- package/dist/_internal/server/jobs/core/onReviewWrite.js +8 -0
- package/dist/_internal/server/jobs/core/onStoreWrite.d.ts +1 -1
- package/dist/_internal/server/jobs/core/onStoreWrite.js +8 -2
- package/dist/_internal/server/jobs/core/priceIndexRollup.d.ts +47 -0
- package/dist/_internal/server/jobs/core/priceIndexRollup.js +234 -0
- package/dist/_internal/server/jobs/core/revalidateNotify.d.ts +28 -0
- package/dist/_internal/server/jobs/core/revalidateNotify.js +125 -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/cache/revalidate-targets.d.ts +49 -0
- package/dist/_internal/shared/cache/revalidate-targets.js +157 -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/auctions/components/AuctionBottomActions.js +10 -1
- package/dist/features/auctions/components/AuctionDetailPageView.js +2 -1
- package/dist/features/auctions/components/AuctionLiveStatusBadge.d.ts +20 -0
- package/dist/features/auctions/components/AuctionLiveStatusBadge.js +21 -0
- package/dist/features/auctions/components/PlaceBidFormClient.d.ts +1 -1
- package/dist/features/auctions/components/PlaceBidFormClient.js +20 -2
- package/dist/features/auctions/hooks/useIsAuctionEnded.d.ts +41 -0
- package/dist/features/auctions/hooks/useIsAuctionEnded.js +76 -0
- package/dist/features/auth/schemas/firestore.d.ts +0 -1
- package/dist/features/blog/api/route.js +16 -1
- package/dist/features/blog/schemas/firestore.d.ts +2 -2
- package/dist/features/catalogue/components/CatalogueItemEditorView.js +14 -3
- package/dist/features/categories/actions/category-actions.d.ts +2 -2
- package/dist/features/categories/actions/category-actions.js +22 -10
- package/dist/features/categories/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/CategoriesIndexListing.d.ts +1 -3
- package/dist/features/categories/components/CategoriesIndexListing.js +1 -1
- package/dist/features/categories/components/CategoriesIndexPageView.d.ts +27 -1
- package/dist/features/categories/components/CategoriesIndexPageView.js +24 -33
- package/dist/features/categories/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 +73 -12
- package/dist/features/categories/repository/categories.repository.js +203 -42
- 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/faq/utils/getHomepageFaqsCached.d.ts +20 -0
- package/dist/features/faq/utils/getHomepageFaqsCached.js +22 -0
- package/dist/features/homepage/components/MarketplaceHomepageView.js +13 -3
- package/dist/features/homepage/lib/live-stats.js +18 -8
- package/dist/features/media/MediaImage.js +19 -2
- package/dist/features/orders/schemas/firestore.d.ts +70 -0
- package/dist/features/orders/types/index.d.ts +55 -0
- package/dist/features/products/actions/product-actions.d.ts +0 -9
- package/dist/features/products/actions/product-actions.js +25 -0
- package/dist/features/products/columns/index.d.ts +0 -1
- package/dist/features/products/columns/index.js +5 -1
- package/dist/features/products/components/ArtStickersListView.js +7 -1
- package/dist/features/products/components/PrizeDrawsSection.js +5 -0
- 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/components/ProductsIndexPageView.js +1 -1
- 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/reviews/repository/reviews.repository.d.ts +22 -0
- package/dist/features/reviews/repository/reviews.repository.js +35 -0
- 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 +10 -29
- 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/media.d.ts +1 -1
- package/dist/seed/_helpers/media.js +44 -8
- 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/orders-seed-data.js +5 -3
- 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 +4 -1
- package/dist/server.js +11 -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/media-url.d.ts +26 -0
- package/dist/utils/media-url.js +144 -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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
+
import { type PhysicalLocationParts } from "../../../utils/physical-location";
|
|
2
3
|
interface PrintCenterStore {
|
|
3
4
|
id: string;
|
|
4
5
|
storeName: string;
|
|
@@ -14,7 +15,7 @@ interface PrintCenterProduct {
|
|
|
14
15
|
listingType?: string;
|
|
15
16
|
condition?: string;
|
|
16
17
|
stockCount?: number;
|
|
17
|
-
physicalLocation?:
|
|
18
|
+
physicalLocation?: PhysicalLocationParts;
|
|
18
19
|
barcodeId?: string;
|
|
19
20
|
}
|
|
20
21
|
interface PrintCenterOrder {
|
|
@@ -29,7 +30,7 @@ interface PrintCenterOrder {
|
|
|
29
30
|
price: number;
|
|
30
31
|
barcodeId?: string;
|
|
31
32
|
}[];
|
|
32
|
-
physicalLocation?:
|
|
33
|
+
physicalLocation?: PhysicalLocationParts;
|
|
33
34
|
}
|
|
34
35
|
interface PrintCenterViewProps {
|
|
35
36
|
store?: PrintCenterStore | null;
|
|
@@ -7,6 +7,7 @@ import { Heading, Text } from "../../../ui/components/Typography";
|
|
|
7
7
|
import { Stack, Row } from "../../../ui/components/Layout";
|
|
8
8
|
import { Button } from "../../../ui/components/Button";
|
|
9
9
|
import { FieldCheckbox } from "../../../ui/forms/FieldCheckbox";
|
|
10
|
+
import { formatPhysicalLocation, } from "../../../utils/physical-location";
|
|
10
11
|
function ProductLabel({ product }) {
|
|
11
12
|
const svgRef = useRef(null);
|
|
12
13
|
useEffect(() => {
|
|
@@ -29,10 +30,10 @@ function ProductLabel({ product }) {
|
|
|
29
30
|
}
|
|
30
31
|
}
|
|
31
32
|
}, [product.barcodeId]);
|
|
32
|
-
return (_jsxs(Stack, { surface: "card", padding: "sm", rounded: "sm", gap: "xs", className: "break-inside-avoid text-center w-[200px]", children: [product.barcodeId ? (_jsx("svg", { ref: svgRef })) : (_jsx(Text, { size: "xs", color: "muted", children: "No barcode" })), _jsx(Text, { size: "xs", weight: "medium", truncate: 1, children: product.name }), _jsxs(Text, { size: "xs", color: "muted", children: ["\u20B9", product.price.toLocaleString("en-IN")] }), product.physicalLocation && (_jsx(Text, { size: "xs", color: "muted", children: product.physicalLocation }))] }));
|
|
33
|
+
return (_jsxs(Stack, { surface: "card", padding: "sm", rounded: "sm", gap: "xs", className: "break-inside-avoid text-center w-[200px]", children: [product.barcodeId ? (_jsx("svg", { ref: svgRef })) : (_jsx(Text, { size: "xs", color: "muted", children: "No barcode" })), _jsx(Text, { size: "xs", weight: "medium", truncate: 1, children: product.name }), _jsxs(Text, { size: "xs", color: "muted", children: ["\u20B9", product.price.toLocaleString("en-IN")] }), formatPhysicalLocation(product.physicalLocation) && (_jsx(Text, { size: "xs", color: "muted", children: formatPhysicalLocation(product.physicalLocation) }))] }));
|
|
33
34
|
}
|
|
34
35
|
function PackingSlip({ order, brandName }) {
|
|
35
|
-
return (_jsxs(Stack, { surface: "card", padding: "md", rounded: "sm", className: "break-inside-avoid mb-4 max-w-[480px]", children: [_jsxs(Row, { justify: "between", className: "mb-2", children: [_jsx(Heading, { level: 5, children: brandName }), _jsxs(Text, { size: "xs", color: "muted", children: ["#", order.id] })] }), _jsx(Text, { size: "xs", color: "muted", children: new Date(order.createdAt).toLocaleDateString("en-IN") }), order.buyerDisplayName && _jsx(Text, { size: "sm", weight: "medium", children: order.buyerDisplayName }), order.buyerCity && _jsx(Text, { size: "xs", color: "muted", children: order.buyerCity }), order.physicalLocation && (_jsxs(Text, { size: "xs", color: "muted", children: ["Staging: ", order.physicalLocation] })), _jsx(Stack, { border: "top", padding: "t-xs", className: "mt-3", children: order.items.map((item, i) => (_jsxs(Row, { justify: "between", padding: "y-2xs", className: "border-b last:border-b-0", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", weight: "medium", children: item.productName }), item.barcodeId && _jsx(Text, { size: "xs", color: "muted", children: item.barcodeId })] }), _jsxs(Stack, { className: "text-right", children: [_jsxs(Text, { size: "xs", children: ["\u00D7", item.quantity] }), _jsxs(Text, { size: "xs", color: "muted", children: ["\u20B9", item.price.toLocaleString("en-IN")] })] })] }, i))) })] }));
|
|
36
|
+
return (_jsxs(Stack, { surface: "card", padding: "md", rounded: "sm", className: "break-inside-avoid mb-4 max-w-[480px]", children: [_jsxs(Row, { justify: "between", className: "mb-2", children: [_jsx(Heading, { level: 5, children: brandName }), _jsxs(Text, { size: "xs", color: "muted", children: ["#", order.id] })] }), _jsx(Text, { size: "xs", color: "muted", children: new Date(order.createdAt).toLocaleDateString("en-IN") }), order.buyerDisplayName && _jsx(Text, { size: "sm", weight: "medium", children: order.buyerDisplayName }), order.buyerCity && _jsx(Text, { size: "xs", color: "muted", children: order.buyerCity }), formatPhysicalLocation(order.physicalLocation) && (_jsxs(Text, { size: "xs", color: "muted", children: ["Staging: ", formatPhysicalLocation(order.physicalLocation)] })), _jsx(Stack, { border: "top", padding: "t-xs", className: "mt-3", children: order.items.map((item, i) => (_jsxs(Row, { justify: "between", padding: "y-2xs", className: "border-b last:border-b-0", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", weight: "medium", children: item.productName }), item.barcodeId && _jsx(Text, { size: "xs", color: "muted", children: item.barcodeId })] }), _jsxs(Stack, { className: "text-right", children: [_jsxs(Text, { size: "xs", children: ["\u00D7", item.quantity] }), _jsxs(Text, { size: "xs", color: "muted", children: ["\u20B9", item.price.toLocaleString("en-IN")] })] })] }, i))) })] }));
|
|
36
37
|
}
|
|
37
38
|
export function PrintCenterView({ store, brandName = "LetItRip", initialProducts = [], initialOrders = [], }) {
|
|
38
39
|
const [tab, setTab] = useState("labels");
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useApiMutation } from "@mohasinac/appkit/client";
|
|
4
4
|
import React, { useState, useCallback } from "react";
|
|
5
|
+
import { alertMetricsForScope } from "../../store-extensions/constants/alert-metrics";
|
|
5
6
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
6
7
|
import { Alert, Badge, Button, Div, Heading, Row, Section, Stack, Text, Toggle, useToast } from "../../../ui";
|
|
7
8
|
import { apiClient } from "../../../http";
|
|
@@ -39,15 +40,24 @@ const OPERATOR_OPTIONS = ANALYTICS_ALERT_OPERATORS.map((value) => ({
|
|
|
39
40
|
value,
|
|
40
41
|
label: OPERATOR_LABELS[value] ?? value,
|
|
41
42
|
}));
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
43
|
+
/*
|
|
44
|
+
* 🛑 DERIVED, never a hand-written list — that list WAS the defect.
|
|
45
|
+
*
|
|
46
|
+
* These seven values (daily_revenue, daily_orders, low_stock,
|
|
47
|
+
* cancelled_orders, pending_payouts, avg_rating, reviews_count) shared ZERO
|
|
48
|
+
* tokens with the four the seed wrote, and nothing validated either because
|
|
49
|
+
* `metric` was `string`. It stayed invisible because no evaluator read the
|
|
50
|
+
* field at all, so an alert a seller configured could never fire.
|
|
51
|
+
*
|
|
52
|
+
* Now from `alertMetricsForScope("seller")`, which the evaluator switches
|
|
53
|
+
* over exhaustively — so a metric the job cannot compute is not offerable.
|
|
54
|
+
*/
|
|
55
|
+
const METRIC_OPTIONS = alertMetricsForScope("seller").map(({ value, def }) => ({
|
|
56
|
+
value,
|
|
57
|
+
label: def.label,
|
|
58
|
+
}));
|
|
59
|
+
/** Helper text per metric, so "Orders in the window" says what it counts. */
|
|
60
|
+
const METRIC_DESCRIPTIONS = Object.fromEntries(alertMetricsForScope("seller").map(({ value, def }) => [value, def.describes]));
|
|
51
61
|
const WINDOW_OPTIONS = [
|
|
52
62
|
{ value: "1", label: "Last 1 hour" },
|
|
53
63
|
{ value: "6", label: "Last 6 hours" },
|
|
@@ -74,11 +84,11 @@ function AlertCard({ alert, onToggle, onDelete, }) {
|
|
|
74
84
|
const metricLabel = METRIC_OPTIONS.find((m) => m.value === alert.metric)?.label ?? alert.metric;
|
|
75
85
|
const opLabel = OPERATOR_OPTIONS.find((o) => o.value === alert.operator)?.label ?? alert.operator;
|
|
76
86
|
const windowLabel = WINDOW_OPTIONS.find((w) => w.value === String(alert.windowHours))?.label ?? `${alert.windowHours}h`;
|
|
77
|
-
return (_jsxs(Row, { surface: "card", padding: "sm", align: "start", justify: "between", gap: "md", children: [_jsxs(Div, { className: "flex-1 min-w-0", children: [_jsxs(Row, { className: "mb-1", gap: "sm", wrap: true, children: [_jsx(Text, { size: "sm", weight: "medium", color: "primary", children: alert.label }), _jsx(Badge, { variant: alert.isActive ? "success" : "default", children: alert.isActive ? "Active" : "Paused" })] }), _jsxs(Text, { className: "mb-1", color: "muted", size: "xs", children: [metricLabel, " ", opLabel, " ", alert.threshold, " \u00B7 ", windowLabel] }), _jsx(Row, { gap: "xs", wrap: true, children: alert.notifyChannels.map((ch) => (_jsx(Badge, { variant: OPERATOR_BADGE[alert.operator] ?? "default", children: ch }, ch))) }), alert.lastTriggeredAt && (_jsxs(Text, { className: "mt-1", color: "faint", size: "xs", children: ["Last triggered: ", new Date(alert.lastTriggeredAt).toLocaleString("en-IN")] }))] }), _jsxs(Row, { className: "shrink-0", align: "center", gap: "sm", children: [_jsx(Toggle, { checked: alert.isActive, onChange: (v) => onToggle(alert.id, v), size: "sm" }), _jsx(Button, { variant: "ghost", size: "sm", action: ACTIONS.STORE["delete-analytics-alert"], onClick: () => onDelete(alert.id), className: "text-error hover:text-error" })] })] }));
|
|
87
|
+
return (_jsxs(Row, { surface: "card", padding: "sm", align: "start", justify: "between", gap: "md", children: [_jsxs(Div, { className: "flex-1 min-w-0", children: [_jsxs(Row, { className: "mb-1", gap: "sm", wrap: true, children: [_jsx(Text, { size: "sm", weight: "medium", color: "primary", children: alert.label }), _jsx(Badge, { variant: alert.isActive ? "success" : "default", children: alert.isActive ? "Active" : "Paused" })] }), _jsxs(Text, { className: "mb-1", color: "muted", size: "xs", children: [metricLabel, " ", opLabel, " ", alert.threshold, " \u00B7 ", windowLabel] }), METRIC_DESCRIPTIONS[alert.metric] ? (_jsx(Text, { className: "mb-1", color: "muted", size: "xs", children: METRIC_DESCRIPTIONS[alert.metric] })) : null, alert.lastTriggeredAt ? (_jsxs(Text, { className: "mb-1", color: "muted", size: "xs", children: ["Last fired ", new Date(alert.lastTriggeredAt).toLocaleString(), alert.lastObservedValue != null ? ` at ${alert.lastObservedValue}` : ""] })) : null, _jsx(Row, { gap: "xs", wrap: true, children: alert.notifyChannels.map((ch) => (_jsx(Badge, { variant: OPERATOR_BADGE[alert.operator] ?? "default", children: ch }, ch))) }), alert.lastTriggeredAt && (_jsxs(Text, { className: "mt-1", color: "faint", size: "xs", children: ["Last triggered: ", new Date(alert.lastTriggeredAt).toLocaleString("en-IN")] }))] }), _jsxs(Row, { className: "shrink-0", align: "center", gap: "sm", children: [_jsx(Toggle, { checked: alert.isActive, onChange: (v) => onToggle(alert.id, v), size: "sm" }), _jsx(Button, { variant: "ghost", size: "sm", action: ACTIONS.STORE["delete-analytics-alert"], onClick: () => onDelete(alert.id), className: "text-error hover:text-error" })] })] }));
|
|
78
88
|
}
|
|
79
89
|
const EMPTY_DRAFT = {
|
|
80
90
|
label: "",
|
|
81
|
-
metric: "
|
|
91
|
+
metric: "orders_count",
|
|
82
92
|
operator: ">",
|
|
83
93
|
threshold: "",
|
|
84
94
|
windowHours: "24",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { formatPhysicalLocation } from "../../../utils/physical-location";
|
|
3
4
|
import { OrderAddonBadges } from "../../orders/components/OrderAddonBadges";
|
|
4
5
|
import { normalizeError } from "../../../errors/normalize";
|
|
5
6
|
import { Row, SIEVE_OP, sieveFilter } from "@mohasinac/appkit/client";
|
|
@@ -362,7 +363,11 @@ export function SellerOrdersView({ orderDetailApiBase = SELLER_ENDPOINTS.ORDERS,
|
|
|
362
363
|
key: "physicalLocation",
|
|
363
364
|
header: "Staging",
|
|
364
365
|
className: "w-28",
|
|
365
|
-
render: (row) =>
|
|
366
|
+
render: (row) => {
|
|
367
|
+
// Shared formatter — see utils/physical-location.ts.
|
|
368
|
+
const loc = formatPhysicalLocation(row.physicalLocation);
|
|
369
|
+
return loc ? (_jsx(Span, { size: "xs", className: "font-mono", color: "muted", children: loc })) : (_jsx(Span, { size: "xs", color: "muted", children: "\u2014" }));
|
|
370
|
+
},
|
|
366
371
|
},
|
|
367
372
|
{
|
|
368
373
|
key: "updatedAt",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
-
import { Alert, Badge, Checkbox, Div, FormField, FormGroup, Heading, Label, Row, Stack, Text, Toggle } from "../../../ui";
|
|
5
|
+
import { Alert, Badge, Checkbox, Div, FormField, FormGroup, Heading, Label, Row, Span, Stack, Text, Toggle } from "../../../ui";
|
|
6
6
|
import { StackedViewShell } from "../../../ui";
|
|
7
7
|
import { FormShellContext, useFormShellState, applyZodIssues, FormErrorSummary } from "../../../ui/forms";
|
|
8
8
|
import { SectionForm, useSectionFormNav, annotate } from "../../shell";
|
|
@@ -54,6 +54,17 @@ const payoutSettingsDraftSchema = z.object({
|
|
|
54
54
|
}),
|
|
55
55
|
minimumThreshold: annotate(z.string(), { section: "preferences", order: 2, row: "pair" }),
|
|
56
56
|
emiEnabled: annotate(z.boolean(), { section: "preferences", order: 3, row: "quarter" }),
|
|
57
|
+
/*
|
|
58
|
+
* How the seller types prices. Lives in PAYOUT settings, not the store
|
|
59
|
+
* profile, because payout is what it actually changes: at 5% a ₹999
|
|
60
|
+
* inclusive listing pays out ₹951.43, while the same number typed ex-tax is
|
|
61
|
+
* stored as ₹1,048.95 and pays out ₹999.00.
|
|
62
|
+
*
|
|
63
|
+
* 🛑 It does NOT re-price existing listings — the gross-up happens at write
|
|
64
|
+
* time — so the label has to say so, or a seller flips it and assumes their
|
|
65
|
+
* catalogue moved.
|
|
66
|
+
*/
|
|
67
|
+
pricesIncludeGst: annotate(z.boolean(), { section: "preferences", order: 4, row: "quarter" }),
|
|
57
68
|
}).superRefine((v, ctx) => {
|
|
58
69
|
if (v.method === "upi") {
|
|
59
70
|
if (!v.upiId.trim()) {
|
|
@@ -89,6 +100,8 @@ const DEFAULT_DRAFT = {
|
|
|
89
100
|
autoPayout: true,
|
|
90
101
|
minimumThreshold: "",
|
|
91
102
|
emiEnabled: false,
|
|
103
|
+
// Inclusive is the documented default — see StoreDocument.pricesIncludeGst.
|
|
104
|
+
pricesIncludeGst: true,
|
|
92
105
|
};
|
|
93
106
|
export function SellerPayoutSettingsView({ apiBase = SELLER_ENDPOINTS.PAYOUT_SETTINGS }) {
|
|
94
107
|
const [draft, setDraft] = useState(DEFAULT_DRAFT);
|
|
@@ -119,6 +132,8 @@ export function SellerPayoutSettingsView({ apiBase = SELLER_ENDPOINTS.PAYOUT_SET
|
|
|
119
132
|
? String(res.data.preferences.minimumThreshold)
|
|
120
133
|
: "",
|
|
121
134
|
emiEnabled: res?.data?.emiEnabled ?? false,
|
|
135
|
+
// Absent means inclusive, matching the schema default exactly.
|
|
136
|
+
pricesIncludeGst: res?.data?.pricesIncludeGst ?? true,
|
|
122
137
|
});
|
|
123
138
|
})
|
|
124
139
|
.catch(console.error)
|
|
@@ -146,6 +161,7 @@ export function SellerPayoutSettingsView({ apiBase = SELLER_ENDPOINTS.PAYOUT_SET
|
|
|
146
161
|
const body = {
|
|
147
162
|
...methodFields,
|
|
148
163
|
emiEnabled: draft.emiEnabled,
|
|
164
|
+
pricesIncludeGst: draft.pricesIncludeGst,
|
|
149
165
|
taxInfo: {
|
|
150
166
|
gstin: draft.gstin.trim(),
|
|
151
167
|
pan: draft.pan.trim(),
|
|
@@ -212,8 +228,8 @@ export function SellerPayoutSettingsView({ apiBase = SELLER_ENDPOINTS.PAYOUT_SET
|
|
|
212
228
|
{
|
|
213
229
|
id: "preferences",
|
|
214
230
|
label: "Preferences",
|
|
215
|
-
fields: ["autoPayout", "minimumThreshold", "emiEnabled"],
|
|
216
|
-
render: ({ values, onChange }) => (_jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 3, className: "mb-2", children: "Payout Preferences" }), _jsx(Toggle, { checked: values.autoPayout, onChange: (checked) => onChange({ autoPayout: checked }), label: "Enable auto-payout \u2014 automatically transfer earnings on schedule", disabled: busy }), _jsx(FormField, { name: "minimumThreshold", label: "Minimum Payout Threshold (\u20B9)", type: "number", value: values.minimumThreshold, onChange: (v) => onChange({ minimumThreshold: v }), placeholder: "e.g. 500", helpText: "Payouts are held until your balance exceeds this amount. Leave blank for no minimum.", disabled: busy }), _jsxs(Div, { className: `${__P.p3} bg-[var(--appkit-color-surface-alt)] border border-[var(--appkit-color-border)]`, rounded: "lg", children: [_jsx(Text, { className: "mb-1", size: "sm", weight: "medium", children: "Payout Schedule" }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: "Auto-payouts run every Monday for the previous week's settled orders. Manual payouts can be requested from the Payouts page at any time." })] }), _jsx(Toggle, { checked: values.emiEnabled, onChange: (checked) => onChange({ emiEnabled: checked }), label: "Offer EMI financing on eligible orders", disabled: busy }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: "Only shown to buyers when platform-wide EMI is also enabled and their cart subtotal from your store exceeds the site's minimum order value. Orders on an EMI plan hold your payout until every installment is paid, unless you mark the item \"ship before EMI completes\"." })] })),
|
|
231
|
+
fields: ["autoPayout", "minimumThreshold", "emiEnabled", "pricesIncludeGst"],
|
|
232
|
+
render: ({ values, onChange }) => (_jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 3, className: "mb-2", children: "Payout Preferences" }), _jsx(Toggle, { checked: values.autoPayout, onChange: (checked) => onChange({ autoPayout: checked }), label: "Enable auto-payout \u2014 automatically transfer earnings on schedule", disabled: busy }), _jsx(FormField, { name: "minimumThreshold", label: "Minimum Payout Threshold (\u20B9)", type: "number", value: values.minimumThreshold, onChange: (v) => onChange({ minimumThreshold: v }), placeholder: "e.g. 500", helpText: "Payouts are held until your balance exceeds this amount. Leave blank for no minimum.", disabled: busy }), _jsxs(Div, { className: `${__P.p3} bg-[var(--appkit-color-surface-alt)] border border-[var(--appkit-color-border)]`, rounded: "lg", children: [_jsx(Text, { className: "mb-1", size: "sm", weight: "medium", children: "Payout Schedule" }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: "Auto-payouts run every Monday for the previous week's settled orders. Manual payouts can be requested from the Payouts page at any time." })] }), _jsx(Toggle, { checked: values.emiEnabled, onChange: (checked) => onChange({ emiEnabled: checked }), label: "Offer EMI financing on eligible orders", disabled: busy }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: "Only shown to buyers when platform-wide EMI is also enabled and their cart subtotal from your store exceeds the site's minimum order value. Orders on an EMI plan hold your payout until every installment is paid, unless you mark the item \"ship before EMI completes\"." }), _jsx(Toggle, { checked: values.pricesIncludeGst, onChange: (checked) => onChange({ pricesIncludeGst: checked }), label: "The prices I enter already include GST", disabled: busy }), _jsxs(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: ["Buyers always see a tax-inclusive price, so this only changes how your own entry is read. Leave it", " ", _jsx(Span, { weight: "semibold", children: "on" }), " and a \u20B9999 listing at 5% GST means the buyer pays \u20B9999 and you earn \u20B9951.43 \u2014 the \u20B947.57 is GST we collect and remit for you. Turn it", " ", _jsx(Span, { weight: "semibold", children: "off" }), " and \u20B9999 is treated as your ex-GST price: the listing is shown at \u20B91,048.95 and you earn \u20B9999."] }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: "Changing this does not re-price listings you have already published. It applies to products you save from now on." })] })),
|
|
217
233
|
},
|
|
218
234
|
], [busy, current]);
|
|
219
235
|
const { openIds, setOpenIds, goToSection, fieldToSectionIndex, sectionMeta } = useSectionFormNav(sections, draft);
|
|
@@ -9,6 +9,35 @@ export interface SellerProductDraft {
|
|
|
9
9
|
brand?: string;
|
|
10
10
|
condition?: string;
|
|
11
11
|
tags?: string[];
|
|
12
|
+
authenticity?: "original" | "reproduction" | "unverified";
|
|
13
|
+
specifications?: {
|
|
14
|
+
name: string;
|
|
15
|
+
value: string;
|
|
16
|
+
unit?: string;
|
|
17
|
+
}[];
|
|
18
|
+
/** Feature IDS, never prose — `array-contains` is what faceting runs on. */
|
|
19
|
+
features?: string[];
|
|
20
|
+
/**
|
|
21
|
+
* 🛑 OPTIONAL, on purpose, and the reason is C5a.
|
|
22
|
+
*
|
|
23
|
+
* The figure a carrier bills on is the PACKED PARCEL, not the item: a bey is
|
|
24
|
+
* ~30 g and ~300 g boxed. A required product weight would therefore be an
|
|
25
|
+
* item weight that systematically under-estimates every booking — and since
|
|
26
|
+
* the booking is capped at the amount collected from the buyer, systematic
|
|
27
|
+
* under-estimation means silently absorbing the difference.
|
|
28
|
+
*
|
|
29
|
+
* So the category's PACKED default drives the checkout estimate, this field
|
|
30
|
+
* refines it when a seller bothers, and the real parcel figure is entered at
|
|
31
|
+
* shipment time, when the seller has the box in front of them.
|
|
32
|
+
*/
|
|
33
|
+
weightG?: number;
|
|
34
|
+
dimensionsCm?: {
|
|
35
|
+
l: number;
|
|
36
|
+
w: number;
|
|
37
|
+
h: number;
|
|
38
|
+
};
|
|
39
|
+
appliedTemplateId?: string;
|
|
40
|
+
appliedTemplateBody?: string;
|
|
12
41
|
mainImage?: string;
|
|
13
42
|
images?: string[];
|
|
14
43
|
video?: MediaField;
|
|
@@ -121,10 +150,29 @@ export interface SellerProductShellProps {
|
|
|
121
150
|
onChange: (v: string) => void;
|
|
122
151
|
}) => React.ReactNode;
|
|
123
152
|
/**
|
|
124
|
-
* Render
|
|
125
|
-
*
|
|
153
|
+
* Render the category description-template picker, between the
|
|
154
|
+
* Category/Condition row and the Description field.
|
|
155
|
+
*
|
|
156
|
+
* 🛑 The slot existed with ZERO consumers and a single `onApply` prop, which
|
|
157
|
+
* was not enough to build a picker with: it could apply a body but could not
|
|
158
|
+
* know WHICH body to fetch, or whether applying one would destroy something
|
|
159
|
+
* the seller had written. C2b widens it rather than adding a second slot.
|
|
160
|
+
*
|
|
161
|
+
* `isDescriptionDirty` is the one that matters. It is the clobber rule in a
|
|
162
|
+
* boolean: true when the description is non-empty after trim AND not
|
|
163
|
+
* byte-identical to a body applied this session. The picker must not
|
|
164
|
+
* overwrite when it is true — it offers a "Replace description" action
|
|
165
|
+
* instead, which is destructive and therefore carries a confirmation.
|
|
126
166
|
*/
|
|
127
167
|
renderTemplateSelector?: (props: {
|
|
168
|
+
/** The leaf the seller has chosen. Empty until they pick one. */
|
|
169
|
+
categoryId: string;
|
|
170
|
+
/** Drives which `variant` is fetched — new_in_box vs pre_owned. */
|
|
171
|
+
condition: string;
|
|
172
|
+
/** Resolved `{{slot}}` values the picker fills the body with. */
|
|
173
|
+
context: Record<string, string>;
|
|
174
|
+
/** See above. The picker must not silently overwrite when true. */
|
|
175
|
+
isDescriptionDirty: boolean;
|
|
128
176
|
onApply: (partial: Partial<SellerProductDraft>) => void;
|
|
129
177
|
}) => React.ReactNode;
|
|
130
178
|
/** Called with current draft when user clicks "Save as Template". */
|
|
@@ -14,6 +14,7 @@ import { pluginFor } from "../../../_internal/shared/listing-types/_registry";
|
|
|
14
14
|
import { DEFAULT_MIN_OFFER_PERCENT } from "../../../_internal/shared/features/offers/config";
|
|
15
15
|
import { PRODUCT_MAX_IMAGES, PRODUCT_MAX_VIDEOS, PRODUCT_IMAGE_INDEX_MAX, } from "../../../_internal/shared/media/limits";
|
|
16
16
|
import { isFinalSale } from "../../products/constants/final-sale";
|
|
17
|
+
import { ProductFeaturesSelector } from "../../products/components/ProductFeaturesSelector";
|
|
17
18
|
import { normalizeError } from "../../../errors/normalize";
|
|
18
19
|
/**
|
|
19
20
|
* "bundle" is a categoryType, not a ListingType, so it can't live in
|
|
@@ -76,7 +77,74 @@ function fromRupeesString(rupeeStr) {
|
|
|
76
77
|
}
|
|
77
78
|
// ── Step 1: Basic ─────────────────────────────────────────────────────────────
|
|
78
79
|
function StepBasic({ values, onChange, renderCategorySelector, renderBrandSelector, renderTemplateSelector, }) {
|
|
79
|
-
|
|
80
|
+
/*
|
|
81
|
+
* 🛑 The clobber rule, in one expression.
|
|
82
|
+
*
|
|
83
|
+
* Dirty means: non-empty after trim, AND not byte-identical to a body this
|
|
84
|
+
* session applied. A seller who has typed anything owns the field; a seller
|
|
85
|
+
* looking at a body we filled in does not, so a category change may refresh
|
|
86
|
+
* it silently.
|
|
87
|
+
*
|
|
88
|
+
* Byte-identical rather than "did they focus the box": a seller can click
|
|
89
|
+
* into the description, change nothing, and click away, and that must not
|
|
90
|
+
* count as authorship.
|
|
91
|
+
*/
|
|
92
|
+
const description = values.description ?? "";
|
|
93
|
+
const isDescriptionDirty = description.trim() !== "" && description !== (values.appliedTemplateBody ?? "");
|
|
94
|
+
/*
|
|
95
|
+
* The `{{slot}}` values the picker interpolates. Only what the draft
|
|
96
|
+
* genuinely knows — an unresolvable slot becomes the empty string and the
|
|
97
|
+
* picker reports it, which is `interpolate`'s contract. Never a literal
|
|
98
|
+
* `{{series}}` in a published description.
|
|
99
|
+
*
|
|
100
|
+
* `condition` resolves to the LABEL, not the raw enum: a body reading
|
|
101
|
+
* "Condition: like_new" is a template leaking its storage format.
|
|
102
|
+
*/
|
|
103
|
+
const templateContext = {
|
|
104
|
+
title: values.title ?? "",
|
|
105
|
+
brand: values.brand ?? "",
|
|
106
|
+
condition: CONDITION_OPTIONS.find((o) => o.value === (values.condition ?? "new"))?.label ?? "",
|
|
107
|
+
siteName: "LetItRip",
|
|
108
|
+
};
|
|
109
|
+
return (_jsxs(Stack, { gap: "md", children: [_jsx(FormField, { name: "title", label: "Title", type: "text", value: values.title ?? "", onChange: (v) => onChange({ title: v }), placeholder: "e.g. Charizard Base Set PSA 9" }), _jsxs(FormGroup, { columns: 2, children: [renderCategorySelector ? (_jsxs(_Fragment, { children: [_jsx(Text, { className: "text-[var(--appkit-color-text)] mb-1", size: "sm", weight: "medium", children: "Category" }), renderCategorySelector({ value: values.category ?? "", onChange: (v) => onChange({ category: v }) })] })) : (_jsx(FormField, { name: "category", label: "Category", type: "text", value: values.category ?? "", onChange: (v) => onChange({ category: v }), placeholder: "e.g. Trading Cards" })), _jsx(FormField, { name: "condition", label: "Condition", type: "select", value: values.condition ?? "new", onChange: (v) => onChange({ condition: v }), options: CONDITION_OPTIONS })] }), renderTemplateSelector?.({
|
|
110
|
+
categoryId: values.category ?? "",
|
|
111
|
+
condition: values.condition ?? "new",
|
|
112
|
+
context: templateContext,
|
|
113
|
+
isDescriptionDirty,
|
|
114
|
+
onApply: onChange,
|
|
115
|
+
}), _jsx(FormField, { name: "description", label: "Description", type: "textarea", value: description, onChange: (v) => onChange({ description: v }), placeholder: "Describe your listing in detail\u2026" }), renderBrandSelector ? (_jsxs(_Fragment, { children: [_jsx(Text, { className: "text-[var(--appkit-color-text)] mb-1", size: "sm", weight: "medium", children: "Brand" }), renderBrandSelector({ value: values.brand ?? "", onChange: (v) => onChange({ brand: v }) })] })) : (_jsx(FormField, { name: "brand", label: "Brand", type: "text", value: values.brand ?? "", onChange: (v) => onChange({ brand: v }), placeholder: "e.g. Pok\u00E9mon Company" })), _jsx(TagInput, { label: "Tags", value: values.tags ?? [], onChange: (tags) => onChange({ tags }), placeholder: "pokemon, psa9, charizard, holo" }), _jsx(BarcodeField, { value: values.barcodeId ?? "", onChange: (v) => onChange({ barcodeId: v || undefined }), onScan: (v) => onChange({ barcodeId: v }), helperText: "Leave blank to auto-generate. Scan a pre-printed sticker to link it." })] }));
|
|
116
|
+
}
|
|
117
|
+
// ── Step 1b: Details — the inherited-but-editable fields (C2c) ───────────────
|
|
118
|
+
const AUTHENTICITY_OPTIONS = [
|
|
119
|
+
{ value: "original", label: "Original — licensed manufacture (Takara, Takara Tomy, Hasbro…)" },
|
|
120
|
+
{ value: "reproduction", label: "Reproduction / midfake — a copy with no manufacturer's marque" },
|
|
121
|
+
{ value: "unverified", label: "Unverified — I cannot confirm which it is" },
|
|
122
|
+
];
|
|
123
|
+
/**
|
|
124
|
+
* 🛑 Three options, and there is deliberately no fourth.
|
|
125
|
+
*
|
|
126
|
+
* A poor-quality counterfeit — brittle plastic, cadmium or lead in the metal
|
|
127
|
+
* parts — is not a listing state this marketplace offers. It is a scam report.
|
|
128
|
+
* Offering "fake" as a dropdown value would make selling one a supported
|
|
129
|
+
* workflow.
|
|
130
|
+
*
|
|
131
|
+
* `unverified` exists so a seller who genuinely does not know has an honest
|
|
132
|
+
* answer available. Without it the only way to list is to claim something they
|
|
133
|
+
* cannot stand behind, and the field stops meaning anything.
|
|
134
|
+
*/
|
|
135
|
+
function StepDetails({ values, onChange, storeId, listingType, }) {
|
|
136
|
+
const specs = values.specifications ?? [];
|
|
137
|
+
const setSpecs = (next) => onChange({ specifications: next });
|
|
138
|
+
const dims = values.dimensionsCm;
|
|
139
|
+
const setDim = (key, raw) => {
|
|
140
|
+
const n = Number(raw);
|
|
141
|
+
const base = dims ?? { l: 0, w: 0, h: 0 };
|
|
142
|
+
const next = { ...base, [key]: Number.isFinite(n) ? n : 0 };
|
|
143
|
+
// All three or none: a box with a length and no width is not a dimension,
|
|
144
|
+
// and the shipping estimate reads all three or ignores the field.
|
|
145
|
+
onChange({ dimensionsCm: next.l > 0 && next.w > 0 && next.h > 0 ? next : undefined });
|
|
146
|
+
};
|
|
147
|
+
return (_jsxs(Stack, { gap: "md", children: [_jsx(FormField, { name: "authenticity", label: "Authenticity", type: "select", required: true, value: values.authenticity ?? "", onChange: (v) => onChange({ authenticity: v }), options: [{ value: "", label: "Choose one…" }, ...AUTHENTICITY_OPTIONS] }), _jsx(Text, { size: "xs", color: "muted", children: "Shown on the listing whenever it is not Original. A reproduction is a legitimate thing to sell here as long as it is labelled \u2014 what is not allowed is selling one as the real thing. If you believe an item is a cheap counterfeit, report it rather than listing it." }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", children: "Features" }), _jsx(Text, { size: "xs", color: "muted", children: "Condition, authenticity, release type, battle type and spin direction. Some are filled in from the category when you save \u2014 remove any that do not apply and your choice sticks." }), _jsx(ProductFeaturesSelector, { value: values.features ?? [], onChange: (next) => onChange({ features: next }), productType: listingType === "bundle" ? "all" : listingType, ...(storeId ? { storeId } : {}) })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", children: "Specifications" }), _jsx(Text, { size: "xs", color: "muted", children: "Left blank, the category fills these in. Anything you enter here is kept." }), specs.map((row, i) => (_jsxs(FormGroup, { columns: 3, children: [_jsx(FormField, { name: `spec-name-${i}`, label: "Name", type: "text", value: row.name, onChange: (v) => setSpecs(specs.map((r, j) => (j === i ? { ...r, name: v } : r))) }), _jsx(FormField, { name: `spec-value-${i}`, label: "Value", type: "text", value: row.value, onChange: (v) => setSpecs(specs.map((r, j) => (j === i ? { ...r, value: v } : r))) }), _jsxs(Row, { gap: "sm", align: "end", children: [_jsx(FormField, { name: `spec-unit-${i}`, label: "Unit", type: "text", value: row.unit ?? "", onChange: (v) => setSpecs(specs.map((r, j) => (j === i ? { ...r, unit: v } : r))) }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => setSpecs(specs.filter((_, j) => j !== i)), children: "Remove" })] })] }, i))), _jsx(Button, { variant: "secondary", size: "sm", onClick: () => setSpecs([...specs, { name: "", value: "" }]), children: "Add a specification" })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", children: "Weight and size (optional)" }), _jsx(Text, { size: "xs", color: "muted", children: "\uD83D\uDED1 Leave these blank unless you know them \u2014 the category's own packed-parcel figures are used for the shipping estimate, and you enter the real weight of the box when you book the shipment. A figure here should be the PACKED weight, not the bare item." }), _jsxs(FormGroup, { columns: 4, children: [_jsx(FormField, { name: "weightG", label: "Packed weight (g)", type: "number", value: values.weightG != null ? String(values.weightG) : "", onChange: (v) => onChange({ weightG: v.trim() === "" ? undefined : Math.max(0, Number(v) || 0) }) }), _jsx(FormField, { name: "dimL", label: "Length (cm)", type: "number", value: dims?.l ? String(dims.l) : "", onChange: (v) => setDim("l", v) }), _jsx(FormField, { name: "dimW", label: "Width (cm)", type: "number", value: dims?.w ? String(dims.w) : "", onChange: (v) => setDim("w", v) }), _jsx(FormField, { name: "dimH", label: "Height (cm)", type: "number", value: dims?.h ? String(dims.h) : "", onChange: (v) => setDim("h", v) })] })] })] }));
|
|
80
148
|
}
|
|
81
149
|
// ── Step 2: Media ─────────────────────────────────────────────────────────────
|
|
82
150
|
function StepMedia({ values, onChange, storeSlug = "store", }) {
|
|
@@ -306,13 +374,7 @@ function StepShipping({ values, onChange, renderAddressSelector, }) {
|
|
|
306
374
|
] }), renderAddressSelector ? (_jsxs(_Fragment, { children: [_jsx(Text, { className: "text-[var(--appkit-color-text)] mb-1", size: "sm", weight: "medium", children: "Pickup Address (optional)" }), renderAddressSelector({
|
|
307
375
|
value: values.pickupAddressId ?? "",
|
|
308
376
|
onChange: (v) => onChange({ pickupAddressId: v }),
|
|
309
|
-
})] })) : (_jsx(StoreAddressSelectorCreate, { label: "Pickup Address (optional)", value: values.pickupAddressId ?? "", onChange: (id) => onChange({ pickupAddressId: id }) })), _jsx(Toggle, { checked: !!values.insurance, onChange: (checked) => onChange({ insurance: checked, insuranceCost: checked ? values.insuranceCost ?? 0 : undefined }), label: "Offer shipping insurance" }), values.insurance && (_jsx(FormField, { name: "insuranceCost", label: "Insurance Cost (\u20B9)", type: "number", value: toRupeesString(values.insuranceCost), onChange: (v) => onChange({ insuranceCost: fromRupeesString(v) }), placeholder: "0" }))
|
|
310
|
-
{ value: "0", label: "Exempt (0%)" },
|
|
311
|
-
{ value: "5", label: "5%" },
|
|
312
|
-
{ value: "12", label: "12%" },
|
|
313
|
-
{ value: "18", label: "18%" },
|
|
314
|
-
{ value: "28", label: "28%" },
|
|
315
|
-
] }), _jsx(FormField, { name: "hsnCode", label: "HSN Code", value: values.hsnCode ?? "", onChange: (v) => onChange({ hsnCode: v }), placeholder: "e.g. 9503" })] }));
|
|
377
|
+
})] })) : (_jsx(StoreAddressSelectorCreate, { label: "Pickup Address (optional)", value: values.pickupAddressId ?? "", onChange: (id) => onChange({ pickupAddressId: id }) })), _jsx(Toggle, { checked: !!values.insurance, onChange: (checked) => onChange({ insurance: checked, insuranceCost: checked ? values.insuranceCost ?? 0 : undefined }), label: "Offer shipping insurance" }), values.insurance && (_jsx(FormField, { name: "insuranceCost", label: "Insurance Cost (\u20B9)", type: "number", value: toRupeesString(values.insuranceCost), onChange: (v) => onChange({ insuranceCost: fromRupeesString(v) }), placeholder: "0" }))] }));
|
|
316
378
|
}
|
|
317
379
|
// ── Step: Returns ─────────────────────────────────────────────────────────
|
|
318
380
|
/**
|
|
@@ -516,13 +578,43 @@ productId, onSave, onPublish, onDiscard, isLoading = false, storeSlug, renderCat
|
|
|
516
578
|
render: ({ values, onChange }) => (_jsx(StepMedia, { values: values, onChange: onChange, storeSlug: storeSlug })),
|
|
517
579
|
schema: sectionSchemas.media,
|
|
518
580
|
},
|
|
581
|
+
/*
|
|
582
|
+
* 🛑 Details sits between Basic and the type-specific step, not at the end.
|
|
583
|
+
*
|
|
584
|
+
* `authenticity` is required, and a required field buried after the
|
|
585
|
+
* optional ones is a field sellers reach by being bounced back to it from
|
|
586
|
+
* a failed submit. It also reads in the right order: what it IS, then what
|
|
587
|
+
* it is LIKE, then what it costs.
|
|
588
|
+
*/
|
|
589
|
+
{
|
|
590
|
+
id: "details",
|
|
591
|
+
label: "Details",
|
|
592
|
+
required: true,
|
|
593
|
+
fields: ["authenticity", "features", "specifications", "weightG", "dimensionsCm"],
|
|
594
|
+
render: ({ values, onChange }) => (_jsx(StepDetails, { values: values, onChange: onChange, listingType: listingType, ...(storeSlug ? { storeId: storeSlug } : {}) })),
|
|
595
|
+
/*
|
|
596
|
+
* The one required field in the section, and the message says what to do
|
|
597
|
+
* rather than naming the field — "Authenticity is required" tells a
|
|
598
|
+
* seller nothing they cannot see.
|
|
599
|
+
*/
|
|
600
|
+
schema: z
|
|
601
|
+
.object({
|
|
602
|
+
authenticity: z.enum(["original", "reproduction", "unverified"], {
|
|
603
|
+
errorMap: () => ({
|
|
604
|
+
message: "Say whether this is an original, a reproduction, or that you cannot confirm",
|
|
605
|
+
}),
|
|
606
|
+
}),
|
|
607
|
+
})
|
|
608
|
+
.passthrough(),
|
|
609
|
+
},
|
|
519
610
|
...(typeSpecificSection ? [typeSpecificSection] : []),
|
|
520
611
|
{
|
|
521
612
|
id: "pricing",
|
|
522
613
|
label: "Pricing",
|
|
523
614
|
required: true,
|
|
524
615
|
quick: true,
|
|
525
|
-
|
|
616
|
+
// `gstRate` / `hsnCode` deliberately absent — admin-only, see StepPricing.
|
|
617
|
+
fields: ["price", "compareAtPrice", "stockQuantity", "allowOffers", "minOfferPercent"],
|
|
526
618
|
render: ({ values, onChange }) => (_jsx(StepPricing, { values: values, onChange: onChange, listingType: listingType })),
|
|
527
619
|
// Built per render because whether stock is required depends on the
|
|
528
620
|
// listing type's own plugin — a digital code has no stock to speak of.
|
|
@@ -5,6 +5,18 @@ export interface SellerProductsFilterFieldsProps {
|
|
|
5
5
|
id: string;
|
|
6
6
|
label: string;
|
|
7
7
|
}>;
|
|
8
|
+
/**
|
|
9
|
+
* The stock-level options, PASSED IN rather than declared here.
|
|
10
|
+
*
|
|
11
|
+
* They are derived from `LOW_STOCK_THRESHOLD`, and the predicate that reads
|
|
12
|
+
* the chosen value lives in `SellerProductsView` beside them — so declaring
|
|
13
|
+
* a second copy of the labels here is how the picker and the filter end up
|
|
14
|
+
* disagreeing about what "low" means.
|
|
15
|
+
*/
|
|
16
|
+
stockStateOptions: {
|
|
17
|
+
value: string;
|
|
18
|
+
label: string;
|
|
19
|
+
}[];
|
|
8
20
|
onChange: (next: Record<string, string>) => void;
|
|
9
21
|
}
|
|
10
|
-
export declare function SellerProductsFilterFields({ pendingFilters, statusOptions, onChange, }: SellerProductsFilterFieldsProps): React.JSX.Element;
|
|
22
|
+
export declare function SellerProductsFilterFields({ pendingFilters, statusOptions, stockStateOptions, onChange, }: SellerProductsFilterFieldsProps): React.JSX.Element;
|
|
@@ -12,7 +12,7 @@ const CONDITION_OPTIONS = [
|
|
|
12
12
|
{ value: "refurbished", label: "Refurbished" },
|
|
13
13
|
{ value: "for-parts", label: "For parts" },
|
|
14
14
|
];
|
|
15
|
-
export function SellerProductsFilterFields({ pendingFilters, statusOptions, onChange, }) {
|
|
15
|
+
export function SellerProductsFilterFields({ pendingFilters, statusOptions, stockStateOptions, onChange, }) {
|
|
16
16
|
const patch = (k, v) => onChange({ ...pendingFilters, [k]: v });
|
|
17
|
-
return (_jsxs(_Fragment, { children: [_jsx(FilterChipGroup, { label: "Status", tabs: statusOptions, value: pendingFilters.status ?? "", onChange: (id) => patch("status", id) }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", weight: "semibold", color: "muted", transform: "uppercase", children: "Category" }), _jsx(CategoryInlineSelect, { value: pendingFilters.category ?? "", onChange: (v) => patch("category", v), placeholder: "Search categories\u2026" })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", weight: "semibold", color: "muted", transform: "uppercase", children: "Brand" }), _jsx(BrandInlineSelect, { value: pendingFilters.brand ?? "", onChange: (v) => patch("brand", v), placeholder: "Search brands\u2026", allowCreate: false })] }), _jsx(FieldSelect, { name: "condition", label: "Condition", value: pendingFilters.condition ?? "", onChange: (v) => patch("condition", v), options: CONDITION_OPTIONS }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", weight: "semibold", color: "muted", transform: "uppercase", children: "Price (\u20B9 Rupees)" }), _jsxs(Row, { gap: "sm", children: [_jsx(Div, { className: "flex-1", children: _jsx(FieldInput, { name: "minPrice", type: "number", min: 0, value: pendingFilters.minPrice ?? "", onChange: (v) => patch("minPrice", v), placeholder: "min" }) }), _jsx(Div, { className: "flex-1", children: _jsx(FieldInput, { name: "maxPrice", type: "number", min: 0, value: pendingFilters.maxPrice ?? "", onChange: (v) => patch("maxPrice", v), placeholder: "max" }) })] })] }), _jsx(FieldInput, { name: "tags", label: "Tags (comma-separated)", type: "text", value: pendingFilters.tags ?? "", onChange: (v) => patch("tags", v), placeholder: "pokemon, vintage, holo" }), _jsx(FieldInput, { name: "badges", label: "Badges (feature slugs)", type: "text", value: pendingFilters.badges ?? "", onChange: (v) => patch("badges", v), placeholder: "feature-free-shipping, feature-verified" })] }));
|
|
17
|
+
return (_jsxs(_Fragment, { children: [_jsx(FilterChipGroup, { label: "Status", tabs: statusOptions, value: pendingFilters.status ?? "", onChange: (id) => patch("status", id) }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", weight: "semibold", color: "muted", transform: "uppercase", children: "Category" }), _jsx(CategoryInlineSelect, { value: pendingFilters.category ?? "", onChange: (v) => patch("category", v), placeholder: "Search categories\u2026" })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", weight: "semibold", color: "muted", transform: "uppercase", children: "Brand" }), _jsx(BrandInlineSelect, { value: pendingFilters.brand ?? "", onChange: (v) => patch("brand", v), placeholder: "Search brands\u2026", allowCreate: false })] }), _jsx(FieldSelect, { name: "condition", label: "Condition", value: pendingFilters.condition ?? "", onChange: (v) => patch("condition", v), options: CONDITION_OPTIONS }), _jsxs(Stack, { gap: "xs", children: [_jsx(FieldSelect, { name: "stockState", label: "Stock level", value: pendingFilters.stockState ?? "", onChange: (v) => patch("stockState", v), options: stockStateOptions }), _jsx(Text, { size: "xs", color: "muted", children: "Applied to the listings on this page." })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", weight: "semibold", color: "muted", transform: "uppercase", children: "Price (\u20B9 Rupees)" }), _jsxs(Row, { gap: "sm", children: [_jsx(Div, { className: "flex-1", children: _jsx(FieldInput, { name: "minPrice", type: "number", min: 0, value: pendingFilters.minPrice ?? "", onChange: (v) => patch("minPrice", v), placeholder: "min" }) }), _jsx(Div, { className: "flex-1", children: _jsx(FieldInput, { name: "maxPrice", type: "number", min: 0, value: pendingFilters.maxPrice ?? "", onChange: (v) => patch("maxPrice", v), placeholder: "max" }) })] })] }), _jsx(FieldInput, { name: "tags", label: "Tags (comma-separated)", type: "text", value: pendingFilters.tags ?? "", onChange: (v) => patch("tags", v), placeholder: "pokemon, vintage, holo" }), _jsx(FieldInput, { name: "badges", label: "Badges (feature slugs)", type: "text", value: pendingFilters.badges ?? "", onChange: (v) => patch("badges", v), placeholder: "feature-free-shipping, feature-verified" })] }));
|
|
18
18
|
}
|