@mohasinac/appkit 4.62.1 → 4.63.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.build-stamp.json +3 -3
- package/dist/_internal/client/features/layout/DashboardLayoutClient.js +8 -29
- package/dist/_internal/client/features/layout/filterNavItems.d.ts +28 -0
- package/dist/_internal/client/features/layout/filterNavItems.js +35 -0
- package/dist/_internal/server/features/ads/booking.d.ts +118 -0
- package/dist/_internal/server/features/ads/booking.js +326 -0
- package/dist/_internal/server/features/checkout/actions.d.ts +51 -11
- package/dist/_internal/server/features/checkout/actions.js +335 -80
- package/dist/_internal/server/features/checkout/expand-order-items.d.ts +1 -2
- package/dist/_internal/server/features/checkout/expand-order-items.js +32 -0
- package/dist/_internal/server/features/checkout/freeze-parts.d.ts +50 -0
- package/dist/_internal/server/features/checkout/freeze-parts.js +94 -0
- package/dist/_internal/server/features/checkout/value-otp-gate.d.ts +55 -0
- package/dist/_internal/server/features/checkout/value-otp-gate.js +60 -0
- package/dist/_internal/server/features/dashboard/attention.d.ts +128 -0
- package/dist/_internal/server/features/dashboard/attention.js +183 -0
- package/dist/_internal/server/features/dashboard/data.d.ts +47 -0
- package/dist/_internal/server/features/dashboard/data.js +82 -0
- package/dist/_internal/server/features/inventory/reservations.d.ts +99 -0
- package/dist/_internal/server/features/inventory/reservations.js +273 -0
- package/dist/_internal/server/features/offers/destinations.d.ts +40 -0
- package/dist/_internal/server/features/offers/destinations.js +65 -0
- package/dist/_internal/server/features/orders/adapters.js +21 -0
- package/dist/_internal/server/features/orders/amend-delivery.d.ts +56 -0
- package/dist/_internal/server/features/orders/amend-delivery.js +124 -0
- package/dist/_internal/server/features/orders/balance.d.ts +89 -0
- package/dist/_internal/server/features/orders/balance.js +256 -0
- package/dist/_internal/server/features/pricing/actions.d.ts +58 -0
- package/dist/_internal/server/features/pricing/actions.js +144 -0
- package/dist/_internal/server/features/shipping/book-split.d.ts +86 -0
- package/dist/_internal/server/features/shipping/book-split.js +130 -0
- package/dist/_internal/server/features/shipping/book.d.ts +61 -0
- package/dist/_internal/server/features/shipping/book.js +176 -0
- package/dist/_internal/server/features/shipping/courier-block.js +26 -1
- package/dist/_internal/server/features/site-settings/adapters.d.ts +1 -1
- package/dist/_internal/server/features/site-settings/adapters.js +14 -0
- package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
- package/dist/_internal/server/features/stores/adapters.js +14 -0
- package/dist/_internal/server/functions/scheduled.d.ts +2 -1
- package/dist/_internal/server/functions/scheduled.js +57 -1
- package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +2 -0
- package/dist/_internal/server/jobs/core/jobRunners.js +45 -0
- package/dist/_internal/server/jobs/core/onOrderStatusChange.js +4 -1
- package/dist/_internal/server/jobs/core/reservationExpiry.d.ts +17 -0
- package/dist/_internal/server/jobs/core/reservationExpiry.js +40 -0
- package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +15 -0
- package/dist/_internal/server/jobs/handlers/index.d.ts +1 -0
- package/dist/_internal/server/jobs/handlers/index.js +1 -0
- package/dist/_internal/server/jobs/handlers/reservationExpiry.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/reservationExpiry.js +2 -0
- package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
- package/dist/_internal/shared/actions/action-registry.js +268 -0
- package/dist/_internal/shared/checkout/order-math.d.ts +31 -2
- package/dist/_internal/shared/checkout/order-math.js +22 -36
- package/dist/_internal/shared/checkout/rules/index.d.ts +1 -1
- package/dist/_internal/shared/checkout/rules/index.js +1 -1
- package/dist/_internal/shared/checkout/rules/payment-gate.d.ts +18 -0
- package/dist/_internal/shared/checkout/rules/payment-gate.js +25 -0
- package/dist/_internal/shared/checkout/split.d.ts +0 -12
- package/dist/_internal/shared/checkout/split.js +22 -2
- package/dist/_internal/shared/content/disclaimers.js +7 -7
- package/dist/_internal/shared/content/published-values.d.ts +6 -1
- package/dist/_internal/shared/content/published-values.js +8 -0
- package/dist/_internal/shared/features/admin/integrations.d.ts +32 -2
- package/dist/_internal/shared/features/admin/integrations.js +19 -9
- package/dist/_internal/shared/features/dashboard/hub-sections.d.ts +72 -0
- package/dist/_internal/shared/features/dashboard/hub-sections.js +75 -0
- package/dist/_internal/shared/features/emi/choice.d.ts +98 -0
- package/dist/_internal/shared/features/emi/choice.js +69 -0
- package/dist/_internal/shared/features/emi/group.d.ts +84 -0
- package/dist/_internal/shared/features/emi/group.js +126 -0
- package/dist/_internal/shared/features/ledger/posting.d.ts +107 -0
- package/dist/_internal/shared/features/ledger/posting.js +165 -0
- package/dist/_internal/shared/features/ledger/reports.d.ts +76 -0
- package/dist/_internal/shared/features/ledger/reports.js +127 -0
- package/dist/_internal/shared/features/ledger/rules.d.ts +170 -0
- package/dist/_internal/shared/features/ledger/rules.js +221 -0
- package/dist/_internal/shared/features/offers/coordination.d.ts +122 -0
- package/dist/_internal/shared/features/offers/coordination.js +80 -0
- package/dist/_internal/shared/features/orders/delivery-snapshot.d.ts +114 -0
- package/dist/_internal/shared/features/orders/delivery-snapshot.js +116 -0
- package/dist/_internal/shared/features/payouts/pay-period.d.ts +91 -0
- package/dist/_internal/shared/features/payouts/pay-period.js +104 -0
- package/dist/_internal/shared/features/points/accepts.d.ts +1 -8
- package/dist/_internal/shared/features/points/accepts.js +12 -6
- package/dist/_internal/shared/features/pricing/rate-registry.d.ts +150 -0
- package/dist/_internal/shared/features/pricing/rate-registry.js +246 -0
- package/dist/_internal/shared/features/shipping/parcel.d.ts +123 -0
- package/dist/_internal/shared/features/shipping/parcel.js +139 -0
- package/dist/_internal/shared/features/site-settings/fees.d.ts +16 -8
- package/dist/_internal/shared/features/site-settings/fees.js +12 -8
- package/dist/_internal/shared/fees/explain.d.ts +81 -0
- package/dist/_internal/shared/fees/explain.js +101 -0
- package/dist/_internal/shared/history/index.d.ts +1 -1
- package/dist/_internal/shared/history/index.js +1 -1
- package/dist/_internal/shared/history/types.d.ts +7 -1
- package/dist/_internal/shared/history/types.js +25 -0
- package/dist/client.d.ts +11 -1
- package/dist/client.js +13 -0
- package/dist/constants/api-endpoints.d.ts +72 -0
- package/dist/constants/api-endpoints.js +41 -0
- package/dist/constants/field-names.d.ts +41 -0
- package/dist/constants/field-names.js +47 -0
- package/dist/features/account/components/UserOrdersView.js +28 -2
- package/dist/features/admin/components/AdminIntegrationsView.d.ts +55 -0
- package/dist/features/admin/components/AdminIntegrationsView.js +74 -0
- package/dist/features/admin/components/AdminOrderEditorView.d.ts +16 -1
- package/dist/features/admin/components/AdminOrderEditorView.js +72 -3
- package/dist/features/admin/components/AdminOrdersView.js +20 -1
- package/dist/features/admin/schemas/firestore.d.ts +32 -0
- package/dist/features/ads/schemas/booking-write.d.ts +157 -0
- package/dist/features/ads/schemas/booking-write.js +88 -0
- package/dist/features/ads/schemas/creative-form.d.ts +18 -0
- package/dist/features/ads/schemas/creative-form.js +51 -0
- package/dist/features/ads/schemas/firestore.d.ts +140 -0
- package/dist/features/ads/schemas/firestore.js +109 -0
- package/dist/features/auctions/repository/bid.repository.js +3 -1
- package/dist/features/audit-log/schemas/firestore.d.ts +110 -0
- package/dist/features/audit-log/schemas/firestore.js +74 -0
- package/dist/features/auth/actions/profile-actions.d.ts +69 -1
- package/dist/features/auth/actions/profile-actions.js +62 -1
- package/dist/features/auth/schemas/firestore.d.ts +16 -2
- package/dist/features/auth/schemas/firestore.js +2 -1
- package/dist/features/blog/actions/blog-actions.d.ts +6 -0
- package/dist/features/blog/actions/blog-actions.js +17 -2
- package/dist/features/blog/api/route.js +4 -1
- package/dist/features/blog/columns/index.js +5 -0
- package/dist/features/blog/components/BlogIndexPageView.js +2 -1
- package/dist/features/blog/repository/blog.repository.d.ts +4 -0
- package/dist/features/blog/repository/blog.repository.js +5 -0
- package/dist/features/blog/schemas/blog-form.d.ts +3 -0
- package/dist/features/blog/schemas/blog-form.js +1 -0
- package/dist/features/blog/schemas/firestore.d.ts +38 -1
- package/dist/features/blog/schemas/firestore.js +23 -0
- package/dist/features/blog/schemas/index.d.ts +6 -0
- package/dist/features/blog/schemas/index.js +2 -0
- package/dist/features/blog/types/index.d.ts +2 -0
- package/dist/features/cart/components/CartPriceBreakdown.d.ts +12 -1
- package/dist/features/cart/components/CartPriceBreakdown.js +24 -5
- package/dist/features/cart/repository/cart.repository.d.ts +14 -0
- package/dist/features/cart/repository/cart.repository.js +20 -0
- package/dist/features/cart/schemas/firestore.d.ts +24 -0
- package/dist/features/dashboard/components/DashboardHubView.d.ts +83 -0
- package/dist/features/dashboard/components/DashboardHubView.js +56 -0
- package/dist/features/dashboard/components/DashboardSectionsClient.d.ts +23 -0
- package/dist/features/dashboard/components/DashboardSectionsClient.js +66 -0
- package/dist/features/dispute-reasons/components/AdminDisputeReasonsView.d.ts +55 -0
- package/dist/features/dispute-reasons/components/AdminDisputeReasonsView.js +175 -0
- package/dist/features/dispute-reasons/repository/dispute-reasons.repository.d.ts +24 -0
- package/dist/features/dispute-reasons/repository/dispute-reasons.repository.js +28 -0
- package/dist/features/dispute-reasons/schemas/firestore.d.ts +3 -5
- package/dist/features/events/components/EventSaleBanner.js +1 -1
- package/dist/features/faq/components/FAQPageContent.js +2 -2
- package/dist/features/gateway-events/schemas/index.d.ts +2 -2
- package/dist/features/homepage/schemas/index.d.ts +8 -8
- package/dist/features/inventory/schemas/stock-reservation.d.ts +114 -0
- package/dist/features/inventory/schemas/stock-reservation.js +103 -0
- package/dist/features/layout/AppLayoutShell.d.ts +9 -1
- package/dist/features/layout/AppLayoutShell.js +12 -4
- package/dist/features/layout/BottomActions.js +2 -1
- package/dist/features/layout/BottomNavbar.d.ts +10 -2
- package/dist/features/layout/SideActionDock.js +52 -2
- package/dist/features/layout/TitleBarLayout.js +13 -4
- package/dist/features/layout/TitleBarLayout.style.css +59 -0
- package/dist/features/layout/useCtaReveal.d.ts +16 -1
- package/dist/features/layout/useCtaReveal.js +17 -26
- package/dist/features/ledger/repository/journal.repository.d.ts +110 -0
- package/dist/features/ledger/repository/journal.repository.js +312 -0
- package/dist/features/ledger/schemas/expenses.d.ts +87 -0
- package/dist/features/ledger/schemas/expenses.js +27 -0
- package/dist/features/ledger/schemas/firestore.d.ts +183 -0
- package/dist/features/ledger/schemas/firestore.js +69 -0
- package/dist/features/orders/components/BalancePaymentPanel.d.ts +12 -0
- package/dist/features/orders/components/BalancePaymentPanel.js +94 -0
- package/dist/features/orders/components/RaiseDisputeForm.js +19 -11
- package/dist/features/orders/repository/orders.repository.d.ts +28 -1
- package/dist/features/orders/repository/orders.repository.js +60 -6
- package/dist/features/orders/schemas/balance.d.ts +40 -0
- package/dist/features/orders/schemas/balance.js +47 -0
- package/dist/features/orders/schemas/firestore.d.ts +202 -0
- package/dist/features/orders/schemas/firestore.js +43 -0
- package/dist/features/orders/schemas/index.d.ts +2 -2
- package/dist/features/orders/types/index.d.ts +21 -0
- package/dist/features/payments/repository/payout.repository.js +3 -2
- package/dist/features/payments/schemas/firestore.d.ts +92 -1
- package/dist/features/payments/schemas/firestore.js +23 -0
- package/dist/features/pricing/components/AdminPricingView.d.ts +13 -0
- package/dist/features/pricing/components/AdminPricingView.js +157 -0
- package/dist/features/products/schemas/firestore.d.ts +26 -1
- package/dist/features/products/schemas/firestore.js +2 -0
- package/dist/features/products/schemas/index.d.ts +2 -2
- package/dist/features/reviews/repository/reviews.repository.d.ts +14 -0
- package/dist/features/reviews/repository/reviews.repository.js +16 -0
- package/dist/features/scams/repository/scammer.repository.js +2 -1
- package/dist/features/scams/schemas/firestore.d.ts +2 -1
- package/dist/features/scams/schemas/firestore.js +2 -1
- package/dist/features/seller/actions/offer-actions.js +56 -0
- package/dist/features/seller/actions/seller-actions.d.ts +1 -1
- package/dist/features/seller/actions/seller-actions.js +4 -0
- package/dist/features/seller/components/DispatchForm.d.ts +11 -0
- package/dist/features/seller/components/DispatchForm.js +190 -0
- package/dist/features/seller/components/SellerOrdersView.js +94 -2
- package/dist/features/seller/components/index.d.ts +1 -0
- package/dist/features/seller/components/index.js +1 -0
- package/dist/features/seller/repository/offer.repository.js +4 -3
- package/dist/features/seller/schemas/firestore.d.ts +23 -2
- package/dist/features/seller/schemas/firestore.js +3 -2
- package/dist/features/seller/schemas/index.d.ts +4 -4
- package/dist/features/seller/schemas/order-forms.d.ts +61 -4
- package/dist/features/seller/schemas/order-forms.js +41 -8
- package/dist/features/shipments/schemas/firestore.d.ts +25 -0
- package/dist/features/shipping-boxes/repository/shipping-boxes.repository.d.ts +39 -0
- package/dist/features/shipping-boxes/repository/shipping-boxes.repository.js +55 -0
- package/dist/features/shipping-boxes/schemas/firestore.d.ts +2 -4
- package/dist/features/stores/repository/store.repository.d.ts +18 -0
- package/dist/features/stores/repository/store.repository.js +27 -1
- package/dist/features/stores/schemas/firestore.d.ts +41 -0
- package/dist/features/stores/schemas/firestore.js +20 -0
- package/dist/features/stores/schemas/index.d.ts +6 -6
- package/dist/features/support/repository/support.repository.js +72 -6
- package/dist/features/support/schemas/firestore.d.ts +17 -0
- package/dist/features/support/schemas/index.d.ts +2 -2
- package/dist/index.d.ts +5 -0
- package/dist/index.js +19 -0
- package/dist/next/routing/route-map.d.ts +92 -0
- package/dist/next/routing/route-map.js +53 -0
- package/dist/react/contexts/SessionContext.d.ts +17 -1
- package/dist/schemas/registry.d.ts +22 -16
- package/dist/security/pii-schemas.d.ts +1 -1
- package/dist/security/pii-schemas.js +45 -0
- package/dist/server-entry.d.ts +1 -1
- package/dist/server-entry.js +10 -1
- package/dist/server.d.ts +29 -1
- package/dist/server.js +158 -0
- package/dist/styles.css +349 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/Button.d.ts +10 -1
- package/dist/ui/components/Button.js +25 -3
- package/dist/ui/components/Chart.d.ts +63 -0
- package/dist/ui/components/Chart.js +169 -0
- package/dist/ui/components/Chart.style.css +214 -0
- package/dist/ui/components/Disclaimer.js +10 -1
- package/dist/ui/components/OverflowRow.js +54 -2
- package/dist/ui/components/OverflowRow.style.css +38 -0
- package/dist/ui/components/RichTextEditor.js +99 -8
- package/dist/ui/components/chart-spec.d.ts +68 -0
- package/dist/ui/components/chart-spec.js +140 -0
- package/dist/ui/components/index.style.css +1 -0
- package/dist/ui/components/surface-tokens.d.ts +1 -1
- package/dist/ui/components/surface-tokens.js +9 -0
- package/dist/ui/components/toolbar-collapse.d.ts +13 -0
- package/dist/ui/components/toolbar-collapse.js +10 -1
- package/dist/ui/index.d.ts +6 -0
- package/dist/ui/index.js +3 -0
- package/dist/ui/rich-text/RichText.d.ts +10 -1
- package/dist/ui/rich-text/RichText.js +33 -2
- package/dist/ui/rich-text/RichText.style.css +35 -0
- package/dist/ui/rich-text/RichTextRenderer.d.ts +30 -1
- package/dist/ui/rich-text/RichTextRenderer.js +34 -2
- package/dist/ui/rich-text/chart-blocks.d.ts +62 -0
- package/dist/ui/rich-text/chart-blocks.js +87 -0
- package/package.json +1 -1
- package/scripts/seed-cli.mjs +3 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { AttentionCounts } from "../../../_internal/server/features/dashboard/attention";
|
|
3
|
+
import type { DashboardHubSection } from "../../../_internal/shared/features/dashboard/hub-sections";
|
|
4
|
+
/**
|
|
5
|
+
* The dashboard hub — one component, two modes, zero copy of its own.
|
|
6
|
+
*
|
|
7
|
+
* ## Why one component and not two
|
|
8
|
+
*
|
|
9
|
+
* A landing page lists SECTIONS; a section hub lists SCREENS. Those read as
|
|
10
|
+
* two features and are one: a grid of link cards, each with a title, a
|
|
11
|
+
* sentence about what it is for, and an optional count of things waiting. Same
|
|
12
|
+
* prop surface, same renderer — which the Duplication Framework calls a
|
|
13
|
+
* consolidate-on-sight ("copies accept the same parameters and return the same
|
|
14
|
+
* shape"), not a Rule-of-Three wait.
|
|
15
|
+
*
|
|
16
|
+
* The thing it must not become is `AdminGuideHubView`, which is the visual
|
|
17
|
+
* pattern here and hand-writes its own 13-card list — so a guide added to the
|
|
18
|
+
* nav never appears on it. Every card below comes from a nav item's existing
|
|
19
|
+
* `label` and `description`.
|
|
20
|
+
*
|
|
21
|
+
* ## Not a client component
|
|
22
|
+
*
|
|
23
|
+
* It takes plain data and renders. No hook, no fetch, no `"use client"` — the
|
|
24
|
+
* page that mounts it is already per-request (its layout reads the session), so
|
|
25
|
+
* the counts arrive with the render rather than costing a second invocation
|
|
26
|
+
* after it. See `_internal/server/features/dashboard/data.ts`.
|
|
27
|
+
*
|
|
28
|
+
* ## No icons, on purpose
|
|
29
|
+
*
|
|
30
|
+
* A nav item carries no `iconKey`, so icons would mean a hand-written map of
|
|
31
|
+
* 22 sections or ~130 screens — a second list to drift from the first, which is
|
|
32
|
+
* the whole defect this feature exists to avoid (Root Cause #61). The section
|
|
33
|
+
* heading plus the card's own sentence is the distinction; `<Badge>` carries
|
|
34
|
+
* the only signal that needs to catch the eye.
|
|
35
|
+
*/
|
|
36
|
+
export interface DashboardHubViewProps {
|
|
37
|
+
/**
|
|
38
|
+
* Page heading. Omit when the grid is embedded in a page that already has
|
|
39
|
+
* one — `/store` renders it inside a `CollapsibleSection`, and a second
|
|
40
|
+
* `<h1>` mid-page breaks the heading outline for a screen reader.
|
|
41
|
+
*/
|
|
42
|
+
title?: string;
|
|
43
|
+
/** One sentence under the heading. Only rendered alongside a `title`. */
|
|
44
|
+
description?: string;
|
|
45
|
+
/**
|
|
46
|
+
* What to render.
|
|
47
|
+
*
|
|
48
|
+
* - `"sections"` — one card per section, linking to its hub. The landing page.
|
|
49
|
+
* - `"screens"` — one card per screen in `sections[0]`. A section hub.
|
|
50
|
+
*
|
|
51
|
+
* 🛑 A hub is passed a ONE-section array rather than a section plus a mode
|
|
52
|
+
* flag, so the two modes cannot disagree about what they are rendering.
|
|
53
|
+
*/
|
|
54
|
+
mode: "sections" | "screens";
|
|
55
|
+
sections: DashboardHubSection[];
|
|
56
|
+
/** Built by the caller from `ROUTES.{PORTAL}.SECTION(slug)`. */
|
|
57
|
+
sectionHrefs: Record<string, string>;
|
|
58
|
+
counts: AttentionCounts;
|
|
59
|
+
/**
|
|
60
|
+
* Rendered above the cards — the live stat tiles on `/store` and `/user`.
|
|
61
|
+
*
|
|
62
|
+
* Those are real data and stay: the plan's "bland tile walls" are the
|
|
63
|
+
* undifferentiated grids of ~19 nav shortcuts, not the figures beside them.
|
|
64
|
+
* Deleting a working panel while replacing its neighbour is Root Cause #60's
|
|
65
|
+
* shape, so the slot exists and the caller keeps passing them.
|
|
66
|
+
*/
|
|
67
|
+
above?: React.ReactNode;
|
|
68
|
+
/** Shown when the viewer can see nothing at all. */
|
|
69
|
+
emptyMessage?: string;
|
|
70
|
+
/**
|
|
71
|
+
* Render "7 screens" on a section card. Default `true`.
|
|
72
|
+
*
|
|
73
|
+
* 🛑 Pass `false` whenever `sections` has NOT been through every filter that
|
|
74
|
+
* applies to the portal. `/store` is the live case: its entitlement filter
|
|
75
|
+
* (`filterStoreNavGroups`) runs in the layout and on the hub pages, but the
|
|
76
|
+
* client landing does not hold the store document, so its "Listings" card
|
|
77
|
+
* would claim nine screens where the seller can reach six. The section itself
|
|
78
|
+
* is still correct — it is the COUNT that would over-promise, so the count is
|
|
79
|
+
* withheld rather than guessed.
|
|
80
|
+
*/
|
|
81
|
+
showScreenCount?: boolean;
|
|
82
|
+
}
|
|
83
|
+
export declare function DashboardHubView({ title, description, mode, sections, sectionHrefs, counts, above, emptyMessage, showScreenCount, }: DashboardHubViewProps): React.JSX.Element;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Alert, Badge, Div, Grid, Heading, Row, Section, Stack, Text, TextLink } from "../../../ui/index.js";
|
|
3
|
+
/**
|
|
4
|
+
* Sum of the counts inside a section, or `null` when it has none.
|
|
5
|
+
*
|
|
6
|
+
* 🛑 `null` and `0` are different answers and a card renders only the first as
|
|
7
|
+
* "no badge". A section whose every count FAILED must not read as "nothing
|
|
8
|
+
* waiting" (Root Cause #72), so a failed read inside an otherwise-countless
|
|
9
|
+
* section stays `null` rather than collapsing to a sum of zero.
|
|
10
|
+
*/
|
|
11
|
+
function sectionTotal(section, counts) {
|
|
12
|
+
let total = null;
|
|
13
|
+
for (const item of section.items) {
|
|
14
|
+
const value = item.id ? counts[item.id] : undefined;
|
|
15
|
+
if (typeof value !== "number")
|
|
16
|
+
continue;
|
|
17
|
+
total = (total ?? 0) + value;
|
|
18
|
+
}
|
|
19
|
+
return total;
|
|
20
|
+
}
|
|
21
|
+
/** A count worth showing. Zero is not — a `0` badge is noise on every card. */
|
|
22
|
+
function badgeFor(value) {
|
|
23
|
+
if (typeof value !== "number" || value <= 0)
|
|
24
|
+
return null;
|
|
25
|
+
return (_jsx(Badge, { variant: "warning", "aria-label": `${value} waiting`, children: value }));
|
|
26
|
+
}
|
|
27
|
+
function CardGrid({ cards }) {
|
|
28
|
+
return (_jsx(Grid, { cols: "cards", gap: "md", children: cards.map((card) => (_jsxs(TextLink, { variant: "bare", href: card.href, rounded: "xl", layout: "flex-col", gap: "sm", paddingX: "lg", paddingY: "lg", shadow: "hover-md", className: "group border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] hover:border-[var(--appkit-color-primary)] transition-all", children: [_jsxs(Row, { align: "center", justify: "between", gap: "sm", className: "min-w-0 w-full", children: [_jsx(Text, { weight: "semibold", className: "min-w-0 text-[var(--appkit-color-text)] group-hover:text-[var(--appkit-color-primary)] transition-colors", children: card.label }), badgeFor(card.count)] }), card.description && (_jsx(Text, { size: "sm", className: "text-[var(--appkit-color-text-muted)] leading-relaxed", children: card.description })), card.meta && (_jsx(Text, { size: "xs", className: "text-[var(--appkit-color-text-faint)] mt-auto", children: card.meta }))] }, card.key))) }));
|
|
29
|
+
}
|
|
30
|
+
export function DashboardHubView({ title, description, mode, sections, sectionHrefs, counts, above, emptyMessage, showScreenCount = true, }) {
|
|
31
|
+
const hasAny = sections.some((section) => section.items.length > 0);
|
|
32
|
+
return (_jsxs(Stack, { gap: "lg", children: [title && (_jsxs(Stack, { gap: "xs", children: [_jsx(Heading, { level: 1, size: "xl", weight: "semibold", color: "primary", children: title }), description && (_jsx(Text, { size: "sm", color: "muted", children: description }))] })), above, !hasAny && (_jsx(Alert, { variant: "info", title: "Nothing here yet", children: emptyMessage ??
|
|
33
|
+
"Your permission set doesn't include access to any of these screens yet. Ask an admin for the permissions you need." })), hasAny && mode === "sections" && (_jsx(CardGrid, { cards: sections.map((section) => ({
|
|
34
|
+
key: section.slug,
|
|
35
|
+
href: sectionHrefs[section.slug] ?? "",
|
|
36
|
+
label: section.title,
|
|
37
|
+
/*
|
|
38
|
+
* A section has no description of its own and must not invent one —
|
|
39
|
+
* so it names what is inside it instead. `items.length` is the
|
|
40
|
+
* post-filter count, which is what this viewer will actually find
|
|
41
|
+
* PROVIDED every filter has run; see `showScreenCount`.
|
|
42
|
+
*/
|
|
43
|
+
meta: showScreenCount
|
|
44
|
+
? `${section.items.length} ${section.items.length === 1 ? "screen" : "screens"}`
|
|
45
|
+
: undefined,
|
|
46
|
+
count: sectionTotal(section, counts),
|
|
47
|
+
})) })), hasAny &&
|
|
48
|
+
mode === "screens" &&
|
|
49
|
+
sections.map((section) => (_jsxs(Section, { children: [_jsx(Heading, { level: 2, size: "lg", weight: "semibold", className: "mb-4 text-[var(--appkit-color-text)]", children: section.title }), _jsx(CardGrid, { cards: section.items.map((item) => ({
|
|
50
|
+
key: item.id ?? item.href,
|
|
51
|
+
href: item.href,
|
|
52
|
+
label: item.label,
|
|
53
|
+
description: item.description,
|
|
54
|
+
count: item.id ? (counts[item.id] ?? null) : null,
|
|
55
|
+
})) })] }, section.slug))), hasAny && mode === "sections" && (_jsx(Div, { children: _jsx(Text, { size: "xs", color: "faint", children: "Every card above is a section of the sidebar. The screens inside one are the same screens, with a sentence each on what they are for." }) }))] }));
|
|
56
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { NavPortal, SidebarNavGroup } from "../../../_internal/shared/features/layout/types";
|
|
3
|
+
export interface DashboardSectionsClientProps {
|
|
4
|
+
portal: NavPortal;
|
|
5
|
+
/**
|
|
6
|
+
* The portal's nav groups.
|
|
7
|
+
*
|
|
8
|
+
* 🛑 Pass the groups the SIDEBAR was given, already put through any
|
|
9
|
+
* portal-specific filter (`filterStoreNavGroups` for `/store`). This applies
|
|
10
|
+
* only the navConfig/permission filter; a section the store is not entitled
|
|
11
|
+
* to would otherwise appear here and nowhere else.
|
|
12
|
+
*/
|
|
13
|
+
groups: readonly SidebarNavGroup[];
|
|
14
|
+
/** `null` means admin; `undefined` means "no permission gating applies". */
|
|
15
|
+
permissions?: string[] | null;
|
|
16
|
+
/** Omit when embedding in a page that already has a heading. */
|
|
17
|
+
title?: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
/** Live stat tiles and anything else that belongs above the cards. */
|
|
20
|
+
above?: React.ReactNode;
|
|
21
|
+
emptyMessage?: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function DashboardSectionsClient({ portal, groups, permissions, title, description, above, emptyMessage, }: DashboardSectionsClientProps): React.JSX.Element;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import { useSiteSettings } from "../../../core/hooks/useSiteSettings.js";
|
|
5
|
+
import { filterNavGroups } from "../../../_internal/client/features/layout/filterNavItems.js";
|
|
6
|
+
import { hubSections } from "../../../_internal/shared/features/dashboard/hub-sections.js";
|
|
7
|
+
import { ROUTES } from "../../../next/routing/route-map.js";
|
|
8
|
+
import { DashboardHubView } from "./DashboardHubView.js";
|
|
9
|
+
/**
|
|
10
|
+
* The sectioned index of a dashboard, for a landing page that is already a
|
|
11
|
+
* CLIENT component.
|
|
12
|
+
*
|
|
13
|
+
* ## Why a client variant exists at all
|
|
14
|
+
*
|
|
15
|
+
* `/store/page.tsx` and `/user/page.tsx` are `"use client"` and carry live stat
|
|
16
|
+
* tiles (`useStoreDashboard`, `useNotifications`, an avatar uploader). Turning
|
|
17
|
+
* either into a server component to fetch hub data would mean moving a working
|
|
18
|
+
* 250–360 line page into a sibling file for the sake of a card grid — a large
|
|
19
|
+
* diff over a working feature, which is the shape of Root Cause #60.
|
|
20
|
+
*
|
|
21
|
+
* So the DATA source differs and the RENDERER does not: this resolves sections
|
|
22
|
+
* from the same `useSiteSettings` navConfig the sidebar reads, and hands them to
|
|
23
|
+
* the same `<DashboardHubView>` the admin landing renders server-side. One card
|
|
24
|
+
* grid, two ways in — not two card grids.
|
|
25
|
+
*
|
|
26
|
+
* ## 🛑 No attention counts here, and that is honest rather than lazy
|
|
27
|
+
*
|
|
28
|
+
* Counts need Firestore, which a client component can only reach through an
|
|
29
|
+
* endpoint — a second billed invocation per landing visit for numbers the page
|
|
30
|
+
* did not previously show (Root Cause #94). The admin landing, which is a
|
|
31
|
+
* server component under a session-reading layout, gets them for free in its
|
|
32
|
+
* own render. A seller's one countable figure (pending orders) is already a
|
|
33
|
+
* live stat tile on `/store`, so the badge would be the second place for it.
|
|
34
|
+
*
|
|
35
|
+
* `counts={{}}` means every card renders without a badge — never with a `0`,
|
|
36
|
+
* which is the one answer that would wrongly read as "nothing waiting"
|
|
37
|
+
* (Root Cause #72).
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* 🛑 `Record<NavPortal, …>` so a fourth portal is a COMPILE error here rather
|
|
41
|
+
* than a landing page whose section cards all link to `""`. An object keyed on
|
|
42
|
+
* `string` would accept the omission silently (Root Cause #61).
|
|
43
|
+
*/
|
|
44
|
+
const SECTION_ROUTE = {
|
|
45
|
+
admin: (slug) => String(ROUTES.ADMIN.SECTION(slug)),
|
|
46
|
+
store: (slug) => String(ROUTES.STORE.SECTION(slug)),
|
|
47
|
+
user: (slug) => String(ROUTES.USER.SECTION(slug)),
|
|
48
|
+
};
|
|
49
|
+
export function DashboardSectionsClient({ portal, groups, permissions, title, description, above, emptyMessage, }) {
|
|
50
|
+
const { data: settings } = useSiteSettings();
|
|
51
|
+
/*
|
|
52
|
+
* Derived, never copied into state — the settings query can resolve after
|
|
53
|
+
* first paint, and a `useState` seeded from it would freeze the pre-settings
|
|
54
|
+
* answer. Same reasoning as `AdminIntegrationsView`.
|
|
55
|
+
*/
|
|
56
|
+
const sections = useMemo(() => hubSections(filterNavGroups([...groups], settings?.navConfig, permissions)), [groups, settings?.navConfig, permissions]);
|
|
57
|
+
const sectionHrefs = useMemo(() => Object.fromEntries(sections.map((s) => [s.slug, SECTION_ROUTE[portal](s.slug)])), [sections, portal]);
|
|
58
|
+
return (_jsx(DashboardHubView, { title: title, description: description, mode: "sections", sections: sections, sectionHrefs: sectionHrefs, counts: {}, above: above, emptyMessage: emptyMessage,
|
|
59
|
+
/*
|
|
60
|
+
* 🛑 Withheld deliberately. `/store`'s entitlement filter lives in the
|
|
61
|
+
* layout and on the hub pages, not here, so a count rendered from these
|
|
62
|
+
* groups would over-promise on the Listings card. See the prop's own
|
|
63
|
+
* header — the section is right, the number would not be.
|
|
64
|
+
*/
|
|
65
|
+
showScreenCount: false }));
|
|
66
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { type DisputeKind } from "../../../_internal/shared/features/orders/dispute-reasons";
|
|
3
|
+
/**
|
|
4
|
+
* Admin → Trust & Safety → Dispute reasons.
|
|
5
|
+
*
|
|
6
|
+
* ## Why a management page exists at all
|
|
7
|
+
*
|
|
8
|
+
* Inline create in the triage drawer was the cheaper half and was rejected on
|
|
9
|
+
* its own, for a reason worth keeping: **with no management page nothing can
|
|
10
|
+
* ever retire a reason**, so the list only grows and a typo'd row is permanent.
|
|
11
|
+
* Both shipped — the drawer so an agent never leaves a ticket to add a missing
|
|
12
|
+
* reason, this page so the eleven seeded rows are reviewable and retire has a
|
|
13
|
+
* home.
|
|
14
|
+
*
|
|
15
|
+
* ## 🛑 A row is OPENABLE, not just actionable
|
|
16
|
+
*
|
|
17
|
+
* `audit-listing-detail-affordance` requires it and the reason is Root Cause
|
|
18
|
+
* #56: a menu of pure mutations lets staff act on a record they were never able
|
|
19
|
+
* to read. Here that would mean retiring a reason without seeing its KIND — and
|
|
20
|
+
* the kind is the only field that decides which gate the reason inherits, so
|
|
21
|
+
* retiring the wrong one silently narrows the wrong form.
|
|
22
|
+
*
|
|
23
|
+
* `RecordDetailModal` is the shared primitive for exactly this; a bespoke modal
|
|
24
|
+
* would have tripped the Duplication Framework's Rule of Three on sight.
|
|
25
|
+
*/
|
|
26
|
+
export interface AdminDisputeReasonsViewProps {
|
|
27
|
+
/**
|
|
28
|
+
* The staff list endpoint, supplied by the consumer.
|
|
29
|
+
*
|
|
30
|
+
* 🛑 A PROP rather than a literal in here, for the same reason
|
|
31
|
+
* `RaiseDisputeForm` takes `reasonsEndpoint`: appkit holds no consumer
|
|
32
|
+
* routes. The path belongs in `API_ROUTES`, which is the one registry, and a
|
|
33
|
+
* literal here would be a second place it is written — exactly what
|
|
34
|
+
* `audit-hardcoded-api-routes` blocks.
|
|
35
|
+
*/
|
|
36
|
+
endpoint: string;
|
|
37
|
+
/** `PATCH /api/admin/dispute-reasons/{id}` with `{ isActive }`. */
|
|
38
|
+
onSetActive: (id: string, isActive: boolean) => Promise<void>;
|
|
39
|
+
/** `POST /api/dispute-reasons` — the same endpoint the triage drawer uses. */
|
|
40
|
+
onCreate: (input: {
|
|
41
|
+
label: string;
|
|
42
|
+
kind: DisputeKind;
|
|
43
|
+
}) => Promise<void>;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The two fields, exported so the triage drawer's inline create and this page
|
|
47
|
+
* cannot drift on the label bound or the kind vocabulary.
|
|
48
|
+
*/
|
|
49
|
+
export declare function DisputeReasonFields({ label, kind, onLabel, onKind, }: {
|
|
50
|
+
label: string;
|
|
51
|
+
kind: DisputeKind;
|
|
52
|
+
onLabel: (v: string) => void;
|
|
53
|
+
onKind: (v: DisputeKind) => void;
|
|
54
|
+
}): React.JSX.Element;
|
|
55
|
+
export declare function AdminDisputeReasonsView({ endpoint, onSetActive, onCreate, }: AdminDisputeReasonsViewProps): React.JSX.Element;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useCallback, useState } from "react";
|
|
4
|
+
import { Badge, Button, Row, Stack, Text } from "../../../ui/index.js";
|
|
5
|
+
import { RecordDetailModal } from "../../../ui/components/RecordDetailModal.js";
|
|
6
|
+
import { useToast } from "../../../ui/components/Toast.js";
|
|
7
|
+
import { FieldInput } from "../../../ui/forms/FieldInput.js";
|
|
8
|
+
import { FieldSelect } from "../../../ui/forms/FieldSelect.js";
|
|
9
|
+
import { normalizeError } from "../../../errors/normalize.js";
|
|
10
|
+
import { DataListingView } from "../../admin/components/DataListingView.js";
|
|
11
|
+
import { ACTIONS } from "../../../_internal/shared/actions/action-registry.js";
|
|
12
|
+
import { DISPUTE_KIND, } from "../../../_internal/shared/features/orders/dispute-reasons.js";
|
|
13
|
+
import { DISPUTE_REASON_LABEL_MAX, } from "../schemas/firestore.js";
|
|
14
|
+
/**
|
|
15
|
+
* What each kind MEANS, in one place.
|
|
16
|
+
*
|
|
17
|
+
* 🛑 A `Record<DisputeKind, string>`, so a third kind is a compile error here
|
|
18
|
+
* rather than a picker option rendering a bare token. The kind is the only
|
|
19
|
+
* field on a reason with a consequence — it decides which gate a dispute
|
|
20
|
+
* raised under it inherits — so it is the one that must never read as jargon.
|
|
21
|
+
*/
|
|
22
|
+
const KIND_MEANING = {
|
|
23
|
+
item: "Item problem — it arrived wrong or damaged",
|
|
24
|
+
not_reached: "Never arrived — the parcel did not reach the buyer",
|
|
25
|
+
};
|
|
26
|
+
/*
|
|
27
|
+
* Chips over `isActive`, as ordinary Sieve clauses. `listAll` applies no
|
|
28
|
+
* `isActive` filter of its own precisely so these work without the repository
|
|
29
|
+
* growing a method per chip.
|
|
30
|
+
*
|
|
31
|
+
* 🛑 "All" carries NO filter rather than `isActive==true|false`. A pipe-joined
|
|
32
|
+
* OR over a boolean would be two equalities merged for no reason, and — worse —
|
|
33
|
+
* it would exclude any row written before the field existed, which is the `!=`
|
|
34
|
+
* trap one step removed.
|
|
35
|
+
*/
|
|
36
|
+
const SCOPE_CHIPS = [
|
|
37
|
+
{ id: "active", label: "Active", filter: "isActive==true" },
|
|
38
|
+
{ id: "retired", label: "Retired", filter: "isActive==false" },
|
|
39
|
+
{ id: "all", label: "All", filter: undefined },
|
|
40
|
+
];
|
|
41
|
+
/**
|
|
42
|
+
* The two fields, exported so the triage drawer's inline create and this page
|
|
43
|
+
* cannot drift on the label bound or the kind vocabulary.
|
|
44
|
+
*/
|
|
45
|
+
export function DisputeReasonFields({ label, kind, onLabel, onKind, }) {
|
|
46
|
+
return (_jsxs(Stack, { gap: "md", children: [_jsx(FieldInput, { name: "label", label: "Reason wording", required: true, value: label, onChange: onLabel, maxLength: DISPUTE_REASON_LABEL_MAX, placeholder: "The courier left it at the wrong flat" }), _jsx(FieldSelect, { name: "kind", label: "Kind", required: true, value: kind, onChange: (v) => onKind(v),
|
|
47
|
+
/*
|
|
48
|
+
* Two options, so `<FieldSelect>` is correct — `PaginatedSelect` is for
|
|
49
|
+
* >5 (Rule #9), and a searchable dropdown over two values is worse than
|
|
50
|
+
* a native select. Derived from the shipped union so a third kind
|
|
51
|
+
* cannot exist in the gate and be missing here.
|
|
52
|
+
*/
|
|
53
|
+
options: DISPUTE_KIND.map((k) => ({ value: k, label: KIND_MEANING[k] })) })] }));
|
|
54
|
+
}
|
|
55
|
+
/** Create form. Shares `DisputeReasonFields` and the endpoint with the drawer. */
|
|
56
|
+
function CreateReasonForm({ onSubmit, }) {
|
|
57
|
+
const [label, setLabel] = useState("");
|
|
58
|
+
const [kind, setKind] = useState(DISPUTE_KIND[0]);
|
|
59
|
+
const [busy, setBusy] = useState(false);
|
|
60
|
+
const { showToast } = useToast();
|
|
61
|
+
return (_jsxs(Stack, { gap: "md", children: [_jsx(Text, { size: "sm", color: "muted", children: "Short wording, as a buyer would read it. The detail goes in their own free-text field \u2014 this is a picker option, not the explanation." }), _jsx(DisputeReasonFields, { label: label, kind: kind, onLabel: setLabel, onKind: setKind }), _jsx(Button, { action: ACTIONS.ADMIN["add-dispute-reason"], isLoading: busy, disabled: busy || label.trim().length < 3, onClick: async () => {
|
|
62
|
+
setBusy(true);
|
|
63
|
+
try {
|
|
64
|
+
await onSubmit({ label: label.trim(), kind });
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
void normalizeError(err);
|
|
68
|
+
showToast("Couldn't add that reason.", "error");
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
setBusy(false);
|
|
72
|
+
}
|
|
73
|
+
} })] }));
|
|
74
|
+
}
|
|
75
|
+
export function AdminDisputeReasonsView({ endpoint, onSetActive, onCreate, }) {
|
|
76
|
+
const { showToast } = useToast();
|
|
77
|
+
const [open, setOpen] = useState(null);
|
|
78
|
+
const [pending, setPending] = useState(null);
|
|
79
|
+
const setActive = useCallback(async (row, isActive) => {
|
|
80
|
+
setPending(row.id);
|
|
81
|
+
try {
|
|
82
|
+
await onSetActive(row.id, isActive);
|
|
83
|
+
showToast(isActive ? `"${row.label}" is back in the picker.` : `"${row.label}" retired.`, "success");
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
void normalizeError(err);
|
|
87
|
+
/*
|
|
88
|
+
* No optimistic flip anywhere here, so a failure needs no rollback —
|
|
89
|
+
* the row still shows what the server last told us. Only a successful
|
|
90
|
+
* write and the refetch behind it move the list.
|
|
91
|
+
*/
|
|
92
|
+
showToast("Couldn't save that. Nothing was changed.", "error");
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
setPending(null);
|
|
96
|
+
}
|
|
97
|
+
}, [onSetActive, showToast]);
|
|
98
|
+
const rowControls = useCallback((row) => (_jsxs(Row, { align: "center", gap: "sm", children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: () => setOpen(row), children: "View" }), _jsx(Button, { action: row.isActive
|
|
99
|
+
? ACTIONS.ADMIN["retire-dispute-reason"]
|
|
100
|
+
: ACTIONS.ADMIN["reactivate-dispute-reason"], size: "sm", isLoading: pending === row.id, disabled: pending !== null, onClick: () => setActive(row, !row.isActive) })] })), [pending, setActive]);
|
|
101
|
+
return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: {
|
|
102
|
+
portal: "admin",
|
|
103
|
+
title: "Dispute reasons",
|
|
104
|
+
subtitle: "What buyers can pick when reporting a problem. A reason's kind decides which gate it inherits — the wording is yours.",
|
|
105
|
+
endpoint: endpoint,
|
|
106
|
+
queryKey: ["admin", "dispute-reasons"],
|
|
107
|
+
defaultSort: "label",
|
|
108
|
+
/*
|
|
109
|
+
* 🛑 REQUIRED on `ListingViewConfig`, and the default must be one of
|
|
110
|
+
* these or the dropdown opens with nothing selected (Root Cause #63).
|
|
111
|
+
* Eleven rows, so the only orders worth offering are the buyer-facing
|
|
112
|
+
* wording and which kind a reason belongs to.
|
|
113
|
+
*/
|
|
114
|
+
sortOptions: [
|
|
115
|
+
{ value: "label", label: "Wording (A–Z)" },
|
|
116
|
+
{ value: "-label", label: "Wording (Z–A)" },
|
|
117
|
+
{ value: "kind", label: "Kind" },
|
|
118
|
+
],
|
|
119
|
+
filterKeys: ["scope"],
|
|
120
|
+
filterDefaults: { scope: "active" },
|
|
121
|
+
buildFilters: (state) => SCOPE_CHIPS.find((c) => c.id === (state.scope ?? "active"))?.filter,
|
|
122
|
+
mapRows: (res) => (res.items ?? []).map((r) => ({
|
|
123
|
+
id: r.id,
|
|
124
|
+
label: r.label,
|
|
125
|
+
kind: r.kind,
|
|
126
|
+
isActive: r.isActive,
|
|
127
|
+
createdByName: r.createdByName,
|
|
128
|
+
})),
|
|
129
|
+
getTotal: (res, rows) => res.total ?? rows.length,
|
|
130
|
+
/*
|
|
131
|
+
* 🛑 No `searchPlaceholder` and no `search` — the endpoint does not read
|
|
132
|
+
* `q`, and declaring either is what makes the box exist. ~39 views here
|
|
133
|
+
* render a box whose endpoint ignores the term: it accepts typing, the
|
|
134
|
+
* request carries it, and the results do not change, with no error
|
|
135
|
+
* anywhere. Eleven rows with scope chips need no search.
|
|
136
|
+
*/
|
|
137
|
+
emptyLabel: "No reasons in this scope.",
|
|
138
|
+
primaryAction: {
|
|
139
|
+
label: ACTIONS.ADMIN["add-dispute-reason"].label,
|
|
140
|
+
onClick: ({ openCreatePanel }) => openCreatePanel(),
|
|
141
|
+
},
|
|
142
|
+
renderEditor: ({ closePanel }) => (_jsx(CreateReasonForm, { onSubmit: async (input) => {
|
|
143
|
+
await onCreate(input);
|
|
144
|
+
closePanel();
|
|
145
|
+
} })),
|
|
146
|
+
resolveEditorTitle: () => "Add a dispute reason",
|
|
147
|
+
onRowClick: (row) => setOpen(row),
|
|
148
|
+
renderRowActions: rowControls,
|
|
149
|
+
renderCards: (rows) => (_jsx(Stack, { gap: "sm", children: rows.map((row) => (_jsxs(Row, { surface: "card", padding: "md", rounded: "lg", border: "default", align: "center", justify: "between", gap: "md", className: "min-w-0", children: [_jsxs(Stack, { gap: "xs", className: "min-w-0", children: [_jsxs(Row, { align: "center", gap: "sm", className: "min-w-0", children: [_jsx(Text, { weight: "semibold", color: "primary", className: "min-w-0", children: row.label }), _jsx(Badge, { variant: row.isActive ? "success" : "inactive", children: row.isActive ? "Active" : "Retired" })] }), _jsx(Text, { size: "xs", color: "muted", children: KIND_MEANING[row.kind] }), _jsxs(Text, { size: "xs", color: "faint", children: ["Added by ", row.createdByName] })] }), rowControls(row)] }, row.id))) })),
|
|
150
|
+
} }), open && (_jsx(RecordDetailModal, { isOpen: true, onClose: () => setOpen(null), title: open.label, badges: [
|
|
151
|
+
{
|
|
152
|
+
label: open.isActive ? "Active" : "Retired",
|
|
153
|
+
variant: open.isActive ? "success" : "secondary",
|
|
154
|
+
},
|
|
155
|
+
], fields: [
|
|
156
|
+
{ label: "Buyer-facing wording", value: open.label },
|
|
157
|
+
{ label: "Kind", value: `${open.kind} — ${KIND_MEANING[open.kind]}` },
|
|
158
|
+
{
|
|
159
|
+
label: "State",
|
|
160
|
+
value: open.isActive
|
|
161
|
+
? "Active — buyers see it on the dispute form"
|
|
162
|
+
: "Retired — hidden from the form",
|
|
163
|
+
},
|
|
164
|
+
{ label: "Added by", value: open.createdByName },
|
|
165
|
+
], metadata: {
|
|
166
|
+
id: open.id,
|
|
167
|
+
/*
|
|
168
|
+
* Stated on the record itself, because it is the fact an agent is
|
|
169
|
+
* most likely to be unsure about at the moment they are deciding
|
|
170
|
+
* whether to retire: the order snapshots the LABEL, so retiring
|
|
171
|
+
* cannot rewrite what a past dispute said.
|
|
172
|
+
*/
|
|
173
|
+
retiringIsNeverADelete: "Disputes already raised under this reason keep their wording — the order snapshots the label rather than pointing at this row.",
|
|
174
|
+
} }))] }));
|
|
175
|
+
}
|
|
@@ -32,7 +32,31 @@ export declare class DisputeReasonsRepository extends BaseRepository<DisputeReas
|
|
|
32
32
|
reason: DisputeReasonDocument;
|
|
33
33
|
created: boolean;
|
|
34
34
|
}>;
|
|
35
|
+
/**
|
|
36
|
+
* Every row, retired included — the staff management list.
|
|
37
|
+
*
|
|
38
|
+
* 🛑 Deliberately NOT `listActiveByKind` with the `isActive` clause dropped.
|
|
39
|
+
* That method also pins `kind`, and a management view must span both kinds:
|
|
40
|
+
* a reason retired for the wrong gate is exactly the row staff need to find.
|
|
41
|
+
*
|
|
42
|
+
* No `isActive` filter is applied here at all, so the caller's own Sieve
|
|
43
|
+
* filters decide — which is what lets the view offer Active / Retired / All
|
|
44
|
+
* chips without this method growing a parameter per chip.
|
|
45
|
+
*/
|
|
46
|
+
listAll(model: SieveModel): Promise<FirebaseSieveResult<DisputeReasonDocument>>;
|
|
35
47
|
/** Soft-retire. See the schema for why this is never a delete. */
|
|
36
48
|
retire(id: string): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Put a retired reason back in the picker.
|
|
51
|
+
*
|
|
52
|
+
* `createReason` already reactivates on a colliding re-add, which covers the
|
|
53
|
+
* agent who types the wording again. This covers the different case the
|
|
54
|
+
* management view creates: a row you can SEE and want back, where re-typing
|
|
55
|
+
* its exact label to resurrect it would be a trick rather than a control.
|
|
56
|
+
*
|
|
57
|
+
* The original `createdByName` is kept — they wrote the wording, and a
|
|
58
|
+
* reactivation is not authorship.
|
|
59
|
+
*/
|
|
60
|
+
reactivate(id: string): Promise<void>;
|
|
37
61
|
}
|
|
38
62
|
export declare const disputeReasonsRepository: DisputeReasonsRepository;
|
|
@@ -59,10 +59,38 @@ export class DisputeReasonsRepository extends BaseRepository {
|
|
|
59
59
|
await this.createWithId(id, reason);
|
|
60
60
|
return { reason, created: true };
|
|
61
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Every row, retired included — the staff management list.
|
|
64
|
+
*
|
|
65
|
+
* 🛑 Deliberately NOT `listActiveByKind` with the `isActive` clause dropped.
|
|
66
|
+
* That method also pins `kind`, and a management view must span both kinds:
|
|
67
|
+
* a reason retired for the wrong gate is exactly the row staff need to find.
|
|
68
|
+
*
|
|
69
|
+
* No `isActive` filter is applied here at all, so the caller's own Sieve
|
|
70
|
+
* filters decide — which is what lets the view offer Active / Retired / All
|
|
71
|
+
* chips without this method growing a parameter per chip.
|
|
72
|
+
*/
|
|
73
|
+
async listAll(model) {
|
|
74
|
+
return this.sieveQuery(model, DisputeReasonsRepository.SIEVE_FIELDS, { baseQuery: this.getCollection(), defaultPageSize: 20, maxPageSize: 50 });
|
|
75
|
+
}
|
|
62
76
|
/** Soft-retire. See the schema for why this is never a delete. */
|
|
63
77
|
async retire(id) {
|
|
64
78
|
await this.update(id, { isActive: false, updatedAt: new Date() });
|
|
65
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Put a retired reason back in the picker.
|
|
82
|
+
*
|
|
83
|
+
* `createReason` already reactivates on a colliding re-add, which covers the
|
|
84
|
+
* agent who types the wording again. This covers the different case the
|
|
85
|
+
* management view creates: a row you can SEE and want back, where re-typing
|
|
86
|
+
* its exact label to resurrect it would be a trick rather than a control.
|
|
87
|
+
*
|
|
88
|
+
* The original `createdByName` is kept — they wrote the wording, and a
|
|
89
|
+
* reactivation is not authorship.
|
|
90
|
+
*/
|
|
91
|
+
async reactivate(id) {
|
|
92
|
+
await this.update(id, { isActive: true, updatedAt: new Date() });
|
|
93
|
+
}
|
|
66
94
|
}
|
|
67
95
|
DisputeReasonsRepository.SIEVE_FIELDS = {
|
|
68
96
|
/*
|
|
@@ -22,10 +22,10 @@
|
|
|
22
22
|
* should be able to add it without a release.
|
|
23
23
|
*/
|
|
24
24
|
import type { DisputeKind } from "../../../_internal/shared/features/orders/dispute-reasons";
|
|
25
|
+
import type { BaseDocument } from "../../../_internal/shared/types/base-document";
|
|
25
26
|
export declare const DISPUTE_REASON_COLLECTION = "disputeReasons";
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
id: string;
|
|
27
|
+
/** `id` is `dreason-<slug of label>`, so the same reason cannot be added twice. */
|
|
28
|
+
export interface DisputeReasonDocument extends BaseDocument {
|
|
29
29
|
/** Buyer-facing. Shown verbatim in the picker. */
|
|
30
30
|
label: string;
|
|
31
31
|
/** Which gate this reason is subject to. Never free-form. */
|
|
@@ -43,8 +43,6 @@ export interface DisputeReasonDocument {
|
|
|
43
43
|
createdByName: string;
|
|
44
44
|
/** The uid, for moderation. Never rendered. */
|
|
45
45
|
createdByUid: string;
|
|
46
|
-
createdAt: Date;
|
|
47
|
-
updatedAt: Date;
|
|
48
46
|
}
|
|
49
47
|
export declare const DISPUTE_REASON_FIELDS: {
|
|
50
48
|
readonly ID: "id";
|
|
@@ -16,5 +16,5 @@ export function EventSaleBanner({ discountPercent, couponCode, title, subtitle,
|
|
|
16
16
|
const href = filterParam
|
|
17
17
|
? `${ROUTES.PUBLIC.PRODUCTS}?${encodeURIComponent(filterParam.key)}=${encodeURIComponent(filterParam.value)}`
|
|
18
18
|
: `${ROUTES.PUBLIC.PRODUCTS}?onSale=true`;
|
|
19
|
-
return (_jsx(Section, { color: "inverse", tone: "accent-banner", className: `${__P.p6}`, rounded: "2xl", shadow: "lg", children: _jsxs(Stack, { gap: "md", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Heading, { level: 2, weight: "bold", size: "3xl", children: headline }), subtitle ? _jsx(Text, { color: "inverse-muted", size: "base", children: subtitle }) : null] }), couponCode ? (_jsxs(Div, { layout: "inline-flex", gap: "3", align: "center", className: "backdrop-blur", surface: "default", paddingY: "y-xs", paddingX: "x-md", rounded: "lg", children: [_jsx(Span, { size: "xs", className: "tracking-wide opacity-80", transform: "uppercase", children: "Code" }), _jsx(Span, { size: "lg", weight: "bold", className: "font-mono", children: couponCode })] })) : null, _jsx(Div, { children: _jsx(Link, { href: href, children: _jsx(Button, { variant: "secondary", children: "Shop the sale" }) }) })] }) }));
|
|
19
|
+
return (_jsx(Section, { color: "inverse", tone: "accent-banner", className: `${__P.p6}`, rounded: "2xl", shadow: "lg", children: _jsxs(Stack, { gap: "md", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Heading, { color: "inverse", variant: "none", level: 2, weight: "bold", size: "3xl", children: headline }), subtitle ? _jsx(Text, { color: "inverse-muted", size: "base", children: subtitle }) : null] }), couponCode ? (_jsxs(Div, { layout: "inline-flex", gap: "3", align: "center", className: "backdrop-blur", surface: "default", paddingY: "y-xs", paddingX: "x-md", rounded: "lg", children: [_jsx(Span, { size: "xs", className: "tracking-wide opacity-80", transform: "uppercase", children: "Code" }), _jsx(Span, { size: "lg", weight: "bold", className: "font-mono", children: couponCode })] })) : null, _jsx(Div, { children: _jsx(Link, { href: href, children: _jsx(Button, { variant: "secondary", children: "Shop the sale" }) }) })] }) }));
|
|
20
20
|
}
|
|
@@ -45,12 +45,12 @@ export function FAQPageContent({ initialCategory = "all", categories, routeHelpe
|
|
|
45
45
|
total,
|
|
46
46
|
categoryCounts,
|
|
47
47
|
onSelect: handleCategorySelect,
|
|
48
|
-
}) }), _jsxs(Div, { layout: "grid", gap: "8", className: "
|
|
48
|
+
}) }), _jsxs(Div, { layout: "grid", gap: "8", className: "lg:grid-cols-12", children: [_jsx(Div, { className: "hidden lg:col-span-4 lg:block xl:col-span-3", children: _jsx(FAQCategorySidebar, { selectedCategory: selectedCategory, onCategorySelect: handleCategorySelect, categoryCounts: categoryCounts, categories: categories, allFaqsHref: routeHelpers.allFaqsHref, categoryHref: routeHelpers.faqCategoryHref, contactHref: routeHelpers.contactHref, labels: {
|
|
49
49
|
title: labels.categoriesTitle,
|
|
50
50
|
allFaqs: labels.allFaqs,
|
|
51
51
|
stillHaveQuestions: labels.stillHaveQuestions,
|
|
52
52
|
contactSupport: labels.contactSupport,
|
|
53
|
-
} }) }), _jsxs(Div, { className: "
|
|
53
|
+
} }) }), _jsxs(Div, { className: "lg:col-span-8 xl:col-span-9", children: [_jsx(SlottedListingView, { portal: "public", inlineToolbar: true, className: "space-y-6", renderHeader: () => {
|
|
54
54
|
/*
|
|
55
55
|
* The fuller answer, next to the short ones.
|
|
56
56
|
*
|
|
@@ -44,13 +44,13 @@ export declare const gatewayEventFirestoreSchema: z.ZodObject<{
|
|
|
44
44
|
terminal?: boolean | undefined;
|
|
45
45
|
amount?: number | undefined;
|
|
46
46
|
seedPurpose?: string | undefined;
|
|
47
|
+
payload?: Record<string, unknown> | undefined;
|
|
47
48
|
orderIds?: string[] | undefined;
|
|
48
49
|
processedAt?: Date | undefined;
|
|
49
50
|
gatewayPaymentId?: string | undefined;
|
|
50
51
|
gatewayOrderId?: string | undefined;
|
|
51
52
|
providerEventId?: string | undefined;
|
|
52
53
|
lastError?: string | undefined;
|
|
53
|
-
payload?: Record<string, unknown> | undefined;
|
|
54
54
|
payloadTruncated?: boolean | undefined;
|
|
55
55
|
}, {
|
|
56
56
|
status: "received" | "failed" | "processed" | "processing" | "conflict" | "ignored";
|
|
@@ -69,13 +69,13 @@ export declare const gatewayEventFirestoreSchema: z.ZodObject<{
|
|
|
69
69
|
terminal?: boolean | undefined;
|
|
70
70
|
amount?: number | undefined;
|
|
71
71
|
seedPurpose?: string | undefined;
|
|
72
|
+
payload?: Record<string, unknown> | undefined;
|
|
72
73
|
orderIds?: string[] | undefined;
|
|
73
74
|
processedAt?: Date | undefined;
|
|
74
75
|
gatewayPaymentId?: string | undefined;
|
|
75
76
|
gatewayOrderId?: string | undefined;
|
|
76
77
|
providerEventId?: string | undefined;
|
|
77
78
|
lastError?: string | undefined;
|
|
78
|
-
payload?: Record<string, unknown> | undefined;
|
|
79
79
|
payloadTruncated?: boolean | undefined;
|
|
80
80
|
}>;
|
|
81
81
|
export type GatewayEventFromSchema = z.infer<typeof gatewayEventFirestoreSchema>;
|