@mohasinac/appkit 4.31.1 → 4.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_internal/server/features/products/list-public.d.ts +18 -0
- package/dist/_internal/server/features/products/list-public.js +27 -9
- package/dist/client.d.ts +9 -0
- package/dist/client.js +12 -0
- package/dist/features/admin/components/AdminAdsView.js +36 -34
- package/dist/features/admin/components/AdminCarouselEditorView.js +78 -10
- package/dist/features/admin/components/AdminOrderEditorView.js +55 -24
- package/dist/features/admin/components/AdminProductEditorView.js +78 -18
- package/dist/features/admin/components/AdminSectionsView.js +89 -32
- package/dist/features/admin/components/AdminSiteSettingsView.js +96 -44
- package/dist/features/admin/components/AdminSupportTicketDetailView.js +58 -43
- package/dist/features/admin/components/AdminUserEditorView.js +65 -82
- package/dist/features/admin/constants/admin-page-tabs.d.ts +36 -0
- package/dist/features/admin/constants/admin-page-tabs.js +45 -0
- package/dist/features/admin/schemas/admin-ops-forms.d.ts +81 -0
- package/dist/features/admin/schemas/admin-ops-forms.js +123 -1
- package/dist/features/admin/schemas/admin-user-form.d.ts +78 -3
- package/dist/features/admin/schemas/admin-user-form.js +96 -7
- package/dist/features/admin/schemas/homepage-section-form.d.ts +9 -0
- package/dist/features/layout/PageTabs.d.ts +17 -0
- package/dist/features/layout/PageTabs.js +45 -0
- package/dist/features/products/components/GroupSettingsPanel.js +62 -16
- package/dist/features/search/action-index/tab-entries.d.ts +18 -0
- package/dist/features/search/action-index/tab-entries.js +128 -0
- package/dist/features/seller/components/SellerGoogleReviewsView.js +31 -19
- package/dist/features/seller/components/SellerOrdersView.js +57 -19
- package/dist/features/seller/components/SellerReviewsView.d.ts +2 -1
- package/dist/features/seller/components/SellerReviewsView.js +54 -19
- package/dist/features/seller/constants/store-tabs.d.ts +64 -0
- package/dist/features/seller/constants/store-tabs.js +60 -0
- package/dist/features/seller/schemas/order-forms.d.ts +33 -0
- package/dist/features/seller/schemas/order-forms.js +75 -0
- package/dist/features/seller/schemas/review-forms.d.ts +37 -0
- package/dist/features/seller/schemas/review-forms.js +82 -0
- package/dist/features/shell/index.d.ts +1 -1
- package/dist/features/shell/index.js +5 -1
- package/dist/features/shipments/components/AdminShipmentLotItemsView.js +44 -15
- package/dist/features/shipments/schemas/validation.d.ts +41 -0
- package/dist/features/shipments/schemas/validation.js +49 -0
- package/dist/features/store-extensions/schemas/google-config-form.d.ts +36 -0
- package/dist/features/store-extensions/schemas/google-config-form.js +24 -0
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +67 -0
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +53 -22
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2 -0
- package/dist/next/index.d.ts +2 -0
- package/dist/next/index.js +1 -0
- package/dist/react/hooks/useTabParam.d.ts +19 -0
- package/dist/react/hooks/useTabParam.js +65 -0
- package/dist/schemas/registry.d.ts +27 -3
- package/dist/styles.css +1 -1
- package/dist/tailwind-utilities.css +1 -1
- package/package.json +1 -1
|
@@ -396,6 +396,12 @@ const rawTesterChecklistItems = [
|
|
|
396
396
|
href: "/products",
|
|
397
397
|
cases: [
|
|
398
398
|
{ key: "standard-detail", label: "Standard product detail page loads correctly", href: "/products/product-tester-standard-1" },
|
|
399
|
+
{
|
|
400
|
+
key: "tester-fixtures-hidden-from-the-public",
|
|
401
|
+
label: "SIGNED OUT, no product on /products has an id containing \"tester\" — the sandbox is invisible to the public",
|
|
402
|
+
description: "Fixed 2026-09-01. BEFORE: the public products endpoint had no viewer concept, so an ANONYMOUS caller got 25 tester fixtures in its first 40 rows — stickers-tester-sandbox-1, prizedraw-tester-sandbox-closed, live-tester-sandbox-1 — and they filled the public marketplace. AFTER: open /products in a private window and scan the grid; nothing should be named \"Tester …\". Then sign in AS A TESTER and reload — the sandbox items must come back. Both halves matter: hiding them from everyone would break the tester programme, and a fixture that never reappears is the opposite bug.",
|
|
403
|
+
href: "/products",
|
|
404
|
+
},
|
|
399
405
|
{
|
|
400
406
|
key: "seed-images-are-labelled-tiles",
|
|
401
407
|
label: "Every seeded product image is a readable tile showing the item's own name — NOT a broken-image icon and NOT an empty grey box",
|
|
@@ -2053,6 +2059,67 @@ const rawTesterChecklistItems = [
|
|
|
2053
2059
|
{ key: "shipping-configs-crud", label: "Creating, editing, and listing shipping configs works", href: "/store/shipping-configs" },
|
|
2054
2060
|
{ key: "payout-methods-crud", label: "Adding, editing, and listing payout methods works", href: "/store/payout-methods" },
|
|
2055
2061
|
{ key: "payout-settings-page", label: "Payout settings page saves correctly", href: "/store/payout-settings" },
|
|
2062
|
+
{
|
|
2063
|
+
key: "consolidated-tabs-old-urls",
|
|
2064
|
+
label: "Each absorbed URL still lands on the right TAB, not just the right page",
|
|
2065
|
+
description: "W8 C2 folded six sibling pages into tabs. BEFORE: /store/payout-methods, /store/payout-settings, /store/shipping-configs, /store/print-center, /store/slug, /admin/permissions and /admin/settings/navigation were each their own page with their own sidebar entry. AFTER: each redirects to its host with ?tab= set, and the sidebar entry is gone. Visit all seven old URLs. Each must land on the host page with the CORRECT tab already selected — a landing on the host's FIRST tab is the failure, and it looks like success unless you check which tab is active.",
|
|
2066
|
+
href: "/store/payout-methods",
|
|
2067
|
+
},
|
|
2068
|
+
{
|
|
2069
|
+
key: "consolidated-tabs-url-writeback",
|
|
2070
|
+
label: "Clicking a tab updates ?tab= in the address bar",
|
|
2071
|
+
description: "BEFORE: the settings page read ?tab= once on load and never wrote it back, so copying the URL after clicking a tab shared the DEFAULT tab. AFTER: every tabbed page writes the tab into the URL on click. On /store/payouts click through all three tabs, watch ?tab= change, then copy the address and open it in a new tab — it must reopen on the same panel. Back must leave the page rather than walking back through the tabs.",
|
|
2072
|
+
href: "/store/payouts",
|
|
2073
|
+
},
|
|
2074
|
+
{
|
|
2075
|
+
key: "print-center-selection-deeplink",
|
|
2076
|
+
label: "Print labels for a selection still carries the selection through the redirect",
|
|
2077
|
+
description: "/store/print-center is now a redirect, and it is deep-linked from the orders and products lists as ?type=order&ids=…&autoprint=1. Select two orders on /store/orders, use the print action, and confirm the Print Centre tab opens with those two orders loaded. Arriving at an EMPTY Print Centre is the failure — it looks like a normal empty state, which is why this case names the before.",
|
|
2078
|
+
href: "/store/orders",
|
|
2079
|
+
},
|
|
2080
|
+
{
|
|
2081
|
+
key: "consolidated-tabs-command-palette",
|
|
2082
|
+
label: "The absorbed surfaces are still findable by name in search",
|
|
2083
|
+
description: "Their sidebar entries were deleted, and the action index is derived from the sidebar — so without a deliberate entry they would vanish from search on the day they became easier to reach. Search for \"upi\", \"threshold\", \"barcode\", \"slug\" and \"permissions catalogue\". Each must return a result that opens the right TAB.",
|
|
2084
|
+
href: "/store/payouts",
|
|
2085
|
+
},
|
|
2086
|
+
],
|
|
2087
|
+
},
|
|
2088
|
+
{
|
|
2089
|
+
pageKey: "sectionised-forms",
|
|
2090
|
+
pageLabel: "Forms — sections, error summary and the mobile bar",
|
|
2091
|
+
href: "/admin/orders",
|
|
2092
|
+
cases: [
|
|
2093
|
+
{
|
|
2094
|
+
key: "form-sections-save-unchanged",
|
|
2095
|
+
label: "Every migrated form still saves exactly the fields it saved before",
|
|
2096
|
+
description: "W8 C3 moved 16 hand-rolled forms onto <SectionForm>. The risk is a field that renders but no longer reaches the payload. On each of these, change EVERY field, save, reload, and confirm each value came back: the admin order editor (status, tracking number, carrier, notes), the admin user editor (role, email verified, display name, phone, tester flags, admin notes, bio, location, website, four social links), WhatsApp settings, the seller order panel, and the ad settings panel. A field that silently reverts is the failure, and nothing errors when it happens.",
|
|
2097
|
+
href: "/admin/orders",
|
|
2098
|
+
},
|
|
2099
|
+
{
|
|
2100
|
+
key: "form-error-summary-jumps",
|
|
2101
|
+
label: "A validation error names its section and jumps to it",
|
|
2102
|
+
description: "BEFORE: an invalid field produced a banner listing the problem with no way to reach it, and on the site-settings page the summary was mounted outside every form context and so displayed NOTHING at all. AFTER: submit a form with a bad value (a negative refund on an order, a non-https third-party ad URL, an AdSense id that is not ca-pub-…) and the summary must list it, tag it with its section, collapse open that section when clicked, and show a count badge on the section header.",
|
|
2103
|
+
href: "/admin/ads",
|
|
2104
|
+
},
|
|
2105
|
+
{
|
|
2106
|
+
key: "form-mobile-action-bar",
|
|
2107
|
+
label: "Save and Cancel appear in the pinned mobile bar, once",
|
|
2108
|
+
description: "On a phone-width viewport, a page-level form publishes its actions into the bottom chrome tier. Open /admin/products/new and confirm exactly ONE Save row is visible — not one in the page and one pinned. Inside a drawer or modal (the admin order editor, the review reply modal) the pinned bar must NOT appear: an overlay owns its own footer, and a viewport-fixed bar renders behind the backdrop.",
|
|
2109
|
+
href: "/admin/products/new",
|
|
2110
|
+
},
|
|
2111
|
+
{
|
|
2112
|
+
key: "form-conditional-fields-drop-values",
|
|
2113
|
+
label: "A field hidden by a condition does not submit a stale value",
|
|
2114
|
+
description: "BEFORE: hiding a control left its last value in the payload. AFTER: on the admin user editor, switch \"Is Tester\" ON, tick \"Can test admin areas\", switch \"Is Tester\" back OFF, save, reload — the admin-test flag must be false. Same shape on a shipment item: enter a price, tick \"for self use\", save; the price must not be stored.",
|
|
2115
|
+
href: "/admin/users",
|
|
2116
|
+
},
|
|
2117
|
+
{
|
|
2118
|
+
key: "site-settings-save-still-writes-everything",
|
|
2119
|
+
label: "Site settings still saves every tab's fields in one Save",
|
|
2120
|
+
description: "Its 20 tabs write ONE document through one payload, and its schema is deliberately passthrough because that payload carries keys the TypeScript interface does not declare. A closed schema here would silently strip them. Change one field on each of three different tabs, press \"Save all changes\" once, reload, and confirm all three persisted — and that fields on tabs you did NOT touch are unchanged.",
|
|
2121
|
+
href: "/admin/site",
|
|
2122
|
+
},
|
|
2056
2123
|
],
|
|
2057
2124
|
},
|
|
2058
2125
|
{
|
|
@@ -12,12 +12,15 @@ const __O = {
|
|
|
12
12
|
};
|
|
13
13
|
const CLS_SECTION_CARD = "border border-[var(--appkit-color-border)] rounded-xl p-[var(--appkit-space-5)]";
|
|
14
14
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
15
|
-
import { Alert, Badge, Button, Div,
|
|
15
|
+
import { Alert, Badge, Button, Div, Heading, Input, Row, Section, Span, Stack, Text, Toggle, useToast } from "../../../ui";
|
|
16
16
|
import { apiClient } from "../../../http";
|
|
17
17
|
import { WHATSAPP_SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
18
18
|
import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
|
|
19
19
|
import { buildPurchaseAnnouncementMessage } from "../helpers/whatsapp";
|
|
20
|
-
import {
|
|
20
|
+
import { whatsappConnectionSchema, } from "../../admin/schemas/admin-user-form";
|
|
21
|
+
import { SectionForm, useSectionFormNav, buildSectionsFromSchema, visibleValues, } from "../../shell";
|
|
22
|
+
import { useFormShellState, FormShellContext } from "../../../ui/forms";
|
|
23
|
+
import { applyZodIssues } from "../../../ui/forms/apply-zod-issues";
|
|
21
24
|
import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary";
|
|
22
25
|
// ---------------------------------------------------------------------------
|
|
23
26
|
// Sub-components
|
|
@@ -101,20 +104,40 @@ export function SellerWhatsAppSettingsView({ hasCapability }) {
|
|
|
101
104
|
});
|
|
102
105
|
const cfg = data ?? null;
|
|
103
106
|
// Connection form state
|
|
104
|
-
const [
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
107
|
+
const [draft, setDraft] = React.useState({
|
|
108
|
+
phoneNumber: "",
|
|
109
|
+
wabaId: "",
|
|
110
|
+
catalogId: "",
|
|
111
|
+
accessToken: "",
|
|
112
|
+
});
|
|
108
113
|
const [syncEnabled, setSyncEnabled] = React.useState(false);
|
|
109
114
|
React.useEffect(() => {
|
|
110
115
|
if (!cfg)
|
|
111
116
|
return;
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
117
|
+
setDraft({
|
|
118
|
+
phoneNumber: cfg.phoneNumber ?? "",
|
|
119
|
+
wabaId: cfg.wabaId ?? "",
|
|
120
|
+
catalogId: cfg.catalogId ?? "",
|
|
121
|
+
accessToken: cfg.accessToken ?? "",
|
|
122
|
+
});
|
|
116
123
|
setSyncEnabled(cfg.catalogSyncEnabled ?? false);
|
|
117
124
|
}, [cfg]);
|
|
125
|
+
/*
|
|
126
|
+
* The access token needs a renderer: it is a `text` field to the schema, and
|
|
127
|
+
* the control is a masked input with a reveal that must stay suppressed while
|
|
128
|
+
* the field still holds the server's sentinel.
|
|
129
|
+
*/
|
|
130
|
+
const sections = React.useMemo(() => buildSectionsFromSchema(whatsappConnectionSchema, {
|
|
131
|
+
renderers: {
|
|
132
|
+
accessToken: ({ values, onChange }) => (_jsx(MaskedTokenInput, { label: "System User Access Token", value: values.accessToken ?? "", onChange: (v) => onChange({ accessToken: v }), placeholder: "EAAxxxxxxxx\u2026", helperText: "Long-lived token with WhatsApp Business permissions" })),
|
|
133
|
+
},
|
|
134
|
+
}), []);
|
|
135
|
+
const nav = useSectionFormNav(sections, draft, { scope: "store:whatsapp" });
|
|
136
|
+
const form = useFormShellState(whatsappConnectionSchema, {
|
|
137
|
+
sections: nav.sectionMeta,
|
|
138
|
+
onGoToSection: nav.goToSection,
|
|
139
|
+
fieldToSectionIndex: nav.fieldToSectionIndex,
|
|
140
|
+
});
|
|
118
141
|
/*
|
|
119
142
|
* The CONNECTION form. Credentials only.
|
|
120
143
|
*
|
|
@@ -131,16 +154,17 @@ export function SellerWhatsAppSettingsView({ hasCapability }) {
|
|
|
131
154
|
const saveMutation = useApiMutation({
|
|
132
155
|
errorMessage: "Failed to save settings.",
|
|
133
156
|
mutationFn: async () => {
|
|
157
|
+
const v = visibleValues(whatsappConnectionSchema, draft);
|
|
134
158
|
const payload = {};
|
|
135
|
-
if (phoneNumber)
|
|
136
|
-
payload.phoneNumber = phoneNumber;
|
|
137
|
-
if (wabaId)
|
|
138
|
-
payload.wabaId = wabaId;
|
|
139
|
-
if (catalogId)
|
|
140
|
-
payload.catalogId = catalogId;
|
|
159
|
+
if (v.phoneNumber)
|
|
160
|
+
payload.phoneNumber = v.phoneNumber;
|
|
161
|
+
if (v.wabaId)
|
|
162
|
+
payload.wabaId = v.wabaId;
|
|
163
|
+
if (v.catalogId)
|
|
164
|
+
payload.catalogId = v.catalogId;
|
|
141
165
|
// Only send accessToken if user has typed a new value (not the masked placeholder)
|
|
142
|
-
if (accessToken && accessToken !== MASKED_TOKEN)
|
|
143
|
-
payload.accessToken = accessToken;
|
|
166
|
+
if (v.accessToken && v.accessToken !== MASKED_TOKEN)
|
|
167
|
+
payload.accessToken = v.accessToken;
|
|
144
168
|
return apiClient.put(WHATSAPP_SELLER_ENDPOINTS.SETTINGS, payload);
|
|
145
169
|
},
|
|
146
170
|
onSuccess: () => {
|
|
@@ -224,10 +248,17 @@ export function SellerWhatsAppSettingsView({ hasCapability }) {
|
|
|
224
248
|
? cfg?.connected === true
|
|
225
249
|
: Boolean(cfg?.[step.checkKey]);
|
|
226
250
|
return (_jsxs(Row, { gap: "sm", align: "start", children: [_jsx(Row, { textWeight: "bold", textSize: "xs", className: `flex-shrink-0 w-6 h-6 mt-0.5 ${done ? "bg-[var(--appkit-color-success)] text-white" : "bg-[var(--appkit-color-surface-elevated)] text-[var(--appkit-color-text-muted)] dark:text-[var(--appkit-color-text-faint)]"}`, align: "center", justify: "center", rounded: "full", children: done ? "✓" : step.n }), _jsxs(Div, { children: [_jsx(Text, { size: "sm", weight: "medium", color: "primary", children: step.title }), _jsx(Text, { className: "mt-0.5", color: "muted", size: "xs", children: step.body })] })] }, step.n));
|
|
227
|
-
}) })] }), _jsxs(Section, { className: CLS_SECTION_CARD, children: [_jsxs(Row, { justify: "between", align: "center", className: "mb-4", children: [_jsx(Heading, { level: 2, size: "sm", weight: "semibold", color: "primary", children: "Connection" }), isLoading ? null : cfg?.connected ? (_jsx(Badge, { variant: "success", children: "Connected" })) : (_jsx(Badge, { variant: "default", children: "Not configured" }))] }), _jsxs(
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
251
|
+
}) })] }), _jsxs(Section, { className: CLS_SECTION_CARD, children: [_jsxs(Row, { justify: "between", align: "center", className: "mb-4", children: [_jsx(Heading, { level: 2, size: "sm", weight: "semibold", color: "primary", children: "Connection" }), isLoading ? null : cfg?.connected ? (_jsx(Badge, { variant: "success", children: "Connected" })) : (_jsx(Badge, { variant: "default", children: "Not configured" }))] }), _jsxs(FormShellContext.Provider, { value: form.shellCtx, children: [_jsx(FormErrorSummary, {}), _jsx(SectionForm, { sections: sections, values: draft, onChange: (partial) => setDraft((d) => ({ ...d, ...partial })), onSubmit: () => {
|
|
252
|
+
// SectionForm scrolls to the first bad section and then submits
|
|
253
|
+
// regardless, so the guard lives here.
|
|
254
|
+
form.clearErrors();
|
|
255
|
+
const parsed = whatsappConnectionSchema.safeParse(visibleValues(whatsappConnectionSchema, draft));
|
|
256
|
+
if (!parsed.success) {
|
|
257
|
+
applyZodIssues(parsed.error.issues, form.setFieldError);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
saveMutation.mutate();
|
|
261
|
+
}, onValidationChange: () => form.validate(draft), schema: whatsappConnectionSchema, openIds: nav.openIds, onOpenChange: nav.setOpenIds, submitLabel: ACTIONS.STORE["whatsapp-connect"].label, isLoading: saveMutation.isPending })] })] }), _jsxs(Section, { className: CLS_SECTION_CARD, children: [_jsx(Heading, { level: 2, className: "mb-4", color: "primary", size: "sm", weight: "semibold", children: "Catalog Sync" }), _jsxs(Row, { justify: "between", align: "center", className: "mb-4", children: [_jsxs(Div, { children: [_jsx(Text, { size: "sm", color: "muted", children: "Enable catalog sync" }), _jsx(Text, { size: "xs", color: "muted", children: "When enabled, your published standard products can be synced to WhatsApp." })] }), _jsx(Toggle, { checked: syncEnabled, onChange: handleSyncToggle, disabled: !cfg?.connected || syncToggleMutation.isPending })] }), cfg?.lastCatalogSyncAt && (_jsxs(Row, { color: "muted", textSize: "xs", gap: "sm", align: "center", className: "mb-3", children: [_jsxs(Text, { children: ["Last sync: ", new Date(cfg.lastCatalogSyncAt).toLocaleString("en-IN")] }), cfg.lastSyncCount !== undefined && _jsxs(Text, { children: ["\u00B7 ", cfg.lastSyncCount, " products"] }), cfg.lastSyncStatus && (_jsx(Badge, { variant: STATUS_VARIANT[cfg.lastSyncStatus] ?? "default", children: cfg.lastSyncStatus }))] })), _jsx(Text, { className: "mb-3", color: "faint", size: "xs", children: "Only published standard products are synced. Auctions and pre-orders are excluded. Batches of up to 50 products per call." }), _jsxs(Row, { gap: "3", children: [_jsx(Button, { onClick: () => syncMutation.mutate(), isLoading: syncMutation.isPending, disabled: !cfg?.connected || !syncEnabled || syncMutation.isPending, variant: "secondary", children: syncMutation.isPending ? "Syncing…" : ACTIONS.STORE["whatsapp-catalog-sync"].label }), _jsx(Button, { onClick: () => void startImport(), isLoading: importing, disabled: !cfg?.connected || !syncEnabled || importing, variant: "secondary", children: importing ? "Importing…" : ACTIONS.STORE["whatsapp-catalog-import"].label })] }), _jsxs(Text, { className: "mt-3", color: "faint", size: "xs", children: [_jsx(Span, { weight: "bold", children: "Push" }), " sends your published standard products to WhatsApp.", " ", _jsx(Span, { weight: "bold", children: "Import" }), " creates draft products from your WhatsApp catalog. Products are matched by slug in the description field."] })] }), _jsxs(Section, { className: CLS_SECTION_CARD, children: [_jsx(Heading, { level: 2, className: "mb-2", color: "primary", size: "sm", weight: "semibold", children: "Purchase Announcement Preview" }), _jsx(Text, { className: "mb-3", color: "muted", size: "xs", children: "This message is sent automatically to your phone and the platform admin when a new order is placed." }), _jsx(Div, { textSize: "sm", className: "font-mono", color: "primary", surface: "muted", padding: "inline", rounded: "lg", children: buildPurchaseAnnouncementMessage({
|
|
231
262
|
buyerName: "Ravi K.",
|
|
232
263
|
firstItemName: "Charizard PSA 9",
|
|
233
264
|
additionalItemCount: 2,
|
package/dist/index.d.ts
CHANGED
|
@@ -3220,5 +3220,9 @@ export { SITE_SETTINGS_ENTRIES, deriveSettingsEntries, } from "./features/search
|
|
|
3220
3220
|
export type { SettingsIndexSource } from "./features/search/action-index/settings-entries";
|
|
3221
3221
|
export { SITE_SETTINGS_TABS, SITE_SETTINGS_TAB_IDS, isSiteSettingsTabId, } from "./features/admin/constants/site-settings-tabs";
|
|
3222
3222
|
export type { SiteSettingsTab, SiteSettingsTabId } from "./features/admin/constants/site-settings-tabs";
|
|
3223
|
+
export { PAYOUTS_TABS, isPayoutsTabId, SHIPPING_TABS, isShippingTabId, FULFILLMENT_TABS, isFulfillmentTabId, STOREFRONT_TABS, isStorefrontTabId, } from "./features/seller/constants/store-tabs";
|
|
3224
|
+
export type { StoreDashboardTab, PayoutsTabId, ShippingTabId, FulfillmentTabId, StorefrontTabId, } from "./features/seller/constants/store-tabs";
|
|
3225
|
+
export { ROLES_TABS, isRolesTabId, ADMIN_SETTINGS_TABS, isAdminSettingsTabId, } from "./features/admin/constants/admin-page-tabs";
|
|
3226
|
+
export type { AdminPageTab, RolesTabId, AdminSettingsTabId, } from "./features/admin/constants/admin-page-tabs";
|
|
3223
3227
|
export { validateActionIndexEntry, normaliseRoute, } from "./features/search/action-index/validate-entry";
|
|
3224
3228
|
export type { ActionIndexValidationContext, ActionIndexValidationIssue, } from "./features/search/action-index/validate-entry";
|
package/dist/index.js
CHANGED
|
@@ -5626,4 +5626,6 @@ export { deriveNavEntries, deriveQuickActionEntries, buildActionIndexBase, } fro
|
|
|
5626
5626
|
export { matchesNavQuery } from "./_internal/shared/features/layout/matchesNavQuery";
|
|
5627
5627
|
export { SITE_SETTINGS_ENTRIES, deriveSettingsEntries, } from "./features/search/action-index/settings-entries";
|
|
5628
5628
|
export { SITE_SETTINGS_TABS, SITE_SETTINGS_TAB_IDS, isSiteSettingsTabId, } from "./features/admin/constants/site-settings-tabs";
|
|
5629
|
+
export { PAYOUTS_TABS, isPayoutsTabId, SHIPPING_TABS, isShippingTabId, FULFILLMENT_TABS, isFulfillmentTabId, STOREFRONT_TABS, isStorefrontTabId, } from "./features/seller/constants/store-tabs";
|
|
5630
|
+
export { ROLES_TABS, isRolesTabId, ADMIN_SETTINGS_TABS, isAdminSettingsTabId, } from "./features/admin/constants/admin-page-tabs";
|
|
5629
5631
|
export { validateActionIndexEntry, normaliseRoute, } from "./features/search/action-index/validate-entry";
|
package/dist/next/index.d.ts
CHANGED
|
@@ -36,5 +36,7 @@ export { navItemId } from "../_internal/shared/features/layout/types";
|
|
|
36
36
|
export { matchesNavQuery } from "../_internal/shared/features/layout/matchesNavQuery";
|
|
37
37
|
export { buildActionIndexBase, deriveNavEntries, deriveQuickActionEntries, } from "../features/search/action-index/derive";
|
|
38
38
|
export { deriveSettingsEntries } from "../features/search/action-index/settings-entries";
|
|
39
|
+
export { deriveConsolidatedTabEntries, CONSOLIDATED_TAB_SURFACES, } from "../features/search/action-index/tab-entries";
|
|
40
|
+
export type { ConsolidatedTabSurface, ConsolidatedTabHrefs, } from "../features/search/action-index/tab-entries";
|
|
39
41
|
export { mergeActionIndex, projectActionIndexForViewer, } from "../features/search/action-index/types";
|
|
40
42
|
export type { ActionIndexEntry, ActionIndexControl, ActionIndexOverride, } from "../features/search/action-index/types";
|
package/dist/next/index.js
CHANGED
|
@@ -62,4 +62,5 @@ export { matchesNavQuery } from "../_internal/shared/features/layout/matchesNavQ
|
|
|
62
62
|
*/
|
|
63
63
|
export { buildActionIndexBase, deriveNavEntries, deriveQuickActionEntries, } from "../features/search/action-index/derive";
|
|
64
64
|
export { deriveSettingsEntries } from "../features/search/action-index/settings-entries";
|
|
65
|
+
export { deriveConsolidatedTabEntries, CONSOLIDATED_TAB_SURFACES, } from "../features/search/action-index/tab-entries";
|
|
65
66
|
export { mergeActionIndex, projectActionIndexForViewer, } from "../features/search/action-index/types";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface UseTabParamOptions {
|
|
2
|
+
/**
|
|
3
|
+
* A locale-aware router, when the caller has one. Without it the plain
|
|
4
|
+
* `next/navigation` router drops the locale prefix on replace.
|
|
5
|
+
*/
|
|
6
|
+
router?: {
|
|
7
|
+
replace: (url: string) => void;
|
|
8
|
+
};
|
|
9
|
+
pathname?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The active tab, kept in `?tab=`.
|
|
13
|
+
*
|
|
14
|
+
* `isValid` is the page's own type guard — `isSiteSettingsTabId`,
|
|
15
|
+
* `isPayoutsTabId`, and so on — so an unknown value in the URL is IGNORED
|
|
16
|
+
* rather than written into state. That is what makes a stale bookmark open the
|
|
17
|
+
* default tab instead of rendering an empty panel.
|
|
18
|
+
*/
|
|
19
|
+
export declare function useTabParam<T extends string>(isValid: (value: string | null | undefined) => value is T, fallback: T, options?: UseTabParamOptions): [T, (next: string) => void];
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
/*
|
|
3
|
+
* WHY: `Tabs` has no URL sync of its own, so every tabbed page has had to
|
|
4
|
+
* hand-roll one. `AdminSiteSettingsView` read `?tab=` once, in a
|
|
5
|
+
* `useState` initialiser guarded on `typeof window` — and never wrote it
|
|
6
|
+
* back, so the URL went stale the moment anyone clicked a tab. A deep
|
|
7
|
+
* link into a tab worked; sharing the page you were looking at did not.
|
|
8
|
+
*
|
|
9
|
+
* W8's tab consolidations add five more tabbed pages, each of which needs
|
|
10
|
+
* the absorbed page's `?tab=` deep link to keep working, which is well
|
|
11
|
+
* past the Rule of Three.
|
|
12
|
+
*
|
|
13
|
+
* WHAT: Read `?tab=` (validated against the page's own union), and write it
|
|
14
|
+
* back on every change.
|
|
15
|
+
*
|
|
16
|
+
* ## Why `router.replace` and not `push`
|
|
17
|
+
*
|
|
18
|
+
* A tab is a view of one page, not a new destination. Pushing would make Back
|
|
19
|
+
* walk the user through every tab they glanced at before leaving the page,
|
|
20
|
+
* which is the behaviour `useUrlTable` already rejected for filters.
|
|
21
|
+
*
|
|
22
|
+
* ## Why not `useUrlTable().set(TABLE_KEYS.TAB, …)`
|
|
23
|
+
*
|
|
24
|
+
* That would work, and it also resets `page` to 1 — correct for a filter
|
|
25
|
+
* change, meaningless here, and it puts a `page=1` in the URL of a settings
|
|
26
|
+
* page that has no pagination.
|
|
27
|
+
*
|
|
28
|
+
* EXPORTS: useTabParam
|
|
29
|
+
*
|
|
30
|
+
* @tag domain:shared
|
|
31
|
+
* @tag layer:hook
|
|
32
|
+
* @tag pattern:none
|
|
33
|
+
* @tag access:client
|
|
34
|
+
* @tag consumers:AdminSiteSettingsView,store payouts/shipping/fulfillment/storefront,admin roles/settings
|
|
35
|
+
* @tag sideEffects:router
|
|
36
|
+
*/
|
|
37
|
+
import { useRouter, usePathname, useSearchParams } from "next/navigation";
|
|
38
|
+
import { useCallback } from "react";
|
|
39
|
+
import { TABLE_KEYS } from "../../constants/table-keys";
|
|
40
|
+
/**
|
|
41
|
+
* The active tab, kept in `?tab=`.
|
|
42
|
+
*
|
|
43
|
+
* `isValid` is the page's own type guard — `isSiteSettingsTabId`,
|
|
44
|
+
* `isPayoutsTabId`, and so on — so an unknown value in the URL is IGNORED
|
|
45
|
+
* rather than written into state. That is what makes a stale bookmark open the
|
|
46
|
+
* default tab instead of rendering an empty panel.
|
|
47
|
+
*/
|
|
48
|
+
export function useTabParam(isValid, fallback, options) {
|
|
49
|
+
const nativeRouter = useRouter();
|
|
50
|
+
const nativePathname = usePathname();
|
|
51
|
+
const searchParams = useSearchParams();
|
|
52
|
+
const router = options?.router ?? nativeRouter;
|
|
53
|
+
const pathname = options?.pathname ?? nativePathname;
|
|
54
|
+
const requested = searchParams.get(TABLE_KEYS.TAB);
|
|
55
|
+
const active = isValid(requested) ? requested : fallback;
|
|
56
|
+
const setTab = useCallback((next) => {
|
|
57
|
+
if (!isValid(next))
|
|
58
|
+
return;
|
|
59
|
+
const params = new URLSearchParams(searchParams.toString());
|
|
60
|
+
params.set(TABLE_KEYS.TAB, next);
|
|
61
|
+
const query = params.toString();
|
|
62
|
+
router.replace(query ? `${pathname}?${query}` : pathname);
|
|
63
|
+
}, [isValid, pathname, router, searchParams]);
|
|
64
|
+
return [active, setTab];
|
|
65
|
+
}
|
|
@@ -4874,26 +4874,50 @@ export declare const SCHEMAS: {
|
|
|
4874
4874
|
carrier?: string | undefined;
|
|
4875
4875
|
}>;
|
|
4876
4876
|
readonly adminUser: import("zod").ZodObject<{
|
|
4877
|
+
role: import("zod").ZodOptional<import("zod").ZodEnum<["user", "seller", "admin", "moderator"]>>;
|
|
4878
|
+
emailVerified: import("zod").ZodOptional<import("zod").ZodBoolean>;
|
|
4877
4879
|
displayName: import("zod").ZodOptional<import("zod").ZodString>;
|
|
4878
4880
|
phoneNumber: import("zod").ZodUnion<[import("zod").ZodOptional<import("zod").ZodString>, import("zod").ZodLiteral<"">]>;
|
|
4879
4881
|
isTester: import("zod").ZodOptional<import("zod").ZodBoolean>;
|
|
4880
4882
|
canTestAdmin: import("zod").ZodOptional<import("zod").ZodBoolean>;
|
|
4881
4883
|
adminNotes: import("zod").ZodOptional<import("zod").ZodString>;
|
|
4882
|
-
|
|
4884
|
+
bio: import("zod").ZodUnion<[import("zod").ZodOptional<import("zod").ZodString>, import("zod").ZodLiteral<"">]>;
|
|
4885
|
+
location: import("zod").ZodUnion<[import("zod").ZodOptional<import("zod").ZodString>, import("zod").ZodLiteral<"">]>;
|
|
4886
|
+
website: import("zod").ZodUnion<[import("zod").ZodOptional<import("zod").ZodString>, import("zod").ZodLiteral<"">]>;
|
|
4887
|
+
twitter: import("zod").ZodUnion<[import("zod").ZodOptional<import("zod").ZodString>, import("zod").ZodLiteral<"">]>;
|
|
4888
|
+
instagram: import("zod").ZodUnion<[import("zod").ZodOptional<import("zod").ZodString>, import("zod").ZodLiteral<"">]>;
|
|
4889
|
+
facebook: import("zod").ZodUnion<[import("zod").ZodOptional<import("zod").ZodString>, import("zod").ZodLiteral<"">]>;
|
|
4890
|
+
linkedin: import("zod").ZodUnion<[import("zod").ZodOptional<import("zod").ZodString>, import("zod").ZodLiteral<"">]>;
|
|
4883
4891
|
}, "strip", import("zod").ZodTypeAny, {
|
|
4892
|
+
role?: "user" | "seller" | "admin" | "moderator" | undefined;
|
|
4884
4893
|
displayName?: string | undefined;
|
|
4894
|
+
location?: string | undefined;
|
|
4885
4895
|
adminNotes?: string | undefined;
|
|
4896
|
+
bio?: string | undefined;
|
|
4886
4897
|
phoneNumber?: string | undefined;
|
|
4898
|
+
emailVerified?: boolean | undefined;
|
|
4899
|
+
website?: string | undefined;
|
|
4887
4900
|
isTester?: boolean | undefined;
|
|
4888
4901
|
canTestAdmin?: boolean | undefined;
|
|
4889
|
-
|
|
4902
|
+
instagram?: string | undefined;
|
|
4903
|
+
facebook?: string | undefined;
|
|
4904
|
+
twitter?: string | undefined;
|
|
4905
|
+
linkedin?: string | undefined;
|
|
4890
4906
|
}, {
|
|
4907
|
+
role?: "user" | "seller" | "admin" | "moderator" | undefined;
|
|
4891
4908
|
displayName?: string | undefined;
|
|
4909
|
+
location?: string | undefined;
|
|
4892
4910
|
adminNotes?: string | undefined;
|
|
4911
|
+
bio?: string | undefined;
|
|
4893
4912
|
phoneNumber?: string | undefined;
|
|
4913
|
+
emailVerified?: boolean | undefined;
|
|
4914
|
+
website?: string | undefined;
|
|
4894
4915
|
isTester?: boolean | undefined;
|
|
4895
4916
|
canTestAdmin?: boolean | undefined;
|
|
4896
|
-
|
|
4917
|
+
instagram?: string | undefined;
|
|
4918
|
+
facebook?: string | undefined;
|
|
4919
|
+
twitter?: string | undefined;
|
|
4920
|
+
linkedin?: string | undefined;
|
|
4897
4921
|
}>;
|
|
4898
4922
|
readonly employeeInvite: import("zod").ZodObject<{
|
|
4899
4923
|
email: import("zod").ZodString;
|