@mohasinac/appkit 4.61.0 → 4.62.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.build-stamp.json +3 -3
- package/dist/_internal/client/features/lottery/LotteryAdminEditView.js +19 -3
- package/dist/_internal/client/features/maintenance/views/AnalysisRunnerView.js +1 -1
- package/dist/_internal/client/features/maintenance/views/CloudLogsListView.js +1 -1
- package/dist/_internal/server/jobs/core/paymentReconcile.js +87 -97
- package/dist/_internal/shared/actions/action-registry.js +149 -9
- package/dist/_internal/shared/media/limits.d.ts +10 -0
- package/dist/_internal/shared/media/limits.js +10 -0
- package/dist/_internal/shared/types/action-result.js +15 -1
- package/dist/client/api/surface-error.js +16 -2
- package/dist/client/api/useApiMutation.js +17 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/constants/api-endpoints.d.ts +3 -0
- package/dist/constants/api-endpoints.js +1 -0
- package/dist/constants/field-names.d.ts +17 -0
- package/dist/constants/field-names.js +23 -0
- package/dist/core/server-action.js +19 -3
- package/dist/errors/error-mapping.js +26 -1
- package/dist/features/account/components/UserOffersPanel.js +5 -3
- package/dist/features/admin/components/AdminActionIndexView.js +100 -5
- package/dist/features/admin/components/AdminAddressClustersView.js +21 -2
- package/dist/features/admin/components/AdminBlogEditorView.js +8 -8
- package/dist/features/admin/components/AdminCarouselEditorView.js +1 -1
- package/dist/features/admin/components/AdminCarouselView.js +96 -4
- package/dist/features/admin/components/AdminFulfillmentView.js +3 -2
- package/dist/features/admin/components/AdminMediaView.js +36 -3
- package/dist/features/admin/components/AdminNavigationView.js +4 -1
- package/dist/features/admin/components/AdminOffersView.js +2 -1
- package/dist/features/admin/components/AdminPaymentClustersView.js +19 -2
- package/dist/features/admin/components/AdminProductEditorView.js +2 -2
- package/dist/features/admin/components/AdminSectionsView.d.ts +10 -0
- package/dist/features/admin/components/AdminSectionsView.js +486 -224
- package/dist/features/admin/components/ReferenceRecordEditor.js +1 -1
- package/dist/features/admin/hooks/useOptimisticRowFlag.d.ts +33 -0
- package/dist/features/admin/hooks/useOptimisticRowFlag.js +87 -0
- package/dist/features/admin/repository/notification.repository.d.ts +15 -0
- package/dist/features/admin/repository/notification.repository.js +32 -0
- package/dist/features/admin/schemas/action-index-form.d.ts +34 -0
- package/dist/features/admin/schemas/action-index-form.js +115 -0
- package/dist/features/auth/components/RegisterForm.js +2 -1
- package/dist/features/catalogue/components/CatalogueItemEditorView.js +3 -3
- package/dist/features/events/components/AdminEventEditorView.js +8 -6
- package/dist/features/layout/hooks/useFormBottomActions.js +20 -1
- package/dist/features/orders/components/EmiInstallmentsPanel.js +25 -7
- package/dist/features/orders/components/RefundRequestView.js +4 -4
- package/dist/features/orders/schemas/index.d.ts +6 -6
- package/dist/features/products/components/GroupMemberPicker.js +4 -1
- package/dist/features/products/components/PrizeDrawEntryActions.js +2 -1
- package/dist/features/products/components/ProductDetailActions.js +2 -1
- package/dist/features/seller/components/FulfillmentView.js +3 -2
- package/dist/features/seller/components/SellerAddressesView.js +32 -4
- package/dist/features/seller/components/SellerAnalyticsAlertsView.js +62 -3
- package/dist/features/seller/components/SellerBundlesView.js +53 -14
- package/dist/features/seller/components/SellerGroupedListingsView.js +23 -3
- package/dist/features/seller/components/SellerListingTypeView.js +56 -10
- package/dist/features/seller/components/SellerPayoutMethodsView.js +33 -1
- package/dist/features/seller/components/SellerPayoutSettingsView.js +48 -9
- package/dist/features/seller/components/SellerProductsView.js +5 -1
- package/dist/features/seller/components/SellerShippingConfigsView.js +33 -1
- package/dist/features/seller/components/SellerShippingView.js +2 -1
- package/dist/features/seller/components/SellerStoreCategoriesView.js +33 -1
- package/dist/features/seller/components/SellerSupportView.js +69 -2
- package/dist/features/shell/SectionForm.js +20 -16
- package/dist/features/shell/build-sections.js +14 -1
- package/dist/features/shipments/components/AdminShipmentLotItemsView.js +54 -1
- package/dist/features/shipments/components/AdminShipmentProjectionsView.js +3 -2
- package/dist/features/sticker-trace/components/StickerCommitPanel.js +2 -1
- package/dist/features/sticker-trace/components/StickerPrintPanel.js +2 -1
- package/dist/features/sticker-trace/components/StickerRetryPanel.js +24 -4
- package/dist/features/sticker-trace/components/StickerTraceStudio.js +2 -1
- package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +3 -2
- package/dist/index.d.ts +5 -1
- package/dist/index.js +3 -1
- package/dist/next/routing/route-map.d.ts +4 -0
- package/dist/next/routing/route-map.js +9 -0
- package/dist/schemas/registry.d.ts +4 -4
- package/dist/schemas/sieve.d.ts +3 -3
- package/dist/server.d.ts +1 -1
- package/dist/server.js +1 -1
- package/dist/ui/components/Checkbox.js +10 -2
- package/dist/ui/components/DateInput.js +8 -1
- package/dist/ui/components/Input.js +22 -2
- package/dist/ui/components/PaginatedSelect.d.ts +8 -19
- package/dist/ui/components/PaginatedSelect.js +43 -9
- package/dist/ui/components/Radio.js +8 -1
- package/dist/ui/components/Select.js +10 -2
- package/dist/ui/components/TagInput.d.ts +8 -1
- package/dist/ui/components/TagInput.js +4 -3
- package/dist/ui/components/Textarea.js +10 -2
- package/dist/ui/forms/FieldCheckbox.js +1 -1
- package/dist/ui/forms/FormErrorList.d.ts +0 -16
- package/dist/ui/forms/FormErrorList.js +34 -1
- package/dist/ui/forms/apply-zod-issues.d.ts +19 -1
- package/dist/ui/forms/apply-zod-issues.js +23 -1
- package/package.json +1 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import React from "react";
|
|
4
|
-
import { Button, Div, Heading, Modal, Pagination, Select, Skeleton, Stack, Table, Thead, Tbody, Tr, Th, Td, Text } from "../../../ui/index.js";
|
|
4
|
+
import { Button, Div, Heading, Modal, Pagination, Select, Skeleton, Stack, Table, Thead, Tbody, Tr, Th, Td, Text, TextLink } from "../../../ui/index.js";
|
|
5
|
+
import { ROUTES } from "../../../next/routing/route-map.js";
|
|
5
6
|
import { useShipmentProjections, useShipmentItems } from "../hooks/useShipments.js";
|
|
6
7
|
import { ShipmentItemLinkModal } from "./ShipmentItemLinkModal.js";
|
|
7
8
|
import { formatCurrency } from "../../../utils/number.formatter.js";
|
|
@@ -25,7 +26,7 @@ export function AdminShipmentProjectionsView() {
|
|
|
25
26
|
const [sorts, setSorts] = React.useState(SORT_OPTIONS[0].value);
|
|
26
27
|
const { lots, meta, isLoading, refetch } = useShipmentProjections({ page, pageSize, sorts });
|
|
27
28
|
const [pickerLot, setPickerLot] = React.useState(null);
|
|
28
|
-
return (_jsxs(Stack, { gap: "md", children: [_jsxs(Div, { layout: "flex", align: "center", justify: "between", children: [_jsx(Heading, { level: 2, children: "Projections" }), _jsx(Select, { "aria-label": "Sort by", value: sorts, onValueChange: setSorts, options: SORT_OPTIONS })] }), isLoading ? (_jsxs(Stack, { gap: "sm", children: [_jsx(Skeleton, { variant: "rectangular", height: "32px" }), _jsx(Skeleton, { variant: "rectangular", height: "32px" }), _jsx(Skeleton, { variant: "rectangular", height: "32px" })] })) : lots.length === 0 ? (_jsx(Text, { variant: "secondary", children: "No lots to project yet." })) : (_jsxs(Table, { children: [_jsx(Thead, { children: _jsxs(Tr, { children: [_jsx(Th, { children: "Lot" }), _jsx(Th, { children: "Items" }), _jsx(Th, { children: "Landed Cost" }), _jsx(Th, { children: "Projected Revenue" }), _jsx(Th, { children: "Projected Profit" }), _jsx(Th, {})] }) }), _jsx(Tbody, { children: lots.map((lot) => (_jsxs(Tr, { children: [_jsx(Td, { children: lot.lotName }), _jsx(Td, { children: lot.itemCount }), _jsx(Td, { children: formatCurrency(lot.totalLandedCost) }), _jsx(Td, { children: formatCurrency(lot.projectedRevenue) }), _jsx(Td, { className: lot.projectedProfit < 0 ? "text-error" : "text-success", children: formatCurrency(lot.projectedProfit) }), _jsx(Td, { children: _jsx(Button, { size: "sm", variant: "ghost", onClick: () => setPickerLot(lot), children: "Create pre-order link" }) })] }, lot.id))) })] })), meta && meta.totalPages > 1 && (_jsx(Pagination, { currentPage: page, totalPages: meta.totalPages, onPageChange: setPage, pageSize: pageSize, onPageSizeChange: (size) => {
|
|
29
|
+
return (_jsxs(Stack, { gap: "md", children: [_jsxs(Div, { layout: "flex", align: "center", justify: "between", children: [_jsx(Heading, { level: 2, children: "Projections" }), _jsx(Select, { "aria-label": "Sort by", value: sorts, onValueChange: setSorts, options: SORT_OPTIONS })] }), isLoading ? (_jsxs(Stack, { gap: "sm", children: [_jsx(Skeleton, { variant: "rectangular", height: "32px" }), _jsx(Skeleton, { variant: "rectangular", height: "32px" }), _jsx(Skeleton, { variant: "rectangular", height: "32px" })] })) : lots.length === 0 ? (_jsx(Text, { variant: "secondary", children: "No lots to project yet." })) : (_jsxs(Table, { children: [_jsx(Thead, { children: _jsxs(Tr, { children: [_jsx(Th, { children: "Lot" }), _jsx(Th, { children: "Items" }), _jsx(Th, { children: "Landed Cost" }), _jsx(Th, { children: "Projected Revenue" }), _jsx(Th, { children: "Projected Profit" }), _jsx(Th, {})] }) }), _jsx(Tbody, { children: lots.map((lot) => (_jsxs(Tr, { children: [_jsx(Td, { children: _jsx(TextLink, { href: String(ROUTES.ADMIN.SHIPMENT_LOT_ITEMS(lot.shipmentId, lot.id)), children: lot.lotName }) }), _jsx(Td, { children: lot.itemCount }), _jsx(Td, { children: formatCurrency(lot.totalLandedCost) }), _jsx(Td, { children: formatCurrency(lot.projectedRevenue) }), _jsx(Td, { className: lot.projectedProfit < 0 ? "text-error" : "text-success", children: formatCurrency(lot.projectedProfit) }), _jsx(Td, { children: _jsx(Button, { size: "sm", variant: "ghost", onClick: () => setPickerLot(lot), children: "Create pre-order link" }) })] }, lot.id))) })] })), meta && meta.totalPages > 1 && (_jsx(Pagination, { currentPage: page, totalPages: meta.totalPages, onPageChange: setPage, pageSize: pageSize, onPageSizeChange: (size) => {
|
|
29
30
|
setPageSize(size);
|
|
30
31
|
setPage(1);
|
|
31
32
|
} })), pickerLot && (_jsx(LotItemPickerModal, { lot: pickerLot, onClose: () => setPickerLot(null), onLinked: refetch }))] }));
|
|
@@ -37,6 +37,7 @@ import { Alert, Button, Card, Form, Heading, Stack, Text } from "../../../ui/ind
|
|
|
37
37
|
import { FieldInput } from "../../../ui/forms/FieldInput.js";
|
|
38
38
|
import { FieldSelect } from "../../../ui/forms/FieldSelect.js";
|
|
39
39
|
import { TIER_HELP } from "../constants/help-content.js";
|
|
40
|
+
import { toUserMessage } from "../../../errors/error-display-map.js";
|
|
40
41
|
/** Every tier, unlike the studio — see the header. */
|
|
41
42
|
const TIERS = ["draft", "standard", "precise", "max"];
|
|
42
43
|
export function StickerCommitPanel({ file, dimensions, onCommit, onCommitted, }) {
|
|
@@ -80,7 +81,7 @@ export function StickerCommitPanel({ file, dimensions, onCommit, onCommitted, })
|
|
|
80
81
|
* buyer, which this is not — and the routes already map their own
|
|
81
82
|
* failures to user-safe text.
|
|
82
83
|
*/
|
|
83
|
-
setError(n.
|
|
84
|
+
setError(toUserMessage(n.code, undefined, { fallback: "We couldn't commit that sheet. Please try again." }));
|
|
84
85
|
}
|
|
85
86
|
finally {
|
|
86
87
|
setPending(false);
|
|
@@ -32,6 +32,7 @@ import { useCallback, useEffect, useState } from "react";
|
|
|
32
32
|
import { normalizeError } from "../../../errors/normalize.js";
|
|
33
33
|
import { Alert, Badge, Button, Card, Heading, Row, Span, Stack, Text, } from "../../../ui/index.js";
|
|
34
34
|
import { FieldSelect } from "../../../ui/forms/FieldSelect.js";
|
|
35
|
+
import { toUserMessage } from "../../../errors/error-display-map.js";
|
|
35
36
|
const PROFILES = [
|
|
36
37
|
{ value: "print-shop", label: "Print shop (Roland / Graphtec)" },
|
|
37
38
|
{ value: "cricut-cut", label: "Cricut Joy 2 — cut only" },
|
|
@@ -83,7 +84,7 @@ export function StickerPrintPanel({ loadReport, onDownloadCutFile, onDownloadMos
|
|
|
83
84
|
* Rendering both the same way would have an operator confirming a DPI to
|
|
84
85
|
* fix a lapsed session.
|
|
85
86
|
*/
|
|
86
|
-
setError(n.
|
|
87
|
+
setError(toUserMessage(n.code, undefined, { fallback: "We couldn't prepare that print. Please try again." }));
|
|
87
88
|
setReport(null);
|
|
88
89
|
}
|
|
89
90
|
finally {
|
|
@@ -23,11 +23,18 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
23
23
|
* belongs to which stage.
|
|
24
24
|
*
|
|
25
25
|
* 🛑 Built on `<SectionForm>`, not a hand-rolled `<Form>`. Three controls is
|
|
26
|
-
* where a form stops being trivial, and sectionising is what buys the
|
|
27
|
-
*
|
|
26
|
+
* where a form stops being trivial, and sectionising is what buys the
|
|
27
|
+
* jump-to-field link and the pinned mobile action bar —
|
|
28
28
|
* audit-form-sectionised is strict-zero on exactly this, and its grandfather
|
|
29
29
|
* list is empty and closed.
|
|
30
30
|
*
|
|
31
|
+
* 🛑 The error summary is NOT free, and this comment used to claim it was.
|
|
32
|
+
* `SectionForm` deliberately does not own a `FormShellContext` — it consumes
|
|
33
|
+
* whichever one the owning view mounts, and tolerates none. So a view that
|
|
34
|
+
* only renders `<SectionForm schema={…}>` gets section badges and no readable
|
|
35
|
+
* message at all. The view must mount the context and render
|
|
36
|
+
* `<FormErrorSummary>` itself, which is what the bottom of this file now does.
|
|
37
|
+
*
|
|
31
38
|
* EXPORTS: StickerRetryPanel
|
|
32
39
|
*
|
|
33
40
|
* @tag domain:sticker-trace
|
|
@@ -43,11 +50,14 @@ import { normalizeError } from "../../../errors/normalize.js";
|
|
|
43
50
|
import { Alert, Card, Stack, Text } from "../../../ui/index.js";
|
|
44
51
|
import { FieldInput } from "../../../ui/forms/FieldInput.js";
|
|
45
52
|
import { FieldSelect } from "../../../ui/forms/FieldSelect.js";
|
|
53
|
+
import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary.js";
|
|
54
|
+
import { FormShellContext, useFormShellState } from "../../../ui/forms/FormShell.js";
|
|
46
55
|
import { SectionForm } from "../../shell/SectionForm.js";
|
|
47
56
|
import { TRACE_STAGES } from "../../../_internal/shared/features/sticker-trace/types.js";
|
|
48
57
|
import { resolveResumeStage, explainResume, } from "../../../_internal/shared/features/sticker-trace/resume.js";
|
|
49
58
|
import { paramsForTier } from "../../../_internal/shared/features/sticker-trace/config.js";
|
|
50
59
|
import { TIER_HELP } from "../constants/help-content.js";
|
|
60
|
+
import { toUserMessage } from "../../../errors/error-display-map.js";
|
|
51
61
|
const TIERS = ["draft", "standard", "precise", "max"];
|
|
52
62
|
/**
|
|
53
63
|
* 🛑 `.min(1)` on the name, mirroring `requireName` on the server.
|
|
@@ -103,7 +113,7 @@ export function StickerRetryPanel({ source, artifactsPruned = false, onSubmit, o
|
|
|
103
113
|
* operator looking at a form that did nothing — and the likeliest causes,
|
|
104
114
|
* a lapsed session or a rejected name, are both named by the message.
|
|
105
115
|
*/
|
|
106
|
-
setError(n.
|
|
116
|
+
setError(toUserMessage(n.code, undefined, { fallback: "We couldn't retry that run. Please try again." }));
|
|
107
117
|
}
|
|
108
118
|
finally {
|
|
109
119
|
setPending(false);
|
|
@@ -124,5 +134,15 @@ export function StickerRetryPanel({ source, artifactsPruned = false, onSubmit, o
|
|
|
124
134
|
render: ({ values: v, onChange: set }) => (_jsxs(Stack, { gap: "comfortable", children: [_jsx(FieldSelect, { name: "requested", label: "Resume from", value: v.requested, options: TRACE_STAGES.map((s) => ({ value: s, label: s })), onChange: (next) => set({ requested: next }) }), notice ? _jsx(Alert, { variant: "warning", children: notice }) : null, artifactsPruned ? (_jsx(Alert, { variant: "warning", children: "This run's artifacts have been cleared, so the retry starts from the beginning whatever you pick here." })) : null, !source.params ? (_jsx(Text, { size: "xs", color: "muted", children: "This run predates parameter recording, so the resume point cannot be checked ahead of time \u2014 the server will decide and tell you." })) : null] })),
|
|
125
135
|
},
|
|
126
136
|
], [source.name, source.params, notice, artifactsPruned]);
|
|
127
|
-
|
|
137
|
+
/*
|
|
138
|
+
* 🛑 `SectionForm` does NOT provide a FormShellContext — it reads whichever
|
|
139
|
+
* one the owning view mounts, and may legitimately get none. The header of
|
|
140
|
+
* this file says sectionising buys "the error summary … for free", and that
|
|
141
|
+
* was wrong: nothing here mounted a context or rendered a summary, so a
|
|
142
|
+
* schema rejection set the section badge and produced no readable message
|
|
143
|
+
* anywhere. The file passed `audit-form-error-summary` only because a bare
|
|
144
|
+
* `<SectionForm schema=>` was not a recognised call site.
|
|
145
|
+
*/
|
|
146
|
+
const { shellCtx } = useFormShellState(retrySchema);
|
|
147
|
+
return (_jsx(Card, { variant: "default", children: _jsx(FormShellContext.Provider, { value: shellCtx, children: _jsxs(Stack, { gap: "comfortable", children: [error ? _jsx(Alert, { variant: "error", children: error }) : null, _jsx(FormErrorSummary, {}), _jsx(SectionForm, { sections: sections, values: values, onChange: onChange, onSubmit: submit, schema: retrySchema, submitLabel: "Start retry", isLoading: pending, onCancel: onCancel, cancelLabel: "Cancel" })] }) }) }));
|
|
128
148
|
}
|
|
@@ -30,6 +30,7 @@ import { PARAM_HELP, TIER_HELP } from "../constants/help-content.js";
|
|
|
30
30
|
import { useLiveTrace } from "../hooks/useLiveTrace.js";
|
|
31
31
|
import { canvasCodec } from "../../../_internal/client/features/sticker-trace/canvas-codec.js";
|
|
32
32
|
import { normalizeError } from "../../../errors/normalize.js";
|
|
33
|
+
import { toUserMessage } from "../../../errors/error-display-map.js";
|
|
33
34
|
/*
|
|
34
35
|
* Only `draft` is offered. The hook runs on the main thread and draft's
|
|
35
36
|
* 1200px cap is what keeps one trace near a second; `standard`'s 3000 would
|
|
@@ -103,7 +104,7 @@ export function StickerTraceStudio({ renderCommit }) {
|
|
|
103
104
|
// the operator is the one who needs to know the commit is blocked.
|
|
104
105
|
if (live) {
|
|
105
106
|
setNatural(null);
|
|
106
|
-
setProbeError(n.
|
|
107
|
+
setProbeError(toUserMessage(n.code, undefined, { fallback: "That probe could not be run." }));
|
|
107
108
|
}
|
|
108
109
|
});
|
|
109
110
|
return () => {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
4
|
-
import { Button, Card, Details, Div, Row, Stack, Summary, Text } from "../../../ui/index.js";
|
|
4
|
+
import { Button, Card, Details, Div, Row, Stack, Summary, Text, TextLink } from "../../../ui/index.js";
|
|
5
|
+
import { ROUTES } from "../../../next/routing/route-map.js";
|
|
5
6
|
import { MediaImage } from "../../media/MediaImage.js";
|
|
6
7
|
import { apiClient } from "../../../http/index.js";
|
|
7
8
|
import { useApiMutation } from "../../../client/api/useApiMutation.js";
|
|
@@ -50,6 +51,6 @@ export function AdminTesterFeedbackIssuesView() {
|
|
|
50
51
|
const sortedPhases = Array.from(byPhase.keys()).sort((a, b) => a - b);
|
|
51
52
|
return (_jsx(Stack, { gap: "md", children: sortedPhases.map((phase) => {
|
|
52
53
|
const phaseIssues = byPhase.get(phase);
|
|
53
|
-
return (_jsxs(Details, { tone: "card", defaultOpen: sortedPhases.length === 1, children: [_jsxs(Summary, { size: "lg", weight: "bold", children: ["Phase ", phase, " (", phaseIssues.length, " issue", phaseIssues.length === 1 ? "" : "s", ")"] }), _jsx(Div, { padding: "t-sm", children: _jsx(Stack, { gap: "sm", children: phaseIssues.map((issue) => (_jsx(Card, { padding: "md", variant: "default", children: _jsxs(Stack, { gap: "xs", children: [_jsx(
|
|
54
|
+
return (_jsxs(Details, { tone: "card", defaultOpen: sortedPhases.length === 1, children: [_jsxs(Summary, { size: "lg", weight: "bold", children: ["Phase ", phase, " (", phaseIssues.length, " issue", phaseIssues.length === 1 ? "" : "s", ")"] }), _jsx(Div, { padding: "t-sm", children: _jsx(Stack, { gap: "sm", children: phaseIssues.map((issue) => (_jsx(Card, { padding: "md", variant: "default", children: _jsxs(Stack, { gap: "xs", children: [_jsx(TextLink, { href: String(ROUTES.ADMIN.TESTER_CHECKLIST_EDIT(issue.checklistItemId)), weight: "semibold", children: issue.label }), _jsxs(Text, { size: "sm", color: "muted", children: [issue.groupLabel, " \u203A ", issue.pageLabel] }), _jsxs(Text, { size: "sm", color: "muted", children: ["Reported by ", issue.testerDisplayName] }), issue.comment && _jsx(Text, { size: "sm", children: issue.comment }), issue.screenshotUrl && (_jsx(MediaImage, { src: issue.screenshotUrl, alt: "Tester screenshot", size: "card" })), issue.bugConfirmed ? (_jsxs(Row, { gap: "sm", align: "center", wrap: true, children: [_jsxs(Text, { size: "sm", weight: "semibold", color: "success", children: ["\uD83D\uDC1B Confirmed \u2014 credited to ", issue.bugHunterName ?? "unknown tester"] }), !issue.supersededByItemId && (_jsx(Button, { size: "sm", action: ACTIONS.ADMIN["reopen-checklist-item"], isLoading: reopenMutation.isPending, onClick: () => reopenMutation.mutate(issue.checklistItemId) }))] })) : (_jsx(Div, { children: _jsx(Button, { size: "sm", action: ACTIONS.ADMIN["confirm-bug"], isLoading: confirmBugMutation.isPending, onClick: () => confirmBugMutation.mutate(issue.id) }) }))] }) }, issue.id))) }) })] }, phase));
|
|
54
55
|
}) }));
|
|
55
56
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -52,6 +52,10 @@ export type { FieldSelectProps } from "./ui/index";
|
|
|
52
52
|
export { FieldSelect } from "./ui/index";
|
|
53
53
|
export type { FieldCheckboxProps } from "./ui/index";
|
|
54
54
|
export { FieldCheckbox } from "./ui/index";
|
|
55
|
+
export type { FieldTextareaProps } from "./ui/index";
|
|
56
|
+
export { FieldTextarea } from "./ui/index";
|
|
57
|
+
export type { FormErrorSummaryProps } from "./ui/index";
|
|
58
|
+
export { FormErrorSummary } from "./ui/index";
|
|
55
59
|
export { GRID_MAP } from "./ui/index";
|
|
56
60
|
export { ImageGallery } from "./ui/index";
|
|
57
61
|
export { ImageLightbox } from "./ui/index";
|
|
@@ -2940,7 +2944,7 @@ export type { OwnerQuickActionsProps, OwnerQuickActionsResource } from "./featur
|
|
|
2940
2944
|
export type { ListingTypePlugin } from "./_internal/shared/listing-types/_registry";
|
|
2941
2945
|
export { getListingRule, getCategoryRule, pickOrderType, getSplitKey, runSyncPreflight, CHECKOUT_RULES, CATEGORY_CHECKOUT_RULES, CHECKOUT_MAX_ORDERS_PER_TX, PRIZE_DRAW_MAX_REVEALS_PER_ORDER, BUNDLE_MAX_QTY_PER_TX, STANDARD_MAX_QTY_PER_LINE, } from "./_internal/shared/checkout/rules";
|
|
2942
2946
|
export type { ListingCheckoutRule, CategoryCheckoutRule, CartItemProductPair, OrderItemInput, RefundPolicy, } from "./_internal/shared/checkout/rules";
|
|
2943
|
-
export { MEGABYTE, MAX_IMAGE_BYTES, MAX_PDF_BYTES, MAX_VIDEO_BYTES, MAX_LABEL, MAX_BYTES, ALLOWED_IMAGE_MIMES, IMAGE_ACCEPT_ATTR, ALLOWED_VIDEO_MIMES, ALLOWED_DOC_MIMES, ALLOWED_MIMES, ALLOWED_TYPES_LABEL, MIME_TO_EXT, PDF_MAGIC, VIDEO_CONVERSION_HINTS, classifyMime, isAllowedMime, maxBytesFor, getConversionHint, PRODUCT_MAX_IMAGES, PRODUCT_MAX_VIDEOS, PRODUCT_IMAGE_INDEX_MAX, } from "./_internal/shared/media/limits";
|
|
2947
|
+
export { MEGABYTE, MAX_IMAGE_BYTES, MAX_PDF_BYTES, MAX_VIDEO_BYTES, MAX_LABEL, MAX_BYTES, ALLOWED_IMAGE_MIMES, IMAGE_ACCEPT_ATTR, ALLOWED_VIDEO_MIMES, ALLOWED_DOC_MIMES, ALLOWED_MIMES, ALLOWED_TYPES_LABEL, MIME_TO_EXT, PDF_MAGIC, VIDEO_CONVERSION_HINTS, classifyMime, isAllowedMime, maxBytesFor, getConversionHint, PRODUCT_MAX_IMAGES, REVIEW_MAX_IMAGES, PRODUCT_MAX_VIDEOS, PRODUCT_IMAGE_INDEX_MAX, } from "./_internal/shared/media/limits";
|
|
2944
2948
|
export type { MediaKind, AllowedImageMime, AllowedVideoMime, AllowedDocMime, AllowedMime, } from "./_internal/shared/media/limits";
|
|
2945
2949
|
export { PRODUCT_FIELDS, PRODUCT_STATUS_TRANSITIONS, ORDER_FIELDS, REVIEW_FIELDS, BID_FIELDS, AD_FIELDS, AD_TRACKED_FIELDS, BLOG_FIELDS, BRAND_FIELDS, WISHLIST_FIELDS, HISTORY_FIELDS, COUPON_USAGE_FIELDS, CAROUSEL_FIELDS, FAQ_FIELDS, HOMEPAGE_SECTION_FIELDS, SITE_SETTINGS_FIELDS, COMMON_FIELDS, OAUTH_STATE_VALUES, SCHEMA_DEFAULTS, } from "./constants/field-names";
|
|
2946
2950
|
export { SORT_DIR, sortBy } from "./constants/sort";
|
package/dist/index.js
CHANGED
|
@@ -138,6 +138,8 @@ export { FormGroup } from "./ui/index.js";
|
|
|
138
138
|
export { FieldInput } from "./ui/index.js";
|
|
139
139
|
export { FieldSelect } from "./ui/index.js";
|
|
140
140
|
export { FieldCheckbox } from "./ui/index.js";
|
|
141
|
+
export { FieldTextarea } from "./ui/index.js";
|
|
142
|
+
export { FormErrorSummary } from "./ui/index.js";
|
|
141
143
|
// [CLIENT-SSR]-Runs in both SSR and browser â€" React component or hook that does not depend on browser-only APIs.
|
|
142
144
|
// GRID_MAP - Constant used across modules.
|
|
143
145
|
export { GRID_MAP } from "./ui/index.js";
|
|
@@ -5249,7 +5251,7 @@ export { RecordOwnerQuickActions } from "./features/admin/components/RecordOwner
|
|
|
5249
5251
|
// Checkout rule registry â€" pure TS, safe in both client and server bundles.
|
|
5250
5252
|
export { getListingRule, getCategoryRule, pickOrderType, getSplitKey, runSyncPreflight, CHECKOUT_RULES, CATEGORY_CHECKOUT_RULES, CHECKOUT_MAX_ORDERS_PER_TX, PRIZE_DRAW_MAX_REVEALS_PER_ORDER, BUNDLE_MAX_QTY_PER_TX, STANDARD_MAX_QTY_PER_LINE, } from "./_internal/shared/checkout/rules/index.js";
|
|
5251
5253
|
// Media upload limits â€" pure constants, safe in both client and server bundles.
|
|
5252
|
-
export { MEGABYTE, MAX_IMAGE_BYTES, MAX_PDF_BYTES, MAX_VIDEO_BYTES, MAX_LABEL, MAX_BYTES, ALLOWED_IMAGE_MIMES, IMAGE_ACCEPT_ATTR, ALLOWED_VIDEO_MIMES, ALLOWED_DOC_MIMES, ALLOWED_MIMES, ALLOWED_TYPES_LABEL, MIME_TO_EXT, PDF_MAGIC, VIDEO_CONVERSION_HINTS, classifyMime, isAllowedMime, maxBytesFor, getConversionHint, PRODUCT_MAX_IMAGES, PRODUCT_MAX_VIDEOS, PRODUCT_IMAGE_INDEX_MAX, } from "./_internal/shared/media/limits.js";
|
|
5254
|
+
export { MEGABYTE, MAX_IMAGE_BYTES, MAX_PDF_BYTES, MAX_VIDEO_BYTES, MAX_LABEL, MAX_BYTES, ALLOWED_IMAGE_MIMES, IMAGE_ACCEPT_ATTR, ALLOWED_VIDEO_MIMES, ALLOWED_DOC_MIMES, ALLOWED_MIMES, ALLOWED_TYPES_LABEL, MIME_TO_EXT, PDF_MAGIC, VIDEO_CONVERSION_HINTS, classifyMime, isAllowedMime, maxBytesFor, getConversionHint, PRODUCT_MAX_IMAGES, REVIEW_MAX_IMAGES, PRODUCT_MAX_VIDEOS, PRODUCT_IMAGE_INDEX_MAX, } from "./_internal/shared/media/limits.js";
|
|
5253
5255
|
// ── Field name constants — canonical source of truth for all Firestore field names ──
|
|
5254
5256
|
// NOTE: STORE_FIELDS, CATEGORY_FIELDS, EVENT_FIELDS, EVENT_ENTRY_FIELDS, PAYOUT_FIELDS,
|
|
5255
5257
|
// COUPON_FIELDS, CART_FIELDS, USER_FIELDS, ADDRESS_FIELDS, NOTIFICATION_FIELDS,
|
|
@@ -182,6 +182,8 @@ export declare const DEFAULT_ROUTE_MAP: {
|
|
|
182
182
|
readonly CLAIMED_COUPONS: "/user/coupons";
|
|
183
183
|
/** Points balance + ledger. 1 point = ₹1, spendable at checkout. */
|
|
184
184
|
readonly POINTS: "/user/points";
|
|
185
|
+
readonly SESSIONS: "/user/sessions";
|
|
186
|
+
readonly PAYMENT_METHODS: "/user/payment-methods";
|
|
185
187
|
readonly BECOME_SELLER: "/user/become-seller";
|
|
186
188
|
readonly CART: "/cart";
|
|
187
189
|
readonly CHECKOUT: "/checkout";
|
|
@@ -634,6 +636,8 @@ export declare const ROUTES: {
|
|
|
634
636
|
readonly CLAIMED_COUPONS: "/user/coupons";
|
|
635
637
|
/** Points balance + ledger. 1 point = ₹1, spendable at checkout. */
|
|
636
638
|
readonly POINTS: "/user/points";
|
|
639
|
+
readonly SESSIONS: "/user/sessions";
|
|
640
|
+
readonly PAYMENT_METHODS: "/user/payment-methods";
|
|
637
641
|
readonly BECOME_SELLER: "/user/become-seller";
|
|
638
642
|
readonly CART: "/cart";
|
|
639
643
|
readonly CHECKOUT: "/checkout";
|
|
@@ -302,6 +302,15 @@ export const DEFAULT_ROUTE_MAP = {
|
|
|
302
302
|
CLAIMED_COUPONS: "/user/coupons",
|
|
303
303
|
/** Points balance + ledger. 1 point = ₹1, spendable at checkout. */
|
|
304
304
|
POINTS: "/user/points",
|
|
305
|
+
/*
|
|
306
|
+
* 🛑 Both of these had a COMPLETE API and no page. `GET /api/user/sessions`
|
|
307
|
+
* + `DELETE [id]`, and `GET/POST /api/user/payment-methods` + `DELETE
|
|
308
|
+
* [id]`, were all live with zero consumers — and `BuyerAccountGuideView`
|
|
309
|
+
* already told buyers to "view all devices signed in at My Account →
|
|
310
|
+
* Sessions", pointing at a security control that did not exist.
|
|
311
|
+
*/
|
|
312
|
+
SESSIONS: "/user/sessions",
|
|
313
|
+
PAYMENT_METHODS: "/user/payment-methods",
|
|
305
314
|
BECOME_SELLER: "/user/become-seller",
|
|
306
315
|
CART: "/cart",
|
|
307
316
|
CHECKOUT: "/checkout",
|
|
@@ -2801,8 +2801,8 @@ export declare const SCHEMAS: {
|
|
|
2801
2801
|
}, import("zod").ZodTypeAny, "passthrough">>]>>;
|
|
2802
2802
|
}, "strip", import("zod").ZodTypeAny, {
|
|
2803
2803
|
status: "paid" | "pending" | "overdue";
|
|
2804
|
-
amount: number;
|
|
2805
2804
|
index: number;
|
|
2805
|
+
amount: number;
|
|
2806
2806
|
dueDate: string | Date | import("zod").objectOutputType<{
|
|
2807
2807
|
toDate: import("zod").ZodFunction<import("zod").ZodTuple<[], import("zod").ZodUnknown>, import("zod").ZodDate>;
|
|
2808
2808
|
}, import("zod").ZodTypeAny, "passthrough">;
|
|
@@ -2816,8 +2816,8 @@ export declare const SCHEMAS: {
|
|
|
2816
2816
|
}, import("zod").ZodTypeAny, "passthrough"> | undefined;
|
|
2817
2817
|
}, {
|
|
2818
2818
|
status: "paid" | "pending" | "overdue";
|
|
2819
|
-
amount: number;
|
|
2820
2819
|
index: number;
|
|
2820
|
+
amount: number;
|
|
2821
2821
|
dueDate: string | Date | import("zod").objectInputType<{
|
|
2822
2822
|
toDate: import("zod").ZodFunction<import("zod").ZodTuple<[], import("zod").ZodUnknown>, import("zod").ZodDate>;
|
|
2823
2823
|
}, import("zod").ZodTypeAny, "passthrough">;
|
|
@@ -3080,8 +3080,8 @@ export declare const SCHEMAS: {
|
|
|
3080
3080
|
emiSurchargeAmount?: number | undefined;
|
|
3081
3081
|
emiInstallments?: {
|
|
3082
3082
|
status: "paid" | "pending" | "overdue";
|
|
3083
|
-
amount: number;
|
|
3084
3083
|
index: number;
|
|
3084
|
+
amount: number;
|
|
3085
3085
|
dueDate: string | Date | import("zod").objectOutputType<{
|
|
3086
3086
|
toDate: import("zod").ZodFunction<import("zod").ZodTuple<[], import("zod").ZodUnknown>, import("zod").ZodDate>;
|
|
3087
3087
|
}, import("zod").ZodTypeAny, "passthrough">;
|
|
@@ -3234,8 +3234,8 @@ export declare const SCHEMAS: {
|
|
|
3234
3234
|
emiSurchargeAmount?: number | undefined;
|
|
3235
3235
|
emiInstallments?: {
|
|
3236
3236
|
status: "paid" | "pending" | "overdue";
|
|
3237
|
-
amount: number;
|
|
3238
3237
|
index: number;
|
|
3238
|
+
amount: number;
|
|
3239
3239
|
dueDate: string | Date | import("zod").objectInputType<{
|
|
3240
3240
|
toDate: import("zod").ZodFunction<import("zod").ZodTuple<[], import("zod").ZodUnknown>, import("zod").ZodDate>;
|
|
3241
3241
|
}, import("zod").ZodTypeAny, "passthrough">;
|
package/dist/schemas/sieve.d.ts
CHANGED
|
@@ -23,12 +23,12 @@ export declare const sieveFilterClauseSchema: z.ZodObject<{
|
|
|
23
23
|
value: z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull, z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean]>, "many">]>;
|
|
24
24
|
}, "strip", z.ZodTypeAny, {
|
|
25
25
|
value: string | number | boolean | (string | number | boolean)[] | null;
|
|
26
|
-
op: "==" | "!=" | "<" | "<=" | ">" | ">=" | "array-contains" | "in" | "not-in" | "array-contains-any";
|
|
27
26
|
field: string;
|
|
27
|
+
op: "==" | "!=" | "<" | "<=" | ">" | ">=" | "array-contains" | "in" | "not-in" | "array-contains-any";
|
|
28
28
|
}, {
|
|
29
29
|
value: string | number | boolean | (string | number | boolean)[] | null;
|
|
30
|
-
op: "==" | "!=" | "<" | "<=" | ">" | ">=" | "array-contains" | "in" | "not-in" | "array-contains-any";
|
|
31
30
|
field: string;
|
|
31
|
+
op: "==" | "!=" | "<" | "<=" | ">" | ">=" | "array-contains" | "in" | "not-in" | "array-contains-any";
|
|
32
32
|
}>;
|
|
33
33
|
export type SieveFilterClause = z.infer<typeof sieveFilterClauseSchema>;
|
|
34
34
|
export type SieveWhereOp = z.infer<typeof sieveWhereOpSchema>;
|
|
@@ -36,8 +36,8 @@ export declare const sieveSortClauseSchema: z.ZodObject<{
|
|
|
36
36
|
field: z.ZodString;
|
|
37
37
|
direction: z.ZodDefault<z.ZodEnum<["asc", "desc"]>>;
|
|
38
38
|
}, "strip", z.ZodTypeAny, {
|
|
39
|
-
direction: "desc" | "asc";
|
|
40
39
|
field: string;
|
|
40
|
+
direction: "desc" | "asc";
|
|
41
41
|
}, {
|
|
42
42
|
field: string;
|
|
43
43
|
direction?: "desc" | "asc" | undefined;
|
package/dist/server.d.ts
CHANGED
|
@@ -502,7 +502,7 @@ export { LISTING_TYPE_REGISTRY, pluginFor, detectListingTypeFromSlug } from "./_
|
|
|
502
502
|
export type { ListingTypePlugin } from "./_internal/shared/listing-types/_registry";
|
|
503
503
|
export { applyMediaContextGuards, CONTEXT_LIMITS, } from "./_internal/server/features/media/contextGuards";
|
|
504
504
|
export type { GuardResult, GuardError, GuardSuccess } from "./_internal/server/features/media/contextGuards";
|
|
505
|
-
export { MEGABYTE, MAX_IMAGE_BYTES, MAX_PDF_BYTES, MAX_VIDEO_BYTES, MAX_LABEL, MAX_BYTES, ALLOWED_IMAGE_MIMES, IMAGE_ACCEPT_ATTR, ALLOWED_VIDEO_MIMES, ALLOWED_DOC_MIMES, ALLOWED_MIMES, ALLOWED_TYPES_LABEL, MIME_TO_EXT, PDF_MAGIC, VIDEO_CONVERSION_HINTS, classifyMime, isAllowedMime, maxBytesFor, getConversionHint, PRODUCT_MAX_IMAGES, PRODUCT_MAX_VIDEOS, PRODUCT_IMAGE_INDEX_MAX, } from "./_internal/shared/media/limits";
|
|
505
|
+
export { MEGABYTE, MAX_IMAGE_BYTES, MAX_PDF_BYTES, MAX_VIDEO_BYTES, MAX_LABEL, MAX_BYTES, ALLOWED_IMAGE_MIMES, IMAGE_ACCEPT_ATTR, ALLOWED_VIDEO_MIMES, ALLOWED_DOC_MIMES, ALLOWED_MIMES, ALLOWED_TYPES_LABEL, MIME_TO_EXT, PDF_MAGIC, VIDEO_CONVERSION_HINTS, classifyMime, isAllowedMime, maxBytesFor, getConversionHint, PRODUCT_MAX_IMAGES, REVIEW_MAX_IMAGES, PRODUCT_MAX_VIDEOS, PRODUCT_IMAGE_INDEX_MAX, } from "./_internal/shared/media/limits";
|
|
506
506
|
export type { MediaKind, AllowedImageMime, AllowedVideoMime, AllowedDocMime, AllowedMime, } from "./_internal/shared/media/limits";
|
|
507
507
|
export { getServerPermissions, checkPermission, checkAnyPermission, makeAdminSectionLayout, } from "./_internal/server/features/auth/permissions";
|
|
508
508
|
export type { ResolvedPermissions, AdminSectionLayoutOpts, } from "./_internal/server/features/auth/permissions";
|
package/dist/server.js
CHANGED
|
@@ -1435,7 +1435,7 @@ export { LISTING_TYPE_REGISTRY, pluginFor, detectListingTypeFromSlug } from "./_
|
|
|
1435
1435
|
// Media context guards — shared by /api/media/sign + legacy /api/media/upload.
|
|
1436
1436
|
export { applyMediaContextGuards, CONTEXT_LIMITS, } from "./_internal/server/features/media/contextGuards.js";
|
|
1437
1437
|
// Media upload limits — shared by /api/media/sign + /api/media/finalize + /api/media/upload.
|
|
1438
|
-
export { MEGABYTE, MAX_IMAGE_BYTES, MAX_PDF_BYTES, MAX_VIDEO_BYTES, MAX_LABEL, MAX_BYTES, ALLOWED_IMAGE_MIMES, IMAGE_ACCEPT_ATTR, ALLOWED_VIDEO_MIMES, ALLOWED_DOC_MIMES, ALLOWED_MIMES, ALLOWED_TYPES_LABEL, MIME_TO_EXT, PDF_MAGIC, VIDEO_CONVERSION_HINTS, classifyMime, isAllowedMime, maxBytesFor, getConversionHint, PRODUCT_MAX_IMAGES, PRODUCT_MAX_VIDEOS, PRODUCT_IMAGE_INDEX_MAX, } from "./_internal/shared/media/limits.js";
|
|
1438
|
+
export { MEGABYTE, MAX_IMAGE_BYTES, MAX_PDF_BYTES, MAX_VIDEO_BYTES, MAX_LABEL, MAX_BYTES, ALLOWED_IMAGE_MIMES, IMAGE_ACCEPT_ATTR, ALLOWED_VIDEO_MIMES, ALLOWED_DOC_MIMES, ALLOWED_MIMES, ALLOWED_TYPES_LABEL, MIME_TO_EXT, PDF_MAGIC, VIDEO_CONVERSION_HINTS, classifyMime, isAllowedMime, maxBytesFor, getConversionHint, PRODUCT_MAX_IMAGES, REVIEW_MAX_IMAGES, PRODUCT_MAX_VIDEOS, PRODUCT_IMAGE_INDEX_MAX, } from "./_internal/shared/media/limits.js";
|
|
1439
1439
|
// ── RBAC server helpers ───────────────────────────────────────────────────────
|
|
1440
1440
|
// getServerPermissions, checkPermission, makeAdminSectionLayout
|
|
1441
1441
|
export { getServerPermissions, checkPermission, checkAnyPermission, makeAdminSectionLayout, } from "./_internal/server/features/auth/permissions.js";
|
|
@@ -19,13 +19,21 @@ export function Checkbox({ label, suffix, error, indeterminate = false, type = "
|
|
|
19
19
|
const generatedId = React.useId();
|
|
20
20
|
const inputId = id ?? generatedId;
|
|
21
21
|
const inputRef = React.useRef(null);
|
|
22
|
+
/*
|
|
23
|
+
* A caller's own `aria-invalid` wins. `{...props}` is spread first, so
|
|
24
|
+
* re-specifying the attribute afterwards used to overwrite an incoming
|
|
25
|
+
* `"true"` with `undefined` — and `FieldCheckbox` sets it itself while
|
|
26
|
+
* withholding `error` (to avoid a double message). See the full writeup in
|
|
27
|
+
* `Input.tsx`; the same bug existed here.
|
|
28
|
+
*/
|
|
29
|
+
const ariaInvalid = props["aria-invalid"] ?? (error ? "true" : undefined);
|
|
22
30
|
React.useEffect(() => {
|
|
23
31
|
if (inputRef.current) {
|
|
24
32
|
inputRef.current.indeterminate = indeterminate;
|
|
25
33
|
}
|
|
26
34
|
}, [indeterminate]);
|
|
27
35
|
if (bare) {
|
|
28
|
-
return (_jsx("input", { ...props, ref: inputRef, id: inputId, type: type, checked: checked, disabled: disabled, "aria-invalid":
|
|
36
|
+
return (_jsx("input", { ...props, ref: inputRef, id: inputId, type: type, checked: checked, disabled: disabled, "aria-invalid": ariaInvalid, className: className }));
|
|
29
37
|
}
|
|
30
38
|
// A checkbox with no label/suffix has no text to lay out beside the box, so
|
|
31
39
|
// the default `width: 100%` on the root serves no purpose and actively harms
|
|
@@ -43,7 +51,7 @@ export function Checkbox({ label, suffix, error, indeterminate = false, type = "
|
|
|
43
51
|
disabled ? UI_CHECKBOX.labelDisabled : "",
|
|
44
52
|
]
|
|
45
53
|
.filter(Boolean)
|
|
46
|
-
.join(" "), children: [_jsxs("span", { className: UI_CHECKBOX.boxWrap, children: [_jsx("input", { ...props, ref: inputRef, id: inputId, type: type, checked: checked, disabled: disabled, "aria-invalid":
|
|
54
|
+
.join(" "), children: [_jsxs("span", { className: UI_CHECKBOX.boxWrap, children: [_jsx("input", { ...props, ref: inputRef, id: inputId, type: type, checked: checked, disabled: disabled, "aria-invalid": ariaInvalid, className: [
|
|
47
55
|
UI_CHECKBOX.input,
|
|
48
56
|
error ? UI_CHECKBOX.inputError : "",
|
|
49
57
|
className,
|
|
@@ -5,7 +5,14 @@ import { Label, Span, Text } from "./Typography.js";
|
|
|
5
5
|
export const DateInput = React.forwardRef(function DateInput({ label, error, helperText, value, onChange, disabled, required, id, className = "", ...props }, ref) {
|
|
6
6
|
const generatedId = React.useId();
|
|
7
7
|
const inputId = id ?? generatedId;
|
|
8
|
-
|
|
8
|
+
/*
|
|
9
|
+
* A caller's own `aria-invalid` wins. `{...props}` is spread first, so
|
|
10
|
+
* re-specifying the attribute afterwards overwrites an incoming "true"
|
|
11
|
+
* with `undefined` — the defect that left every Field*-built form in the
|
|
12
|
+
* app with no aria-invalid at all. See the full writeup in `Input.tsx`.
|
|
13
|
+
*/
|
|
14
|
+
const ariaInvalid = props["aria-invalid"] ?? (error ? "true" : undefined);
|
|
15
|
+
return (_jsxs("div", { className: "w-full", children: [label && (_jsx(Label, { htmlFor: inputId, required: required, className: "appkit-form-field__label mb-1.5", children: label })), _jsx("input", { ...props, ref: ref, id: inputId, type: "date", value: value, disabled: disabled, required: required, "aria-invalid": ariaInvalid, onChange: (e) => onChange?.(e.target.value), className: ["appkit-input", error ? "appkit-input--error" : "", className].filter(Boolean).join(" ") }), error ? (_jsx(Text, { size: "sm", variant: "error", role: "alert", className: "mt-1.5", children: error })) : helperText ? (_jsx(Text, { size: "sm", variant: "secondary", className: "mt-1.5", children: helperText })) : null] }));
|
|
9
16
|
});
|
|
10
17
|
export function DateRangeInput({ label, startLabel = "From", endLabel = "To", startValue, endValue, onStartChange, onEndChange, error, helperText, disabled, required, startMin, startMax, endMin, endMax, }) {
|
|
11
18
|
return (_jsxs("div", { className: "w-full", children: [label && (_jsx(Text, { size: "sm", className: "appkit-form-field__label mb-1.5 block font-medium", children: label })), _jsxs("div", { className: "flex items-end gap-2", children: [_jsx(DateInput, { label: startLabel, value: startValue, onChange: onStartChange, disabled: disabled, required: required, min: startMin, max: startMax ?? endValue }), _jsx(Span, { size: "sm", className: "mb-2.5 shrink-0 text-[var(--appkit-color-text-muted)]", children: "\u2013" }), _jsx(DateInput, { label: endLabel, value: endValue, onChange: onEndChange, disabled: disabled, min: endMin ?? startValue, max: endMax })] }), error ? (_jsx(Text, { size: "sm", variant: "error", role: "alert", className: "mt-1.5", children: error })) : helperText ? (_jsx(Text, { size: "sm", variant: "secondary", className: "mt-1.5", children: helperText })) : null] }));
|
|
@@ -13,6 +13,26 @@ export const Input = React.forwardRef(function Input({ label, error, helperText,
|
|
|
13
13
|
: variant === "ghost"
|
|
14
14
|
? "border-transparent bg-transparent shadow-none"
|
|
15
15
|
: "";
|
|
16
|
+
/*
|
|
17
|
+
* 🛑 A CALLER'S OWN `aria-invalid` MUST WIN — it did not, and that broke
|
|
18
|
+
* every form in the app.
|
|
19
|
+
*
|
|
20
|
+
* `{...props}` is spread FIRST on both `<input>` elements below, so
|
|
21
|
+
* re-specifying `aria-invalid={error ? "true" : undefined}` afterwards
|
|
22
|
+
* OVERWROTE an incoming `"true"` with `undefined` (JSX: later wins).
|
|
23
|
+
*
|
|
24
|
+
* That is not hypothetical. `FieldInput` sets `aria-invalid` itself and
|
|
25
|
+
* deliberately WITHHOLDS `error` from this component, so the message is not
|
|
26
|
+
* rendered twice — which means `error` here is `undefined` exactly when the
|
|
27
|
+
* field is invalid. Net effect: no `Field*`-built form ever emitted
|
|
28
|
+
* `aria-invalid`, and `SectionForm`'s jump-to-error
|
|
29
|
+
* (`querySelector("[aria-invalid='true']")?.focus()`) silently focused
|
|
30
|
+
* nothing, every time.
|
|
31
|
+
*
|
|
32
|
+
* Same precedence rule as `bareAriaLabel` below: fill the gap, never
|
|
33
|
+
* override an explicit choice.
|
|
34
|
+
*/
|
|
35
|
+
const ariaInvalid = props["aria-invalid"] ?? (error ? "true" : undefined);
|
|
16
36
|
const inputClassName = [
|
|
17
37
|
"appkit-input",
|
|
18
38
|
icon ? "pl-10" : "",
|
|
@@ -45,10 +65,10 @@ export const Input = React.forwardRef(function Input({ label, error, helperText,
|
|
|
45
65
|
* overrides an explicit choice.
|
|
46
66
|
*/
|
|
47
67
|
const bareAriaLabel = props["aria-label"] ?? (typeof label === "string" ? label : undefined);
|
|
48
|
-
return (_jsx("input", { ...props, ref: ref, id: inputId, disabled: disabled, "aria-label": bareAriaLabel, "aria-invalid":
|
|
68
|
+
return (_jsx("input", { ...props, ref: ref, id: inputId, disabled: disabled, "aria-label": bareAriaLabel, "aria-invalid": ariaInvalid, className: inputClassName }));
|
|
49
69
|
}
|
|
50
70
|
const adornment = rightIcon ??
|
|
51
71
|
(success && !error ? (_jsx("svg", { className: CLS_INPUT_SUCCESS_ICON, viewBox: "0 0 20 20", fill: "currentColor", "aria-hidden": "true", children: _jsx("path", { fillRule: "evenodd", d: "M16.704 5.29a1 1 0 010 1.42l-7.2 7.2a1 1 0 01-1.415 0l-3.2-3.2a1 1 0 111.414-1.42l2.493 2.494 6.493-6.494a1 1 0 011.415 0z", clipRule: "evenodd" }) })) : null);
|
|
52
|
-
return (_jsxs("div", { className: "w-full", "data-section": "input-div-527", children: [label && (_jsx(Label, { htmlFor: inputId, required: required, children: label })), _jsxs("div", { className: "relative", "data-section": "input-div-528", children: [icon && (_jsx(Div, { className: "pointer-events-none absolute inset-y-0 left-3 flex items-center text-[var(--appkit-color-text-faint)]", "data-section": "input-div-529", children: icon })), _jsx("input", { ...props, ref: ref, id: inputId, disabled: disabled, "aria-invalid":
|
|
72
|
+
return (_jsxs("div", { className: "w-full", "data-section": "input-div-527", children: [label && (_jsx(Label, { htmlFor: inputId, required: required, children: label })), _jsxs("div", { className: "relative", "data-section": "input-div-528", children: [icon && (_jsx(Div, { className: "pointer-events-none absolute inset-y-0 left-3 flex items-center text-[var(--appkit-color-text-faint)]", "data-section": "input-div-529", children: icon })), _jsx("input", { ...props, ref: ref, id: inputId, disabled: disabled, "aria-invalid": ariaInvalid, className: inputClassName }), adornment && (_jsx(Div, { className: "pointer-events-none absolute inset-y-0 right-3 flex items-center text-[var(--appkit-color-text-faint)]", "data-section": "input-div-530", children: adornment }))] }), error ? (_jsx(Text, { size: "sm", variant: "error", className: "mt-1.5", role: "alert", children: error })) : helperText ? (_jsx(Text, { size: "sm", variant: "secondary", className: "mt-1.5", children: helperText })) : null] }));
|
|
53
73
|
});
|
|
54
74
|
Input.displayName = "Input";
|
|
@@ -20,6 +20,14 @@ interface BaseProps<V> {
|
|
|
20
20
|
disabled?: boolean;
|
|
21
21
|
className?: string;
|
|
22
22
|
ariaLabel?: string;
|
|
23
|
+
/** Field name — resolves this control's error from `FormShellContext`, as `Field*` do. */
|
|
24
|
+
name?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Validation message. Explicit prop wins; else read from context by `name`.
|
|
27
|
+
* Without these two this control had no error affordance at all: a required
|
|
28
|
+
* taxonomy pick could fail and the control showed nothing.
|
|
29
|
+
*/
|
|
30
|
+
error?: React.ReactNode;
|
|
23
31
|
/** "+ Create new …" button. Provide `renderCreateForm` OR (`createFields` + `onCreateSubmit`). */
|
|
24
32
|
createLabel?: string;
|
|
25
33
|
drawerTitle?: string;
|
|
@@ -59,24 +67,5 @@ interface MultiProps<V> extends BaseProps<V> {
|
|
|
59
67
|
maxChipsVisible?: number;
|
|
60
68
|
}
|
|
61
69
|
export type PaginatedSelectProps<V = string> = SingleProps<V> | MultiProps<V>;
|
|
62
|
-
/**
|
|
63
|
-
* PaginatedSelect — searchable, async-paginated select with optional multi-mode
|
|
64
|
-
* and optional inline create-new drawer.
|
|
65
|
-
*
|
|
66
|
-
* @example single-select
|
|
67
|
-
* <PaginatedSelect value={storeId} onChange={setStoreId} loadOptions={loadStores} />
|
|
68
|
-
*
|
|
69
|
-
* @example single-select with inline create
|
|
70
|
-
* <PaginatedSelect
|
|
71
|
-
* value={categoryId}
|
|
72
|
-
* onChange={(v) => setCategoryId(v ?? "")}
|
|
73
|
-
* loadOptions={loadCategories}
|
|
74
|
-
* createLabel="Category"
|
|
75
|
-
* renderCreateForm={({ onCreated, onCancel }) => <CategoryForm ... />}
|
|
76
|
-
* />
|
|
77
|
-
*
|
|
78
|
-
* @example multi-select
|
|
79
|
-
* <PaginatedSelect multiple value={tagIds} onChange={setTagIds} loadOptions={loadTags} />
|
|
80
|
-
*/
|
|
81
70
|
export declare function PaginatedSelect<V = string>(props: PaginatedSelectProps<V>): React.JSX.Element;
|
|
82
71
|
export {};
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
+
import React, { useCallback, useEffect, useRef, useState } from "react";
|
|
4
4
|
import { Input } from "./Input.js";
|
|
5
|
+
import { toUserMessage } from "../../errors/error-display-map.js";
|
|
6
|
+
import { Text } from "./Typography.js";
|
|
7
|
+
import { FormShellContext } from "../forms/FormShell.js";
|
|
5
8
|
import { SideDrawer } from "./SideDrawer.js";
|
|
6
9
|
import { Button } from "./Button.js";
|
|
7
10
|
import { QuickFormDrawer } from "../../features/shell/QuickFormDrawer.js";
|
|
@@ -87,9 +90,43 @@ function isValueSelected(value, props, isMulti) {
|
|
|
87
90
|
* @example multi-select
|
|
88
91
|
* <PaginatedSelect multiple value={tagIds} onChange={setTagIds} loadOptions={loadTags} />
|
|
89
92
|
*/
|
|
93
|
+
/**
|
|
94
|
+
* Resolve this control's validation message and the id that labels it.
|
|
95
|
+
*
|
|
96
|
+
* A hook rather than four lines in the component body: `PaginatedSelect` is
|
|
97
|
+
* already at `audit-code-quality`'s size limit, and the error affordance is
|
|
98
|
+
* self-contained — explicit prop wins, else `FormShellContext` by `name`,
|
|
99
|
+
* exactly as the `Field*` primitives resolve theirs.
|
|
100
|
+
*/
|
|
101
|
+
function usePaginatedSelectError(name, errorProp) {
|
|
102
|
+
const ctx = React.useContext(FormShellContext);
|
|
103
|
+
const error = errorProp ?? (name ? ctx?.errors[name] : undefined);
|
|
104
|
+
const errorId = `${React.useId()}-error`;
|
|
105
|
+
return { error, errorId };
|
|
106
|
+
}
|
|
107
|
+
/** The message, under the trigger it describes. */
|
|
108
|
+
function PaginatedSelectError({ id, error }) {
|
|
109
|
+
if (!error)
|
|
110
|
+
return null;
|
|
111
|
+
return (_jsx(Text, { id: id, size: "sm", variant: "error", className: "appkit-form-field__error", role: "alert", children: error }));
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The selected-values row, multi-select only.
|
|
115
|
+
*
|
|
116
|
+
* Its own component because `PaginatedSelect` sits against
|
|
117
|
+
* `audit-code-quality`'s size limit, and this block needs none of the
|
|
118
|
+
* control's twelve state variables — only what it is handed.
|
|
119
|
+
*/
|
|
120
|
+
function PaginatedSelectChips({ chips, labelFor, overflowCount, disabled, onRemove, }) {
|
|
121
|
+
return (_jsxs("div", { className: C.chips, children: [chips.map((v) => (_jsxs("span", { className: C.chip, children: [_jsx("span", { className: C.chipLabel, children: labelFor(v) }), !disabled && (_jsx("button", { type: "button", "aria-label": "Remove", className: C.chipRemove, onClick: (e) => {
|
|
122
|
+
e.stopPropagation();
|
|
123
|
+
onRemove(v);
|
|
124
|
+
}, children: "\u2715" }))] }, String(v)))), overflowCount > 0 && (_jsxs("span", { className: C.overflowChip, children: ["+", overflowCount, " more"] }))] }));
|
|
125
|
+
}
|
|
90
126
|
export function PaginatedSelect(props) {
|
|
91
|
-
const { options, loadOptions, placeholder = "Select...", searchPlaceholder = "Search...", noResultsText = "No results", disabled, className = "", ariaLabel, createLabel, drawerTitle, renderCreateForm, createFields, createSchema, onCreateSubmit, createSubmitLabel, editLabel, editDrawerTitle, renderEditForm, editFields, editSchema, onEditLoad, onEditSubmit, editSubmitLabel, } = props;
|
|
127
|
+
const { options, loadOptions, placeholder = "Select...", searchPlaceholder = "Search...", noResultsText = "No results", disabled, className = "", ariaLabel, name, error: errorProp, createLabel, drawerTitle, renderCreateForm, createFields, createSchema, onCreateSubmit, createSubmitLabel, editLabel, editDrawerTitle, renderEditForm, editFields, editSchema, onEditLoad, onEditSubmit, editSubmitLabel, } = props;
|
|
92
128
|
const isMulti = props.multiple === true;
|
|
129
|
+
const { error, errorId } = usePaginatedSelectError(name, errorProp);
|
|
93
130
|
const maxChipsVisible = isMulti ? props.maxChipsVisible ?? 3 : 0;
|
|
94
131
|
const [query, setQuery] = useState("");
|
|
95
132
|
const [open, setOpen] = useState(false);
|
|
@@ -222,7 +259,7 @@ export function PaginatedSelect(props) {
|
|
|
222
259
|
// Same staleness rule as the success path: a superseded request must
|
|
223
260
|
// not paint its failure over a newer request's results.
|
|
224
261
|
if (requestId === requestIdRef.current) {
|
|
225
|
-
setLoadError(normalized.
|
|
262
|
+
setLoadError(toUserMessage(normalized.code, undefined, { fallback: "Could not load options." }));
|
|
226
263
|
if (reset)
|
|
227
264
|
setAsyncOptions([]);
|
|
228
265
|
}
|
|
@@ -370,12 +407,9 @@ export function PaginatedSelect(props) {
|
|
|
370
407
|
const multiValue = isMulti ? props.value : [];
|
|
371
408
|
const visibleChips = multiValue.slice(0, maxChipsVisible);
|
|
372
409
|
const overflowCount = multiValue.length - maxChipsVisible;
|
|
373
|
-
return (_jsxs(_Fragment, { children: [_jsxs("div", { ref: containerRef, className: [C.root, className].filter(Boolean).join(" "),
|
|
374
|
-
e.stopPropagation();
|
|
375
|
-
removeChip(v);
|
|
376
|
-
}, children: "\u2715" }))] }, String(v)))), overflowCount > 0 && (_jsxs("span", { className: C.overflowChip, children: ["+", overflowCount, " more"] }))] })), _jsxs("button", { type: "button", disabled: disabled, onClick: () => setOpen((prev) => !prev), className: [C.trigger, disabled ? C.triggerDisabled : ""]
|
|
410
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { ref: containerRef, className: [C.root, className].filter(Boolean).join(" "), children: [isMulti && multiValue.length > 0 && (_jsx(PaginatedSelectChips, { chips: visibleChips, labelFor: (v) => labelMap.current.get(v) ?? String(v), overflowCount: overflowCount, disabled: disabled, onRemove: removeChip })), _jsxs("button", { type: "button", disabled: disabled, onClick: () => setOpen((prev) => !prev), className: [C.trigger, disabled ? C.triggerDisabled : ""]
|
|
377
411
|
.filter(Boolean)
|
|
378
|
-
.join(" "), "aria-expanded": open, "aria-haspopup": "listbox", children: [_jsx("span", { className: C.triggerLeft, children: _jsx("span", { className: triggerEmpty ? C.placeholder : "", children: triggerLabel }) }), _jsx("span", { "aria-hidden": "true", children: "\u25BE" })] }), hasEdit && !disabled ? (_jsx("button", { type: "button", className: C.editBtn, "aria-label": `Edit ${editLabel}`, title: `Edit ${editLabel}`, onClick: () => {
|
|
412
|
+
.join(" "), "aria-label": ariaLabel, "aria-expanded": open, "aria-haspopup": "listbox", "aria-invalid": error ? "true" : undefined, "aria-describedby": error ? errorId : undefined, children: [_jsx("span", { className: C.triggerLeft, children: _jsx("span", { className: triggerEmpty ? C.placeholder : "", children: triggerLabel }) }), _jsx("span", { "aria-hidden": "true", children: "\u25BE" })] }), hasEdit && !disabled ? (_jsx("button", { type: "button", className: C.editBtn, "aria-label": `Edit ${editLabel}`, title: `Edit ${editLabel}`, onClick: () => {
|
|
379
413
|
setOpen(false);
|
|
380
414
|
setEditOpen(true);
|
|
381
415
|
}, children: "\u270E" })) : null, open && (_jsxs("div", { className: [C.dropdown, opensUp ? C.dropdownUp : ""].filter(Boolean).join(" "), role: "listbox", "aria-multiselectable": isMulti || undefined, children: [_jsx(Input, { autoFocus: true, value: query, onChange: (e) => setQuery(e.target.value), placeholder: searchPlaceholder, className: C.search }), _jsxs("ul", { className: C.list, children: [filtered.map((opt, idx) => {
|
|
@@ -386,7 +420,7 @@ export function PaginatedSelect(props) {
|
|
|
386
420
|
}), loadError && !loading && (_jsxs("li", { className: C.empty, role: "alert", children: [loadError, " ", _jsx(Button, { variant: "ghost", size: "sm", onClick: () => void load(query, 1, true), children: "Try again" })] })), !loadError && filtered.length === 0 && !loading && (_jsx("li", { className: C.empty, children: noResultsText })), loading && _jsx("li", { className: C.loading, children: "Loading\u2026" }), hasMore && !loading && (_jsx("li", { children: _jsx("button", { type: "button", className: C.loadMore, onClick: () => void load(query, page + 1), children: "Load more" }) })), hasCreate && (_jsx("li", { children: _jsxs("button", { type: "button", className: C.createBtn, onClick: () => {
|
|
387
421
|
setOpen(false);
|
|
388
422
|
setDrawerOpen(true);
|
|
389
|
-
}, children: ["+ Create new ", createLabel] }) }, CREATE_SENTINEL))] })] }))] }), hasCreate && renderCreateForm ? (_jsx(SideDrawer, { isOpen: drawerOpen, onClose: () => setDrawerOpen(false), title: drawerTitle ?? `Create ${createLabel}`, mode: "create", footer: _jsx(Button, { variant: "ghost", onClick: () => setDrawerOpen(false), children: "Cancel" }), children: renderCreateForm({
|
|
423
|
+
}, children: ["+ Create new ", createLabel] }) }, CREATE_SENTINEL))] })] })), _jsx(PaginatedSelectError, { id: errorId, error: error })] }), hasCreate && renderCreateForm ? (_jsx(SideDrawer, { isOpen: drawerOpen, onClose: () => setDrawerOpen(false), title: drawerTitle ?? `Create ${createLabel}`, mode: "create", footer: _jsx(Button, { variant: "ghost", onClick: () => setDrawerOpen(false), children: "Cancel" }), children: renderCreateForm({
|
|
390
424
|
onCreated: handleCreated,
|
|
391
425
|
onCancel: () => setDrawerOpen(false),
|
|
392
426
|
}) })) : hasCreate && createFields && onCreateSubmit ? (_jsx(QuickFormDrawer, { isOpen: drawerOpen, onClose: () => setDrawerOpen(false), title: drawerTitle ?? `Create ${createLabel}`, fields: createFields, schema: createSchema, submitLabel: createSubmitLabel ?? `Create ${createLabel}`, onSubmit: async (values) => {
|
|
@@ -66,5 +66,12 @@ export function RadioGroup({ name, options, value, onChange, label, error, orien
|
|
|
66
66
|
export function RadioItem({ label, suffix, error, className = "", checked, disabled, id, ...props }) {
|
|
67
67
|
const generatedId = React.useId();
|
|
68
68
|
const inputId = id ?? generatedId;
|
|
69
|
-
|
|
69
|
+
/*
|
|
70
|
+
* A caller's own `aria-invalid` wins. `{...props}` is spread first, so
|
|
71
|
+
* re-specifying the attribute afterwards overwrites an incoming "true"
|
|
72
|
+
* with `undefined` — the defect that left every Field*-built form in the
|
|
73
|
+
* app with no aria-invalid at all. See the full writeup in `Input.tsx`.
|
|
74
|
+
*/
|
|
75
|
+
const ariaInvalid = props["aria-invalid"] ?? (error ? "true" : undefined);
|
|
76
|
+
return (_jsxs("div", { className: "appkit-radio-item", "data-section": "radio-item-div", children: [_jsxs(Label, { htmlFor: inputId, className: ["appkit-radio-item__label", disabled ? "appkit-radio-item__label--disabled" : ""].filter(Boolean).join(" "), children: [_jsx("span", { className: "appkit-radio-item__box-wrap", children: _jsx("input", { ...props, id: inputId, type: "radio", checked: checked, disabled: disabled, "aria-invalid": ariaInvalid, className: ["appkit-radio-item__input", error ? "appkit-radio-item__input--error" : "", className].filter(Boolean).join(" ") }) }), (label || suffix) && (_jsxs("span", { className: "appkit-radio-item__content", children: [label ? _jsx(Span, { className: "appkit-radio-item__text", children: label }) : _jsx("span", {}), suffix] }))] }), error && (_jsx(Text, { size: "sm", variant: "error", className: "appkit-radio-item__error", role: "alert", children: error }))] }));
|
|
70
77
|
}
|