@withpotter/components 0.13.0-next.107 → 0.13.0-next.109
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/shell/settings-groups.js +1 -1
- package/dist/shell/settings-groups.js.map +1 -1
- package/dist/shell/settings-host.d.ts +12 -1
- package/dist/shell/settings-host.js +13 -1
- package/dist/shell/settings-host.js.map +1 -1
- package/dist/shell/settings-panels.js +27 -11
- package/dist/shell/settings-panels.js.map +1 -1
- package/dist/shell/settings-rows.d.ts +3 -1
- package/dist/shell/settings-rows.js +89 -56
- package/dist/shell/settings-rows.js.map +1 -1
- package/dist/shell/shell.css +41 -0
- package/dist/shell/unified-settings-modal.js +110 -69
- package/dist/shell/unified-settings-modal.js.map +1 -1
- package/package.json +4 -4
|
@@ -6,7 +6,7 @@ const SETTINGS_GROUP_LABEL = {
|
|
|
6
6
|
[SettingsGroupKey.Business]: "Business",
|
|
7
7
|
[SettingsGroupKey.You]: "You"
|
|
8
8
|
};
|
|
9
|
-
const PERSONAL = /* @__PURE__ */ new Set(["
|
|
9
|
+
const PERSONAL = /* @__PURE__ */ new Set(["profile", "security", "notifications", "preferences"]);
|
|
10
10
|
function groupOf(id) {
|
|
11
11
|
return PERSONAL.has(id) ? SettingsGroupKey.You : SettingsGroupKey.Business;
|
|
12
12
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/shell/settings-groups.ts"],"sourcesContent":["/**\n * The rail of the settings window is two groups: the business's settings, and the person's own. A row belongs to\n * Business unless it is one of the few that are about the signed-in person. A host's own rows are the business's.\n */\nexport const SettingsGroupKey = {\n Business: 'business',\n You: 'you',\n} as const;\nexport type SettingsGroupKey = (typeof SettingsGroupKey)[keyof typeof SettingsGroupKey];\n\nexport const SETTINGS_GROUP_LABEL: Record<SettingsGroupKey, string> = {\n [SettingsGroupKey.Business]: 'Business',\n [SettingsGroupKey.You]: 'You',\n};\n\n/** The rows that are about the person, not the business. */\nconst PERSONAL = new Set(['
|
|
1
|
+
{"version":3,"sources":["../../src/shell/settings-groups.ts"],"sourcesContent":["/**\n * The rail of the settings window is two groups: the business's settings, and the person's own. A row belongs to\n * Business unless it is one of the few that are about the signed-in person. A host's own rows are the business's.\n */\nexport const SettingsGroupKey = {\n Business: 'business',\n You: 'you',\n} as const;\nexport type SettingsGroupKey = (typeof SettingsGroupKey)[keyof typeof SettingsGroupKey];\n\nexport const SETTINGS_GROUP_LABEL: Record<SettingsGroupKey, string> = {\n [SettingsGroupKey.Business]: 'Business',\n [SettingsGroupKey.You]: 'You',\n};\n\n/** The rows that are about the person, not the business. */\nconst PERSONAL = new Set(['profile', 'security', 'notifications', 'preferences']);\n\nexport function groupOf(id: string): SettingsGroupKey {\n return PERSONAL.has(id) ? SettingsGroupKey.You : SettingsGroupKey.Business;\n}\n\nexport interface SettingsGroup<T> {\n key: SettingsGroupKey;\n label: string;\n rows: T[];\n}\n\n/**\n * The rows split under their group labels, each group in the order the rows came in, Business first. A group with no\n * rows (a search that matches none of them) is left out, so no label is ever drawn over nothing.\n */\nexport function groupRows<T extends { id: string }>(rows: T[]): SettingsGroup<T>[] {\n return [SettingsGroupKey.Business, SettingsGroupKey.You]\n .map((key) => ({ key, label: SETTINGS_GROUP_LABEL[key], rows: rows.filter((row) => groupOf(row.id) === key) }))\n .filter((group) => group.rows.length > 0);\n}\n"],"mappings":"AAIO,MAAM,mBAAmB;AAAA,EAC9B,UAAU;AAAA,EACV,KAAK;AACP;AAGO,MAAM,uBAAyD;AAAA,EACpE,CAAC,iBAAiB,QAAQ,GAAG;AAAA,EAC7B,CAAC,iBAAiB,GAAG,GAAG;AAC1B;AAGA,MAAM,WAAW,oBAAI,IAAI,CAAC,WAAW,YAAY,iBAAiB,aAAa,CAAC;AAEzE,SAAS,QAAQ,IAA8B;AACpD,SAAO,SAAS,IAAI,EAAE,IAAI,iBAAiB,MAAM,iBAAiB;AACpE;AAYO,SAAS,UAAoC,MAA+B;AACjF,SAAO,CAAC,iBAAiB,UAAU,iBAAiB,GAAG,EACpD,IAAI,CAAC,SAAS,EAAE,KAAK,OAAO,qBAAqB,GAAG,GAAG,MAAM,KAAK,OAAO,CAAC,QAAQ,QAAQ,IAAI,EAAE,MAAM,GAAG,EAAE,EAAE,EAC7G,OAAO,CAAC,UAAU,MAAM,KAAK,SAAS,CAAC;AAC5C;","names":[]}
|
|
@@ -9,6 +9,8 @@ import type { LucideIcon } from 'lucide-react';
|
|
|
9
9
|
export interface HostSettingsRow {
|
|
10
10
|
id: string;
|
|
11
11
|
name: string;
|
|
12
|
+
/** The line under the title when the row is open. */
|
|
13
|
+
subtitle?: string;
|
|
12
14
|
icon: LucideIcon;
|
|
13
15
|
/** Addresses this row owns: a link to any of them opens the window on it. */
|
|
14
16
|
paths: string[];
|
|
@@ -19,10 +21,19 @@ export interface HostSettingsRow {
|
|
|
19
21
|
/** The screen drawn in the window. */
|
|
20
22
|
render: () => ReactNode;
|
|
21
23
|
}
|
|
22
|
-
export declare function SettingsHostProvider({ rows, children, }: {
|
|
24
|
+
export declare function SettingsHostProvider({ rows, generalRows, children, }: {
|
|
23
25
|
rows: HostSettingsRow[];
|
|
26
|
+
/**
|
|
27
|
+
* Rows a host adds to the Business card of General, below the business's own details. Olive shows its CAC
|
|
28
|
+
* certificate there. A host that passes nothing gets the card it had.
|
|
29
|
+
*/
|
|
30
|
+
generalRows?: () => ReactNode;
|
|
24
31
|
children: ReactNode;
|
|
25
32
|
}): import("react").JSX.Element;
|
|
33
|
+
/** What a host drew into a slot, or null when there is no host, or the host has nothing to show right now. */
|
|
34
|
+
export declare function slotContent(render: (() => ReactNode) | null | undefined): ReactNode;
|
|
35
|
+
/** The host's rows for the Business card of General, drawn by the card itself. */
|
|
36
|
+
export declare function useGeneralHostRows(): ReactNode;
|
|
26
37
|
export declare function useSettingsHostRows(): HostSettingsRow[];
|
|
27
38
|
/** The host row that owns an address: the address is one of its paths or sits below one. */
|
|
28
39
|
export declare function hostRowForHref(rows: HostSettingsRow[], href: string | null | undefined): HostSettingsRow | undefined;
|
|
@@ -2,11 +2,21 @@
|
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import { createContext, useContext } from "react";
|
|
4
4
|
const Context = createContext([]);
|
|
5
|
+
const GeneralSlotContext = createContext(null);
|
|
5
6
|
function SettingsHostProvider({
|
|
6
7
|
rows,
|
|
8
|
+
generalRows,
|
|
7
9
|
children
|
|
8
10
|
}) {
|
|
9
|
-
return /* @__PURE__ */ jsx(Context.Provider, { value: rows, children });
|
|
11
|
+
return /* @__PURE__ */ jsx(Context.Provider, { value: rows, children: /* @__PURE__ */ jsx(GeneralSlotContext.Provider, { value: generalRows ?? null, children }) });
|
|
12
|
+
}
|
|
13
|
+
function slotContent(render) {
|
|
14
|
+
if (!render) return null;
|
|
15
|
+
const content = render();
|
|
16
|
+
return content === void 0 || content === false ? null : content;
|
|
17
|
+
}
|
|
18
|
+
function useGeneralHostRows() {
|
|
19
|
+
return slotContent(useContext(GeneralSlotContext));
|
|
10
20
|
}
|
|
11
21
|
function useSettingsHostRows() {
|
|
12
22
|
return useContext(Context);
|
|
@@ -20,6 +30,8 @@ function hostRowForHref(rows, href) {
|
|
|
20
30
|
export {
|
|
21
31
|
SettingsHostProvider,
|
|
22
32
|
hostRowForHref,
|
|
33
|
+
slotContent,
|
|
34
|
+
useGeneralHostRows,
|
|
23
35
|
useSettingsHostRows
|
|
24
36
|
};
|
|
25
37
|
//# sourceMappingURL=settings-host.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/shell/settings-host.tsx"],"sourcesContent":["'use client';\n\nimport { createContext, useContext, type ReactNode } from 'react';\nimport type { LucideIcon } from 'lucide-react';\n\n/**\n * A row a host adds to the settings window, with the screen behind it.\n *\n * Olive's plans come from its own engine, so its Plan & billing is its own screen rather than the shared Billing page.\n * A host that never wraps the dashboard in `SettingsHostProvider` gets the window it had.\n */\nexport interface HostSettingsRow {\n id: string;\n name: string;\n icon: LucideIcon;\n /** Addresses this row owns: a link to any of them opens the window on it. */\n paths: string[];\n /** What search also matches. */\n keywords: string[];\n /** The row it is placed after. Default: after General. */\n after?: string;\n /** The screen drawn in the window. */\n render: () => ReactNode;\n}\n\nconst Context = createContext<HostSettingsRow[]>([]);\n\nexport function SettingsHostProvider({\n rows,\n children,\n}: {\n rows: HostSettingsRow[];\n children: ReactNode;\n}) {\n return <Context.Provider value={rows}
|
|
1
|
+
{"version":3,"sources":["../../src/shell/settings-host.tsx"],"sourcesContent":["'use client';\n\nimport { createContext, useContext, type ReactNode } from 'react';\nimport type { LucideIcon } from 'lucide-react';\n\n/**\n * A row a host adds to the settings window, with the screen behind it.\n *\n * Olive's plans come from its own engine, so its Plan & billing is its own screen rather than the shared Billing page.\n * A host that never wraps the dashboard in `SettingsHostProvider` gets the window it had.\n */\nexport interface HostSettingsRow {\n id: string;\n name: string;\n /** The line under the title when the row is open. */\n subtitle?: string;\n icon: LucideIcon;\n /** Addresses this row owns: a link to any of them opens the window on it. */\n paths: string[];\n /** What search also matches. */\n keywords: string[];\n /** The row it is placed after. Default: after General. */\n after?: string;\n /** The screen drawn in the window. */\n render: () => ReactNode;\n}\n\nconst Context = createContext<HostSettingsRow[]>([]);\nconst GeneralSlotContext = createContext<(() => ReactNode) | null>(null);\n\nexport function SettingsHostProvider({\n rows,\n generalRows,\n children,\n}: {\n rows: HostSettingsRow[];\n /**\n * Rows a host adds to the Business card of General, below the business's own details. Olive shows its CAC\n * certificate there. A host that passes nothing gets the card it had.\n */\n generalRows?: () => ReactNode;\n children: ReactNode;\n}) {\n return (\n <Context.Provider value={rows}>\n <GeneralSlotContext.Provider value={generalRows ?? null}>\n {children}\n </GeneralSlotContext.Provider>\n </Context.Provider>\n );\n}\n\n/** What a host drew into a slot, or null when there is no host, or the host has nothing to show right now. */\nexport function slotContent(render: (() => ReactNode) | null | undefined): ReactNode {\n if (!render) return null;\n\n const content = render();\n\n return content === undefined || content === false ? null : content;\n}\n\n/** The host's rows for the Business card of General, drawn by the card itself. */\nexport function useGeneralHostRows(): ReactNode {\n return slotContent(useContext(GeneralSlotContext));\n}\n\nexport function useSettingsHostRows(): HostSettingsRow[] {\n return useContext(Context);\n}\n\n/** The host row that owns an address: the address is one of its paths or sits below one. */\nexport function hostRowForHref(\n rows: HostSettingsRow[],\n href: string | null | undefined,\n): HostSettingsRow | undefined {\n const path = (href ?? '').split(/[?#]/)[0];\n\n return rows.find((row) =>\n row.paths.some((p) => path === p || path.startsWith(`${p}/`)),\n );\n}\n"],"mappings":";AA6CM;AA3CN,SAAS,eAAe,kBAAkC;AAyB1D,MAAM,UAAU,cAAiC,CAAC,CAAC;AACnD,MAAM,qBAAqB,cAAwC,IAAI;AAEhE,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AACF,GAQG;AACD,SACE,oBAAC,QAAQ,UAAR,EAAiB,OAAO,MACvB,8BAAC,mBAAmB,UAAnB,EAA4B,OAAO,eAAe,MAChD,UACH,GACF;AAEJ;AAGO,SAAS,YAAY,QAAyD;AACnF,MAAI,CAAC,OAAQ,QAAO;AAEpB,QAAM,UAAU,OAAO;AAEvB,SAAO,YAAY,UAAa,YAAY,QAAQ,OAAO;AAC7D;AAGO,SAAS,qBAAgC;AAC9C,SAAO,YAAY,WAAW,kBAAkB,CAAC;AACnD;AAEO,SAAS,sBAAyC;AACvD,SAAO,WAAW,OAAO;AAC3B;AAGO,SAAS,eACd,MACA,MAC6B;AAC7B,QAAM,QAAQ,QAAQ,IAAI,MAAM,MAAM,EAAE,CAAC;AAEzC,SAAO,KAAK;AAAA,IAAK,CAAC,QAChB,IAAI,MAAM,KAAK,CAAC,MAAM,SAAS,KAAK,KAAK,WAAW,GAAG,CAAC,GAAG,CAAC;AAAA,EAC9D;AACF;","names":[]}
|
|
@@ -8,8 +8,10 @@ import { SignInContent } from "@withpotter/pages-core/dashboard/settings/devices
|
|
|
8
8
|
import { DataPrivacyContent } from "@withpotter/pages-core/dashboard/settings/data-privacy/_components/data-privacy-content";
|
|
9
9
|
import { PreferencesContent } from "@withpotter/pages-core/dashboard/settings/language/_components/preferences-content";
|
|
10
10
|
import { useAccessibility } from "@withpotter/lib/accessibility-context";
|
|
11
|
+
import { usePermissions } from "@withpotter/lib/use-permissions";
|
|
11
12
|
import { RotateCcw } from "lucide-react";
|
|
12
13
|
import { AccessibilityCard } from "@withpotter/pages-core/dashboard/settings/accessibility/_components/accessibility-card";
|
|
14
|
+
import { TaxesSettingsContent } from "@withpotter/pages-core/dashboard/settings/taxes/_components/taxes-content";
|
|
13
15
|
import { PoliciesContent } from "@withpotter/pages-core/dashboard/settings/policies/_components/policies-content";
|
|
14
16
|
import { UsageContent } from "@withpotter/pages-core/dashboard/settings/usage/_components/usage-content";
|
|
15
17
|
import { GeneralContent } from "@withpotter/pages-core/dashboard/settings/_components/general-content";
|
|
@@ -34,14 +36,25 @@ function Section({
|
|
|
34
36
|
children
|
|
35
37
|
] });
|
|
36
38
|
}
|
|
37
|
-
function
|
|
39
|
+
function ProfilePanel() {
|
|
38
40
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-8", children: [
|
|
39
|
-
/* @__PURE__ */ jsx(
|
|
40
|
-
/* @__PURE__ */ jsx(Section, { title: "Sign-in and security", children: /* @__PURE__ */ jsx("div", { className: "px-4", children: /* @__PURE__ */ jsx(SignInContent, {}) }) }),
|
|
41
|
-
/* @__PURE__ */ jsx(Section, { title: "Data & privacy", children: /* @__PURE__ */ jsx(DataPrivacyContent, {}) }),
|
|
41
|
+
/* @__PURE__ */ jsx(ProfileContent, {}),
|
|
42
42
|
/* @__PURE__ */ jsx(SecurityContent, {})
|
|
43
43
|
] });
|
|
44
44
|
}
|
|
45
|
+
function SecurityPanel() {
|
|
46
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-8", children: [
|
|
47
|
+
/* @__PURE__ */ jsx(Section, { title: "Sign-in and security", children: /* @__PURE__ */ jsx("div", { className: "px-4", children: /* @__PURE__ */ jsx(SignInContent, {}) }) }),
|
|
48
|
+
/* @__PURE__ */ jsx(Section, { title: "Data & privacy", children: /* @__PURE__ */ jsx(DataPrivacyContent, {}) })
|
|
49
|
+
] });
|
|
50
|
+
}
|
|
51
|
+
function ExtrasPanel() {
|
|
52
|
+
const { can } = usePermissions();
|
|
53
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-8", children: [
|
|
54
|
+
can("canManageSettings") && /* @__PURE__ */ jsx(FeaturesContent, {}),
|
|
55
|
+
/* @__PURE__ */ jsx(Section, { title: "Sample content", children: /* @__PURE__ */ jsx(DemoContentCard, {}) })
|
|
56
|
+
] });
|
|
57
|
+
}
|
|
45
58
|
function ResetAccessibility() {
|
|
46
59
|
const a11y = useAccessibility();
|
|
47
60
|
return /* @__PURE__ */ jsxs(
|
|
@@ -58,21 +71,24 @@ function ResetAccessibility() {
|
|
|
58
71
|
);
|
|
59
72
|
}
|
|
60
73
|
function PreferencesPanel() {
|
|
61
|
-
return /* @__PURE__ */
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
74
|
+
return /* @__PURE__ */ jsx(
|
|
75
|
+
PreferencesContent,
|
|
76
|
+
{
|
|
77
|
+
accessibility: /* @__PURE__ */ jsx(AccessibilityCard, { action: /* @__PURE__ */ jsx(ResetAccessibility, {}) })
|
|
78
|
+
}
|
|
79
|
+
);
|
|
65
80
|
}
|
|
66
81
|
const PANELS = {
|
|
67
82
|
general: () => /* @__PURE__ */ jsx(GeneralContent, {}),
|
|
68
|
-
|
|
69
|
-
|
|
83
|
+
extras: () => /* @__PURE__ */ jsx(ExtrasPanel, {}),
|
|
84
|
+
profile: () => /* @__PURE__ */ jsx(ProfilePanel, {}),
|
|
85
|
+
security: () => /* @__PURE__ */ jsx(SecurityPanel, {}),
|
|
70
86
|
team: () => /* @__PURE__ */ jsx(TeamContent, {}),
|
|
71
87
|
notifications: () => /* @__PURE__ */ jsx(NotificationsContent, {}),
|
|
72
|
-
"demo-content": () => /* @__PURE__ */ jsx(DemoContentCard, {}),
|
|
73
88
|
preferences: () => /* @__PURE__ */ jsx(PreferencesPanel, {}),
|
|
74
89
|
"payment-approvals": () => /* @__PURE__ */ jsx(PaymentApprovalsContent, {}),
|
|
75
90
|
billing: () => /* @__PURE__ */ jsx(BillingContent, {}),
|
|
91
|
+
taxes: () => /* @__PURE__ */ jsx(TaxesSettingsContent, {}),
|
|
76
92
|
usage: () => /* @__PURE__ */ jsx(UsageContent, {}),
|
|
77
93
|
policies: () => /* @__PURE__ */ jsx(PoliciesContent, {}),
|
|
78
94
|
legal: () => /* @__PURE__ */ jsx(LegalContent, {})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/shell/settings-panels.tsx"],"sourcesContent":["'use client';\n\nimport { StateView } from '@withpotter/ui-kit/state-view';\nimport { Suspense, type ReactNode } from 'react';\nimport { ProfileContent } from '@withpotter/pages-core/dashboard/settings/profile/_components/profile-content';\nimport { SecurityContent } from '@withpotter/pages-core/dashboard/settings/profile/_components/security-content';\nimport { SignInContent } from '@withpotter/pages-core/dashboard/settings/devices/_components/sign-in-content';\nimport { DataPrivacyContent } from '@withpotter/pages-core/dashboard/settings/data-privacy/_components/data-privacy-content';\nimport { PreferencesContent } from '@withpotter/pages-core/dashboard/settings/language/_components/preferences-content';\nimport { useAccessibility } from '@withpotter/lib/accessibility-context';\nimport { RotateCcw } from 'lucide-react';\nimport { AccessibilityCard } from '@withpotter/pages-core/dashboard/settings/accessibility/_components/accessibility-card';\nimport { PoliciesContent } from '@withpotter/pages-core/dashboard/settings/policies/_components/policies-content';\nimport { UsageContent } from '@withpotter/pages-core/dashboard/settings/usage/_components/usage-content';\nimport { GeneralContent } from '@withpotter/pages-core/dashboard/settings/_components/general-content';\nimport { NotificationsContent } from '@withpotter/pages-core/dashboard/settings/notifications/_components/notifications-content';\nimport { TeamContent } from '@withpotter/pages-core/dashboard/settings/team/_components/team-content';\nimport { BillingContent } from '@withpotter/pages-core/dashboard/settings/billing/_components/billing-content';\nimport { LegalContent } from '@withpotter/pages-core/dashboard/settings/legal/_components/legal-content';\nimport { PaymentApprovalsContent } from '@withpotter/pages-core/dashboard/settings/payment-approvals/_components/payment-approvals-content';\nimport { DemoContentCard } from '@withpotter/pages-core/dashboard/settings/demo-content/_components/demo-content-card';\nimport { FeaturesContent } from '@withpotter/pages-core/dashboard/settings/features/_components/features-content';\nimport type { SettingsRowId } from './settings-rows';\n\n/**\n * The page behind a row, drawn in the window.\n *\n * The business rows render the real settings page, so each keeps its own\n * loading, saving and sections. They are loaded on first open of a row rather\n * than with the window, and each is a client page that reads the same hooks it\n * always did.\n */\n/** What a row shows while its code loads, so a first open is never blank. */\nconst loading = () => <StateView kind=\"loading\" title=\"Loading\" />;\n\n/** A section heading inside a merged page. */\nfunction Section({\n title,\n action,\n children,\n}: {\n title: string;\n action?: ReactNode;\n children: ReactNode;\n}) {\n return (\n <section className=\"space-y-3\">\n <div className=\"flex items-center justify-between gap-3 px-4\">\n <h3 className=\"text-sm font-semibold text-[var(--text-primary)]\">\n {title}\n </h3>\n {action}\n </div>\n {children}\n </section>\n );\n}\n\n/**\n *
|
|
1
|
+
{"version":3,"sources":["../../src/shell/settings-panels.tsx"],"sourcesContent":["'use client';\n\nimport { StateView } from '@withpotter/ui-kit/state-view';\nimport { Suspense, type ReactNode } from 'react';\nimport { ProfileContent } from '@withpotter/pages-core/dashboard/settings/profile/_components/profile-content';\nimport { SecurityContent } from '@withpotter/pages-core/dashboard/settings/profile/_components/security-content';\nimport { SignInContent } from '@withpotter/pages-core/dashboard/settings/devices/_components/sign-in-content';\nimport { DataPrivacyContent } from '@withpotter/pages-core/dashboard/settings/data-privacy/_components/data-privacy-content';\nimport { PreferencesContent } from '@withpotter/pages-core/dashboard/settings/language/_components/preferences-content';\nimport { useAccessibility } from '@withpotter/lib/accessibility-context';\nimport { usePermissions } from '@withpotter/lib/use-permissions';\nimport { RotateCcw } from 'lucide-react';\nimport { AccessibilityCard } from '@withpotter/pages-core/dashboard/settings/accessibility/_components/accessibility-card';\nimport { TaxesSettingsContent } from '@withpotter/pages-core/dashboard/settings/taxes/_components/taxes-content';\nimport { PoliciesContent } from '@withpotter/pages-core/dashboard/settings/policies/_components/policies-content';\nimport { UsageContent } from '@withpotter/pages-core/dashboard/settings/usage/_components/usage-content';\nimport { GeneralContent } from '@withpotter/pages-core/dashboard/settings/_components/general-content';\nimport { NotificationsContent } from '@withpotter/pages-core/dashboard/settings/notifications/_components/notifications-content';\nimport { TeamContent } from '@withpotter/pages-core/dashboard/settings/team/_components/team-content';\nimport { BillingContent } from '@withpotter/pages-core/dashboard/settings/billing/_components/billing-content';\nimport { LegalContent } from '@withpotter/pages-core/dashboard/settings/legal/_components/legal-content';\nimport { PaymentApprovalsContent } from '@withpotter/pages-core/dashboard/settings/payment-approvals/_components/payment-approvals-content';\nimport { DemoContentCard } from '@withpotter/pages-core/dashboard/settings/demo-content/_components/demo-content-card';\nimport { FeaturesContent } from '@withpotter/pages-core/dashboard/settings/features/_components/features-content';\nimport type { SettingsRowId } from './settings-rows';\n\n/**\n * The page behind a row, drawn in the window.\n *\n * The business rows render the real settings page, so each keeps its own\n * loading, saving and sections. They are loaded on first open of a row rather\n * than with the window, and each is a client page that reads the same hooks it\n * always did.\n */\n/** What a row shows while its code loads, so a first open is never blank. */\nconst loading = () => <StateView kind=\"loading\" title=\"Loading\" />;\n\n/** A section heading inside a merged page. */\nfunction Section({\n title,\n action,\n children,\n}: {\n title: string;\n action?: ReactNode;\n children: ReactNode;\n}) {\n return (\n <section className=\"space-y-3\">\n <div className=\"flex items-center justify-between gap-3 px-4\">\n <h3 className=\"text-sm font-semibold text-[var(--text-primary)]\">\n {title}\n </h3>\n {action}\n </div>\n {children}\n </section>\n );\n}\n\n/**\n * Who the person is: their profile. Closing the account is last, as it is the\n * one thing here that cannot be undone.\n */\nfunction ProfilePanel() {\n return (\n <div className=\"space-y-8\">\n <ProfileContent />\n <SecurityContent />\n </div>\n );\n}\n\n/** How the person signs in and where, and what is kept about them. */\nfunction SecurityPanel() {\n return (\n <div className=\"space-y-8\">\n <Section title=\"Sign-in and security\">\n <div className=\"px-4\">\n <SignInContent />\n </div>\n </Section>\n <Section title=\"Data & privacy\">\n <DataPrivacyContent />\n </Section>\n </div>\n );\n}\n\n/**\n * The switches (Blog today) and the sample content, in one place. The switches\n * are shown only to someone who may change them; the demo card decides for\n * itself who sees its actions.\n */\nfunction ExtrasPanel() {\n const { can } = usePermissions();\n return (\n <div className=\"space-y-8\">\n {can('canManageSettings') && <FeaturesContent />}\n <Section title=\"Sample content\">\n <DemoContentCard />\n </Section>\n </div>\n );\n}\n\n/** Reset to defaults was a page header action; here it sits on its section. */\nfunction ResetAccessibility() {\n const a11y = useAccessibility();\n return (\n <button\n type=\"button\"\n onClick={a11y.reset}\n className=\"inline-flex items-center gap-2 text-xs font-medium text-[var(--text-secondary)] hover:text-[var(--text-primary)]\"\n >\n <RotateCcw className=\"size-4\" />\n Reset to defaults\n </button>\n );\n}\n\n/** How the app looks and behaves for the person using it. */\nfunction PreferencesPanel() {\n return (\n <PreferencesContent\n accessibility={<AccessibilityCard action={<ResetAccessibility />} />}\n />\n );\n}\n\nconst PANELS: Record<SettingsRowId, () => ReactNode> = {\n general: () => <GeneralContent />,\n extras: () => <ExtrasPanel />,\n profile: () => <ProfilePanel />,\n security: () => <SecurityPanel />,\n team: () => <TeamContent />,\n notifications: () => <NotificationsContent />,\n preferences: () => <PreferencesPanel />,\n 'payment-approvals': () => <PaymentApprovalsContent />,\n billing: () => <BillingContent />,\n taxes: () => <TaxesSettingsContent />,\n usage: () => <UsageContent />,\n policies: () => <PoliciesContent />,\n legal: () => <LegalContent />,\n};\n\nexport function SettingsPanel({ id }: { id: SettingsRowId }) {\n // Some pages read the query string, which needs a boundary above it.\n return <Suspense fallback={loading()}>{PANELS[id]()}</Suspense>;\n}\n"],"mappings":";AAmCsB,cAchB,YAdgB;AAjCtB,SAAS,iBAAiB;AAC1B,SAAS,gBAAgC;AACzC,SAAS,sBAAsB;AAC/B,SAAS,uBAAuB;AAChC,SAAS,qBAAqB;AAC9B,SAAS,0BAA0B;AACnC,SAAS,0BAA0B;AACnC,SAAS,wBAAwB;AACjC,SAAS,sBAAsB;AAC/B,SAAS,iBAAiB;AAC1B,SAAS,yBAAyB;AAClC,SAAS,4BAA4B;AACrC,SAAS,uBAAuB;AAChC,SAAS,oBAAoB;AAC7B,SAAS,sBAAsB;AAC/B,SAAS,4BAA4B;AACrC,SAAS,mBAAmB;AAC5B,SAAS,sBAAsB;AAC/B,SAAS,oBAAoB;AAC7B,SAAS,+BAA+B;AACxC,SAAS,uBAAuB;AAChC,SAAS,uBAAuB;AAYhC,MAAM,UAAU,MAAM,oBAAC,aAAU,MAAK,WAAU,OAAM,WAAU;AAGhE,SAAS,QAAQ;AAAA,EACf;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,SACE,qBAAC,aAAQ,WAAU,aACjB;AAAA,yBAAC,SAAI,WAAU,gDACb;AAAA,0BAAC,QAAG,WAAU,oDACX,iBACH;AAAA,MACC;AAAA,OACH;AAAA,IACC;AAAA,KACH;AAEJ;AAMA,SAAS,eAAe;AACtB,SACE,qBAAC,SAAI,WAAU,aACb;AAAA,wBAAC,kBAAe;AAAA,IAChB,oBAAC,mBAAgB;AAAA,KACnB;AAEJ;AAGA,SAAS,gBAAgB;AACvB,SACE,qBAAC,SAAI,WAAU,aACb;AAAA,wBAAC,WAAQ,OAAM,wBACb,8BAAC,SAAI,WAAU,QACb,8BAAC,iBAAc,GACjB,GACF;AAAA,IACA,oBAAC,WAAQ,OAAM,kBACb,8BAAC,sBAAmB,GACtB;AAAA,KACF;AAEJ;AAOA,SAAS,cAAc;AACrB,QAAM,EAAE,IAAI,IAAI,eAAe;AAC/B,SACE,qBAAC,SAAI,WAAU,aACZ;AAAA,QAAI,mBAAmB,KAAK,oBAAC,mBAAgB;AAAA,IAC9C,oBAAC,WAAQ,OAAM,kBACb,8BAAC,mBAAgB,GACnB;AAAA,KACF;AAEJ;AAGA,SAAS,qBAAqB;AAC5B,QAAM,OAAO,iBAAiB;AAC9B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,SAAS,KAAK;AAAA,MACd,WAAU;AAAA,MAEV;AAAA,4BAAC,aAAU,WAAU,UAAS;AAAA,QAAE;AAAA;AAAA;AAAA,EAElC;AAEJ;AAGA,SAAS,mBAAmB;AAC1B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,eAAe,oBAAC,qBAAkB,QAAQ,oBAAC,sBAAmB,GAAI;AAAA;AAAA,EACpE;AAEJ;AAEA,MAAM,SAAiD;AAAA,EACrD,SAAS,MAAM,oBAAC,kBAAe;AAAA,EAC/B,QAAQ,MAAM,oBAAC,eAAY;AAAA,EAC3B,SAAS,MAAM,oBAAC,gBAAa;AAAA,EAC7B,UAAU,MAAM,oBAAC,iBAAc;AAAA,EAC/B,MAAM,MAAM,oBAAC,eAAY;AAAA,EACzB,eAAe,MAAM,oBAAC,wBAAqB;AAAA,EAC3C,aAAa,MAAM,oBAAC,oBAAiB;AAAA,EACrC,qBAAqB,MAAM,oBAAC,2BAAwB;AAAA,EACpD,SAAS,MAAM,oBAAC,kBAAe;AAAA,EAC/B,OAAO,MAAM,oBAAC,wBAAqB;AAAA,EACnC,OAAO,MAAM,oBAAC,gBAAa;AAAA,EAC3B,UAAU,MAAM,oBAAC,mBAAgB;AAAA,EACjC,OAAO,MAAM,oBAAC,gBAAa;AAC7B;AAEO,SAAS,cAAc,EAAE,GAAG,GAA0B;AAE3D,SAAO,oBAAC,YAAS,UAAU,QAAQ,GAAI,iBAAO,EAAE,EAAE,GAAE;AACtD;","names":[]}
|
|
@@ -12,10 +12,12 @@ import type { PermissionKey } from '@withpotter/lib/use-permissions';
|
|
|
12
12
|
* `keywords` are what search also matches: the names of the sections inside a
|
|
13
13
|
* row, so "passkeys" or "time zone" finds the row that holds them.
|
|
14
14
|
*/
|
|
15
|
-
export type SettingsRowId = 'general' | '
|
|
15
|
+
export type SettingsRowId = 'general' | 'extras' | 'profile' | 'security' | 'team' | 'notifications' | 'preferences' | 'payment-approvals' | 'billing' | 'taxes' | 'usage' | 'policies' | 'legal';
|
|
16
16
|
export interface SettingsRow {
|
|
17
17
|
id: SettingsRowId;
|
|
18
18
|
name: string;
|
|
19
|
+
/** The line under the title when the row is open. */
|
|
20
|
+
subtitle?: string;
|
|
19
21
|
icon: LucideIcon;
|
|
20
22
|
paths: string[];
|
|
21
23
|
keywords: string[];
|
|
@@ -6,42 +6,45 @@ import {
|
|
|
6
6
|
CircleHelp,
|
|
7
7
|
CreditCard,
|
|
8
8
|
FileText,
|
|
9
|
+
Lock,
|
|
10
|
+
ReceiptText,
|
|
9
11
|
Scale,
|
|
10
12
|
Sparkles,
|
|
11
13
|
SlidersHorizontal,
|
|
12
|
-
ToggleLeft,
|
|
13
14
|
UserRound,
|
|
14
15
|
Users
|
|
15
16
|
} from "lucide-react";
|
|
16
17
|
const BILLING_ROW = {
|
|
17
18
|
id: "billing",
|
|
18
19
|
name: "Billing & plan",
|
|
20
|
+
subtitle: "Your plan, how you pay, and your payments.",
|
|
19
21
|
icon: CreditCard,
|
|
20
22
|
paths: ["/dashboard/settings/billing"],
|
|
21
23
|
keywords: ["plan", "invoice", "credits", "top-up", "cancel"],
|
|
22
24
|
permission: ["canManageSettings"]
|
|
23
25
|
};
|
|
26
|
+
const TAXES_ROW = {
|
|
27
|
+
id: "taxes",
|
|
28
|
+
name: "Taxes",
|
|
29
|
+
subtitle: "What you charge, and the number shown on receipts.",
|
|
30
|
+
icon: ReceiptText,
|
|
31
|
+
paths: ["/dashboard/settings/taxes"],
|
|
32
|
+
keywords: ["vat", "tax rate", "registration number", "receipts"],
|
|
33
|
+
permission: ["canManageSettings"]
|
|
34
|
+
};
|
|
24
35
|
const PAYMENT_APPROVALS_ROW = {
|
|
25
36
|
id: "payment-approvals",
|
|
26
37
|
name: "Payment approvals",
|
|
38
|
+
subtitle: "Who approves money going out.",
|
|
27
39
|
icon: CircleCheck,
|
|
28
40
|
paths: ["/dashboard/settings/payment-approvals"],
|
|
29
41
|
keywords: ["approval", "accepted", "rejected", "decisions", "history"]
|
|
30
42
|
};
|
|
31
|
-
const DEMO_CONTENT_ROW = {
|
|
32
|
-
// Sample products, pages and pictures to start with, clear, or add again.
|
|
33
|
-
// No permission gate, like the sidebar entry: everyone sees the state and
|
|
34
|
-
// the card itself shows the actions only to the owner and admins.
|
|
35
|
-
id: "demo-content",
|
|
36
|
-
name: "Demo content",
|
|
37
|
-
icon: Sparkles,
|
|
38
|
-
paths: ["/dashboard/settings/demo-content"],
|
|
39
|
-
keywords: ["sample", "products", "starter", "clear", "example data"]
|
|
40
|
-
};
|
|
41
43
|
const SETTINGS_ROWS = [
|
|
42
44
|
{
|
|
43
45
|
id: "general",
|
|
44
46
|
name: "General",
|
|
47
|
+
subtitle: "Your business details and store address.",
|
|
45
48
|
icon: Building2,
|
|
46
49
|
paths: [
|
|
47
50
|
"/dashboard/settings",
|
|
@@ -51,68 +54,107 @@ const SETTINGS_ROWS = [
|
|
|
51
54
|
keywords: ["business name", "branding", "logo", "colours", "numbering"],
|
|
52
55
|
permission: ["canManageBusinessProfile", "canManageSettings"]
|
|
53
56
|
},
|
|
57
|
+
// A licensed box that bills outside the console hides the row.
|
|
58
|
+
...process.env.NEXT_PUBLIC_BOX_HIDE_BILLING === "true" ? [] : [BILLING_ROW],
|
|
59
|
+
// A box that keeps a tax (VAT) setting has it as its own row, after the plan.
|
|
60
|
+
...process.env.NEXT_PUBLIC_BOX_TAXES_IN_POLICIES === "true" ? [TAXES_ROW] : [],
|
|
61
|
+
{
|
|
62
|
+
id: "team",
|
|
63
|
+
name: "Team & permissions",
|
|
64
|
+
subtitle: "Who can do what in this business.",
|
|
65
|
+
icon: Users,
|
|
66
|
+
paths: [
|
|
67
|
+
"/dashboard/settings/team",
|
|
68
|
+
"/dashboard/settings/roles",
|
|
69
|
+
"/dashboard/settings/activity"
|
|
70
|
+
],
|
|
71
|
+
keywords: ["people", "invite", "roles", "activity", "seats"],
|
|
72
|
+
permission: ["canManageTeam"],
|
|
73
|
+
// A box with its own team screen (its roles and invitations come from
|
|
74
|
+
// its engine, which does not serve this panel's routes) names it here.
|
|
75
|
+
href: process.env.NEXT_PUBLIC_BOX_TEAM_PAGE || void 0
|
|
76
|
+
},
|
|
77
|
+
...process.env.NEXT_PUBLIC_BOX_PAYMENT_APPROVALS === "true" ? [PAYMENT_APPROVALS_ROW] : [],
|
|
54
78
|
{
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
paths: ["/dashboard/settings/features"],
|
|
62
|
-
keywords: ["blog", "posts", "write", "publish", "switch", "website", "features"],
|
|
79
|
+
id: "policies",
|
|
80
|
+
name: "Policies",
|
|
81
|
+
subtitle: "What your customers agree to.",
|
|
82
|
+
icon: Scale,
|
|
83
|
+
paths: ["/dashboard/settings/policies"],
|
|
84
|
+
keywords: ["returns", "shipping", "privacy", "terms"],
|
|
63
85
|
permission: ["canManageSettings"]
|
|
64
86
|
},
|
|
65
87
|
{
|
|
66
|
-
|
|
67
|
-
|
|
88
|
+
// The switches no business type implies (Blog today) and the sample
|
|
89
|
+
// content to start with, clear, or add again. One row for both. No
|
|
90
|
+
// permission gate on the row, like the Demo content entry in the sidebar:
|
|
91
|
+
// everyone sees the state, the panel shows the switches only to those who
|
|
92
|
+
// may change them, and the demo card shows its actions only to the owner
|
|
93
|
+
// and admins.
|
|
94
|
+
id: "extras",
|
|
95
|
+
name: "Extras",
|
|
96
|
+
subtitle: "Switches and sample content for your store.",
|
|
97
|
+
icon: Sparkles,
|
|
98
|
+
paths: [
|
|
99
|
+
"/dashboard/settings/features",
|
|
100
|
+
"/dashboard/settings/demo-content"
|
|
101
|
+
],
|
|
102
|
+
keywords: [
|
|
103
|
+
"blog",
|
|
104
|
+
"posts",
|
|
105
|
+
"write",
|
|
106
|
+
"publish",
|
|
107
|
+
"switch",
|
|
108
|
+
"website",
|
|
109
|
+
"features",
|
|
110
|
+
"sample",
|
|
111
|
+
"products",
|
|
112
|
+
"starter",
|
|
113
|
+
"clear",
|
|
114
|
+
"example data",
|
|
115
|
+
"demo content"
|
|
116
|
+
]
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
id: "profile",
|
|
120
|
+
name: "Profile",
|
|
121
|
+
subtitle: "Your name and how we reach you.",
|
|
68
122
|
icon: UserRound,
|
|
123
|
+
paths: ["/dashboard/settings/profile"],
|
|
124
|
+
keywords: ["name", "email", "photo", "phone", "close account"]
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
id: "security",
|
|
128
|
+
name: "Security",
|
|
129
|
+
subtitle: "How you sign in, and where you are signed in.",
|
|
130
|
+
icon: Lock,
|
|
69
131
|
paths: [
|
|
70
|
-
"/dashboard/settings/profile",
|
|
71
132
|
"/dashboard/settings/devices",
|
|
72
133
|
"/dashboard/settings/data-privacy"
|
|
73
134
|
],
|
|
74
135
|
keywords: [
|
|
75
|
-
"profile",
|
|
76
|
-
"name",
|
|
77
|
-
"email",
|
|
78
|
-
"photo",
|
|
79
|
-
"security",
|
|
80
136
|
"password",
|
|
81
137
|
"passkeys",
|
|
82
138
|
"two-factor",
|
|
83
139
|
"sessions",
|
|
84
140
|
"devices",
|
|
141
|
+
"sign in",
|
|
85
142
|
"privacy",
|
|
86
|
-
"export"
|
|
87
|
-
"close account"
|
|
143
|
+
"export"
|
|
88
144
|
]
|
|
89
145
|
},
|
|
90
|
-
{
|
|
91
|
-
id: "team",
|
|
92
|
-
name: "Team & permissions",
|
|
93
|
-
icon: Users,
|
|
94
|
-
paths: [
|
|
95
|
-
"/dashboard/settings/team",
|
|
96
|
-
"/dashboard/settings/roles",
|
|
97
|
-
"/dashboard/settings/activity"
|
|
98
|
-
],
|
|
99
|
-
keywords: ["people", "invite", "roles", "activity", "seats"],
|
|
100
|
-
permission: ["canManageTeam"],
|
|
101
|
-
// A box with its own team screen (its roles and invitations come from
|
|
102
|
-
// its engine, which does not serve this panel's routes) names it here.
|
|
103
|
-
href: process.env.NEXT_PUBLIC_BOX_TEAM_PAGE || void 0
|
|
104
|
-
},
|
|
105
146
|
{
|
|
106
147
|
id: "notifications",
|
|
107
148
|
name: "Notifications",
|
|
149
|
+
subtitle: "What we tell you, and what your customers hear from you.",
|
|
108
150
|
icon: Bell,
|
|
109
151
|
paths: ["/dashboard/settings/notifications"],
|
|
110
152
|
keywords: ["alerts", "low stock", "email sender", "digest", "reminders"]
|
|
111
153
|
},
|
|
112
|
-
DEMO_CONTENT_ROW,
|
|
113
154
|
{
|
|
114
155
|
id: "preferences",
|
|
115
156
|
name: "Preferences",
|
|
157
|
+
subtitle: "How the dashboard looks and behaves for you.",
|
|
116
158
|
icon: SlidersHorizontal,
|
|
117
159
|
paths: [
|
|
118
160
|
"/dashboard/settings/language",
|
|
@@ -132,31 +174,22 @@ const SETTINGS_ROWS = [
|
|
|
132
174
|
"text size"
|
|
133
175
|
]
|
|
134
176
|
},
|
|
135
|
-
...process.env.NEXT_PUBLIC_BOX_PAYMENT_APPROVALS === "true" ? [PAYMENT_APPROVALS_ROW] : [],
|
|
136
|
-
// A licensed box that bills outside the console hides the row.
|
|
137
|
-
...process.env.NEXT_PUBLIC_BOX_HIDE_BILLING === "true" ? [] : [BILLING_ROW],
|
|
138
177
|
// A box that meters nothing in the console leaves Usage out.
|
|
139
178
|
...process.env.NEXT_PUBLIC_BOX_HIDE_USAGE === "true" ? [] : [
|
|
140
179
|
{
|
|
141
180
|
id: "usage",
|
|
142
181
|
name: "Usage",
|
|
182
|
+
subtitle: "What you have used this month.",
|
|
143
183
|
icon: ChartColumn,
|
|
144
184
|
paths: ["/dashboard/settings/usage"],
|
|
145
185
|
keywords: ["credits", "delivery", "spend"],
|
|
146
186
|
permission: ["canManageSettings"]
|
|
147
187
|
}
|
|
148
188
|
],
|
|
149
|
-
{
|
|
150
|
-
id: "policies",
|
|
151
|
-
name: "Policies",
|
|
152
|
-
icon: Scale,
|
|
153
|
-
paths: ["/dashboard/settings/policies"],
|
|
154
|
-
keywords: ["returns", "shipping", "privacy", "terms"],
|
|
155
|
-
permission: ["canManageSettings"]
|
|
156
|
-
},
|
|
157
189
|
{
|
|
158
190
|
id: "legal",
|
|
159
191
|
name: "Legal",
|
|
192
|
+
subtitle: "Your terms and privacy pages.",
|
|
160
193
|
icon: FileText,
|
|
161
194
|
paths: ["/dashboard/settings/legal"],
|
|
162
195
|
keywords: ["terms", "privacy policy", "licences"]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/shell/settings-rows.ts"],"sourcesContent":["import {\n Bell,\n Building2,\n ChartColumn,\n CircleCheck,\n CircleHelp,\n CreditCard,\n FileText,\n Scale,\n Sparkles,\n SlidersHorizontal,\n ToggleLeft,\n UserRound,\n Users,\n type LucideIcon,\n} from 'lucide-react';\nimport type { PermissionKey } from '@withpotter/lib/use-permissions';\n\n/**\n * The rows of the settings window, in the order they are drawn.\n *\n * One list for the whole window: yours and the business's, mixed, because the\n * person looking for \"Notifications\" or \"Language\" should not have to know\n * whose setting it is. Rows that leave the window (`external`) carry an arrow.\n *\n * `paths` are the addresses a row owns. A link to any of them, or to anything\n * below them (a team member, a credits page), opens the window on that row.\n * `keywords` are what search also matches: the names of the sections inside a\n * row, so \"passkeys\" or \"time zone\" finds the row that holds them.\n */\nexport type SettingsRowId =\n | 'general'\n | 'features'\n | 'account'\n | 'team'\n | 'notifications'\n | 'demo-content'\n | 'preferences'\n | 'payment-approvals'\n | 'billing'\n | 'usage'\n | 'policies'\n | 'legal';\n\nexport interface SettingsRow {\n id: SettingsRowId;\n name: string;\n icon: LucideIcon;\n paths: string[];\n keywords: string[];\n /** Hidden from a member who lacks every one of these. Empty: everyone. */\n permission?: PermissionKey[];\n /**\n * A page this row leaves the window for, where a host has its own screen for\n * the subject. The row then is a link, and the panel is never drawn.\n */\n href?: string;\n}\n\nconst BILLING_ROW: SettingsRow = {\n id: 'billing',\n name: 'Billing & plan',\n icon: CreditCard,\n paths: ['/dashboard/settings/billing'],\n keywords: ['plan', 'invoice', 'credits', 'top-up', 'cancel'],\n permission: ['canManageSettings'],\n};\n\n// A box whose card payments wait for a second person lists the decisions.\nconst PAYMENT_APPROVALS_ROW: SettingsRow = {\n id: 'payment-approvals',\n name: 'Payment approvals',\n icon: CircleCheck,\n paths: ['/dashboard/settings/payment-approvals'],\n keywords: ['approval', 'accepted', 'rejected', 'decisions', 'history'],\n};\n\nconst DEMO_CONTENT_ROW: SettingsRow = {\n // Sample products, pages and pictures to start with, clear, or add again.\n // No permission gate, like the sidebar entry: everyone sees the state and\n // the card itself shows the actions only to the owner and admins.\n id: 'demo-content',\n name: 'Demo content',\n icon: Sparkles,\n paths: ['/dashboard/settings/demo-content'],\n keywords: ['sample', 'products', 'starter', 'clear', 'example data'],\n};\n\nexport const SETTINGS_ROWS: SettingsRow[] = [\n {\n id: 'general',\n name: 'General',\n icon: Building2,\n paths: [\n '/dashboard/settings',\n '/dashboard/settings/branding',\n '/dashboard/settings/regional',\n ],\n keywords: ['business name', 'branding', 'logo', 'colours', 'numbering'],\n permission: ['canManageBusinessProfile', 'canManageSettings'],\n },\n {\n // The switches no business type implies: Blog today. Next to General\n // because it is the business's own setting, before the rows that are\n // about the person (Account, Preferences).\n id: 'features',\n name: 'Features',\n icon: ToggleLeft,\n paths: ['/dashboard/settings/features'],\n keywords: ['blog', 'posts', 'write', 'publish', 'switch', 'website', 'features'],\n permission: ['canManageSettings'],\n },\n {\n id: 'account',\n name: 'Account',\n icon: UserRound,\n paths: [\n '/dashboard/settings/profile',\n '/dashboard/settings/devices',\n '/dashboard/settings/data-privacy',\n ],\n keywords: [\n 'profile',\n 'name',\n 'email',\n 'photo',\n 'security',\n 'password',\n 'passkeys',\n 'two-factor',\n 'sessions',\n 'devices',\n 'privacy',\n 'export',\n 'close account',\n ],\n },\n {\n id: 'team',\n name: 'Team & permissions',\n icon: Users,\n paths: [\n '/dashboard/settings/team',\n '/dashboard/settings/roles',\n '/dashboard/settings/activity',\n ],\n keywords: ['people', 'invite', 'roles', 'activity', 'seats'],\n permission: ['canManageTeam'],\n // A box with its own team screen (its roles and invitations come from\n // its engine, which does not serve this panel's routes) names it here.\n href: process.env.NEXT_PUBLIC_BOX_TEAM_PAGE || undefined,\n },\n {\n id: 'notifications',\n name: 'Notifications',\n icon: Bell,\n paths: ['/dashboard/settings/notifications'],\n keywords: ['alerts', 'low stock', 'email sender', 'digest', 'reminders'],\n },\n DEMO_CONTENT_ROW,\n {\n id: 'preferences',\n name: 'Preferences',\n icon: SlidersHorizontal,\n paths: [\n '/dashboard/settings/language',\n '/dashboard/settings/accessibility',\n ],\n keywords: [\n 'appearance',\n 'theme',\n 'dark',\n 'light',\n 'language',\n 'time zone',\n 'date format',\n 'accessibility',\n 'high contrast',\n 'reduce motion',\n 'text size',\n ],\n },\n ...(process.env.NEXT_PUBLIC_BOX_PAYMENT_APPROVALS === 'true'\n ? [PAYMENT_APPROVALS_ROW]\n : []),\n // A licensed box that bills outside the console hides the row.\n ...(process.env.NEXT_PUBLIC_BOX_HIDE_BILLING === 'true' ? [] : [BILLING_ROW]),\n // A box that meters nothing in the console leaves Usage out.\n ...(process.env.NEXT_PUBLIC_BOX_HIDE_USAGE === 'true'\n ? []\n : [\n {\n id: 'usage' as const,\n name: 'Usage',\n icon: ChartColumn,\n paths: ['/dashboard/settings/usage'],\n keywords: ['credits', 'delivery', 'spend'],\n permission: ['canManageSettings' as const],\n },\n ]),\n {\n id: 'policies',\n name: 'Policies',\n icon: Scale,\n paths: ['/dashboard/settings/policies'],\n keywords: ['returns', 'shipping', 'privacy', 'terms'],\n permission: ['canManageSettings'],\n },\n {\n id: 'legal',\n name: 'Legal',\n icon: FileText,\n paths: ['/dashboard/settings/legal'],\n keywords: ['terms', 'privacy policy', 'licences'],\n },\n];\n\n/** Where \"Get help\" goes. It leaves the window, so it is not a row above. */\nexport const HELP_HREF = '/dashboard/help';\nexport const HELP_ICON = CircleHelp;\n\nconst pathOf = (full: string) => full.split(/[?#]/)[0];\n\n/**\n * The row that owns an address: the longest of its paths that equals the\n * address or is a parent of it. General's own address is a prefix of every\n * other settings address, so it only owns itself.\n */\nexport function rowForHref(href: string | null | undefined): SettingsRowId {\n const path = pathOf(href ?? '/dashboard/settings');\n let best: { id: SettingsRowId; len: number } | null = null;\n for (const row of SETTINGS_ROWS) {\n for (const p of row.paths) {\n const owns =\n path === p || (p !== '/dashboard/settings' && path.startsWith(`${p}/`));\n if (owns && (!best || p.length > best.len))\n best = { id: row.id, len: p.length };\n }\n }\n return best?.id ?? 'general';\n}\n\n/** Rows whose name or inner sections match what was typed. */\nexport function matchRows<T extends { name: string; keywords: string[] }>(\n rows: T[],\n query: string,\n): T[] {\n const q = query.trim().toLowerCase();\n if (!q) return rows;\n return rows.filter(\n (r) =>\n r.name.toLowerCase().includes(q) || r.keywords.some((k) => k.includes(q)),\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/shell/settings-rows.ts"],"sourcesContent":["import {\n Bell,\n Building2,\n ChartColumn,\n CircleCheck,\n CircleHelp,\n CreditCard,\n FileText,\n Lock,\n ReceiptText,\n Scale,\n Sparkles,\n SlidersHorizontal,\n UserRound,\n Users,\n type LucideIcon,\n} from 'lucide-react';\nimport type { PermissionKey } from '@withpotter/lib/use-permissions';\n\n/**\n * The rows of the settings window, in the order they are drawn.\n *\n * One list for the whole window: yours and the business's, mixed, because the\n * person looking for \"Notifications\" or \"Language\" should not have to know\n * whose setting it is. Rows that leave the window (`external`) carry an arrow.\n *\n * `paths` are the addresses a row owns. A link to any of them, or to anything\n * below them (a team member, a credits page), opens the window on that row.\n * `keywords` are what search also matches: the names of the sections inside a\n * row, so \"passkeys\" or \"time zone\" finds the row that holds them.\n */\nexport type SettingsRowId =\n | 'general'\n | 'extras'\n | 'profile'\n | 'security'\n | 'team'\n | 'notifications'\n | 'preferences'\n | 'payment-approvals'\n | 'billing'\n | 'taxes'\n | 'usage'\n | 'policies'\n | 'legal';\n\nexport interface SettingsRow {\n id: SettingsRowId;\n name: string;\n /** The line under the title when the row is open. */\n subtitle?: string;\n icon: LucideIcon;\n paths: string[];\n keywords: string[];\n /** Hidden from a member who lacks every one of these. Empty: everyone. */\n permission?: PermissionKey[];\n /**\n * A page this row leaves the window for, where a host has its own screen for\n * the subject. The row then is a link, and the panel is never drawn.\n */\n href?: string;\n}\n\nconst BILLING_ROW: SettingsRow = {\n id: 'billing',\n name: 'Billing & plan',\n subtitle: 'Your plan, how you pay, and your payments.',\n icon: CreditCard,\n paths: ['/dashboard/settings/billing'],\n keywords: ['plan', 'invoice', 'credits', 'top-up', 'cancel'],\n permission: ['canManageSettings'],\n};\n\nconst TAXES_ROW: SettingsRow = {\n id: 'taxes',\n name: 'Taxes',\n subtitle: 'What you charge, and the number shown on receipts.',\n icon: ReceiptText,\n paths: ['/dashboard/settings/taxes'],\n keywords: ['vat', 'tax rate', 'registration number', 'receipts'],\n permission: ['canManageSettings'],\n};\n\n// A box whose card payments wait for a second person lists the decisions.\nconst PAYMENT_APPROVALS_ROW: SettingsRow = {\n id: 'payment-approvals',\n name: 'Payment approvals',\n subtitle: 'Who approves money going out.',\n icon: CircleCheck,\n paths: ['/dashboard/settings/payment-approvals'],\n keywords: ['approval', 'accepted', 'rejected', 'decisions', 'history'],\n};\n\nexport const SETTINGS_ROWS: SettingsRow[] = [\n {\n id: 'general',\n name: 'General',\n subtitle: 'Your business details and store address.',\n icon: Building2,\n paths: [\n '/dashboard/settings',\n '/dashboard/settings/branding',\n '/dashboard/settings/regional',\n ],\n keywords: ['business name', 'branding', 'logo', 'colours', 'numbering'],\n permission: ['canManageBusinessProfile', 'canManageSettings'],\n },\n // A licensed box that bills outside the console hides the row.\n ...(process.env.NEXT_PUBLIC_BOX_HIDE_BILLING === 'true' ? [] : [BILLING_ROW]),\n // A box that keeps a tax (VAT) setting has it as its own row, after the plan.\n ...(process.env.NEXT_PUBLIC_BOX_TAXES_IN_POLICIES === 'true' ? [TAXES_ROW] : []),\n {\n id: 'team',\n name: 'Team & permissions',\n subtitle: 'Who can do what in this business.',\n icon: Users,\n paths: [\n '/dashboard/settings/team',\n '/dashboard/settings/roles',\n '/dashboard/settings/activity',\n ],\n keywords: ['people', 'invite', 'roles', 'activity', 'seats'],\n permission: ['canManageTeam'],\n // A box with its own team screen (its roles and invitations come from\n // its engine, which does not serve this panel's routes) names it here.\n href: process.env.NEXT_PUBLIC_BOX_TEAM_PAGE || undefined,\n },\n ...(process.env.NEXT_PUBLIC_BOX_PAYMENT_APPROVALS === 'true'\n ? [PAYMENT_APPROVALS_ROW]\n : []),\n {\n id: 'policies',\n name: 'Policies',\n subtitle: 'What your customers agree to.',\n icon: Scale,\n paths: ['/dashboard/settings/policies'],\n keywords: ['returns', 'shipping', 'privacy', 'terms'],\n permission: ['canManageSettings'],\n },\n {\n // The switches no business type implies (Blog today) and the sample\n // content to start with, clear, or add again. One row for both. No\n // permission gate on the row, like the Demo content entry in the sidebar:\n // everyone sees the state, the panel shows the switches only to those who\n // may change them, and the demo card shows its actions only to the owner\n // and admins.\n id: 'extras',\n name: 'Extras',\n subtitle: 'Switches and sample content for your store.',\n icon: Sparkles,\n paths: [\n '/dashboard/settings/features',\n '/dashboard/settings/demo-content',\n ],\n keywords: [\n 'blog',\n 'posts',\n 'write',\n 'publish',\n 'switch',\n 'website',\n 'features',\n 'sample',\n 'products',\n 'starter',\n 'clear',\n 'example data',\n 'demo content',\n ],\n },\n {\n id: 'profile',\n name: 'Profile',\n subtitle: 'Your name and how we reach you.',\n icon: UserRound,\n paths: ['/dashboard/settings/profile'],\n keywords: ['name', 'email', 'photo', 'phone', 'close account'],\n },\n {\n id: 'security',\n name: 'Security',\n subtitle: 'How you sign in, and where you are signed in.',\n icon: Lock,\n paths: [\n '/dashboard/settings/devices',\n '/dashboard/settings/data-privacy',\n ],\n keywords: [\n 'password',\n 'passkeys',\n 'two-factor',\n 'sessions',\n 'devices',\n 'sign in',\n 'privacy',\n 'export',\n ],\n },\n {\n id: 'notifications',\n name: 'Notifications',\n subtitle: 'What we tell you, and what your customers hear from you.',\n icon: Bell,\n paths: ['/dashboard/settings/notifications'],\n keywords: ['alerts', 'low stock', 'email sender', 'digest', 'reminders'],\n },\n {\n id: 'preferences',\n name: 'Preferences',\n subtitle: 'How the dashboard looks and behaves for you.',\n icon: SlidersHorizontal,\n paths: [\n '/dashboard/settings/language',\n '/dashboard/settings/accessibility',\n ],\n keywords: [\n 'appearance',\n 'theme',\n 'dark',\n 'light',\n 'language',\n 'time zone',\n 'date format',\n 'accessibility',\n 'high contrast',\n 'reduce motion',\n 'text size',\n ],\n },\n // A box that meters nothing in the console leaves Usage out.\n ...(process.env.NEXT_PUBLIC_BOX_HIDE_USAGE === 'true'\n ? []\n : [\n {\n id: 'usage' as const,\n name: 'Usage',\n subtitle: 'What you have used this month.',\n icon: ChartColumn,\n paths: ['/dashboard/settings/usage'],\n keywords: ['credits', 'delivery', 'spend'],\n permission: ['canManageSettings' as const],\n },\n ]),\n {\n id: 'legal',\n name: 'Legal',\n subtitle: 'Your terms and privacy pages.',\n icon: FileText,\n paths: ['/dashboard/settings/legal'],\n keywords: ['terms', 'privacy policy', 'licences'],\n },\n];\n\n/** Where \"Get help\" goes. It leaves the window, so it is not a row above. */\nexport const HELP_HREF = '/dashboard/help';\nexport const HELP_ICON = CircleHelp;\n\nconst pathOf = (full: string) => full.split(/[?#]/)[0];\n\n/**\n * The row that owns an address: the longest of its paths that equals the\n * address or is a parent of it. General's own address is a prefix of every\n * other settings address, so it only owns itself.\n */\nexport function rowForHref(href: string | null | undefined): SettingsRowId {\n const path = pathOf(href ?? '/dashboard/settings');\n let best: { id: SettingsRowId; len: number } | null = null;\n for (const row of SETTINGS_ROWS) {\n for (const p of row.paths) {\n const owns =\n path === p || (p !== '/dashboard/settings' && path.startsWith(`${p}/`));\n if (owns && (!best || p.length > best.len))\n best = { id: row.id, len: p.length };\n }\n }\n return best?.id ?? 'general';\n}\n\n/** Rows whose name or inner sections match what was typed. */\nexport function matchRows<T extends { name: string; keywords: string[] }>(\n rows: T[],\n query: string,\n): T[] {\n const q = query.trim().toLowerCase();\n if (!q) return rows;\n return rows.filter(\n (r) =>\n r.name.toLowerCase().includes(q) || r.keywords.some((k) => k.includes(q)),\n );\n}\n"],"mappings":"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","names":[]}
|
package/dist/shell/shell.css
CHANGED
|
@@ -2905,3 +2905,44 @@
|
|
|
2905
2905
|
transition: none;
|
|
2906
2906
|
}
|
|
2907
2907
|
}
|
|
2908
|
+
|
|
2909
|
+
/* ===================== Settings window (the modal) =====================
|
|
2910
|
+
* One named variant: the window's own palette, so its rail, rows and panels draw the same values everywhere
|
|
2911
|
+
* (the prototype's warm frame). Light takes the prototype's values; dark falls back to the theme tokens so the window
|
|
2912
|
+
* still follows the theme. Panels inside the window read the same variables, with their own fallbacks outside it. */
|
|
2913
|
+
.st-modal {
|
|
2914
|
+
--st-rail: #f6f6f3;
|
|
2915
|
+
--st-rail-line: #ecece8;
|
|
2916
|
+
--st-rail-rule: #e6e6e2;
|
|
2917
|
+
--st-ink-2: #4a4a52;
|
|
2918
|
+
--st-ink-3: #6b6b72;
|
|
2919
|
+
--st-ink-4: #8b8b93;
|
|
2920
|
+
--st-row-hover: rgba(0, 0, 0, 0.045);
|
|
2921
|
+
--st-row-on-ring: 0 0 0 1px rgba(0, 0, 29, 0.06), 0 1px 3px rgba(0, 0, 0, 0.06);
|
|
2922
|
+
--st-search-ring: 0 0 0 1px rgba(0, 0, 29, 0.07);
|
|
2923
|
+
--st-card: #f6f6f3;
|
|
2924
|
+
--st-card-ring: 0 0 0 1px rgba(0, 0, 29, 0.05);
|
|
2925
|
+
--st-pick: #f3faf6;
|
|
2926
|
+
--st-pick-ring: 0 0 0 1px rgba(63, 156, 122, 0.45);
|
|
2927
|
+
--st-pick-dot: #3f9c7a;
|
|
2928
|
+
--st-chip: #f1f1ee;
|
|
2929
|
+
--st-rule: #eeeeea;
|
|
2930
|
+
}
|
|
2931
|
+
.dark .st-modal {
|
|
2932
|
+
--st-rail: var(--bg-muted);
|
|
2933
|
+
--st-rail-line: var(--border-subtle);
|
|
2934
|
+
--st-rail-rule: var(--border-subtle);
|
|
2935
|
+
--st-ink-2: var(--text-secondary);
|
|
2936
|
+
--st-ink-3: var(--text-tertiary);
|
|
2937
|
+
--st-ink-4: var(--text-tertiary);
|
|
2938
|
+
--st-row-hover: var(--interactive-hover);
|
|
2939
|
+
--st-row-on-ring: 0 0 0 1px var(--border-subtle);
|
|
2940
|
+
--st-search-ring: 0 0 0 1px var(--border-subtle);
|
|
2941
|
+
--st-card: var(--bg-muted);
|
|
2942
|
+
--st-card-ring: 0 0 0 1px var(--border-subtle);
|
|
2943
|
+
--st-pick: color-mix(in srgb, var(--pv-accent, #3f9c7a) 10%, var(--bg-base));
|
|
2944
|
+
--st-pick-ring: 0 0 0 1px color-mix(in srgb, #3f9c7a 55%, transparent);
|
|
2945
|
+
--st-pick-dot: #3f9c7a;
|
|
2946
|
+
--st-chip: var(--bg-muted);
|
|
2947
|
+
--st-rule: var(--border-subtle);
|
|
2948
|
+
}
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { Fragment, useEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import Link from "next/link";
|
|
5
|
-
import {
|
|
5
|
+
import { ArrowUpRight, ChevronLeft, Search, X } from "lucide-react";
|
|
6
6
|
import { useAnimate, useReducedMotion } from "motion/react";
|
|
7
7
|
import { BusinessMark } from "@withpotter/ui-kit/business-mark";
|
|
8
|
-
import { Dialog, DialogContent, DialogTitle } from "@withpotter/ui-kit/dialog";
|
|
8
|
+
import { Dialog, DialogClose, DialogContent, DialogTitle } from "@withpotter/ui-kit/dialog";
|
|
9
9
|
import { Input } from "@withpotter/ui-kit/input";
|
|
10
10
|
import { TextMorph } from "@withpotter/ui-kit/text-morph";
|
|
11
11
|
import {
|
|
@@ -28,7 +28,9 @@ import {
|
|
|
28
28
|
matchRows,
|
|
29
29
|
rowForHref
|
|
30
30
|
} from "./settings-rows";
|
|
31
|
-
const ROW_CLASS = "flex w-full items-center gap-
|
|
31
|
+
const ROW_CLASS = "mb-px flex h-[34px] w-full items-center gap-[11px] rounded-[9px] px-2 text-left text-[13.5px] font-medium leading-5 transition-colors";
|
|
32
|
+
const ROW_IDLE = "text-[var(--st-ink-2)] hover:bg-[var(--st-row-hover)] hover:text-[var(--text-primary)]";
|
|
33
|
+
const ROW_ON = "bg-[var(--bg-base)] text-[var(--text-primary)] shadow-[var(--st-row-on-ring)]";
|
|
32
34
|
function UnifiedSettingsModal({
|
|
33
35
|
open,
|
|
34
36
|
href,
|
|
@@ -57,6 +59,8 @@ function UnifiedSettingsModal({
|
|
|
57
59
|
}, own);
|
|
58
60
|
}, [can, loading, host]);
|
|
59
61
|
const shown = matchRows(rows, query);
|
|
62
|
+
const railRows = shown.filter((r) => r.id !== "legal");
|
|
63
|
+
const legalRow = shown.find((r) => r.id === "legal");
|
|
60
64
|
const drill = useMemo(
|
|
61
65
|
() => ({
|
|
62
66
|
view,
|
|
@@ -90,7 +94,8 @@ function UnifiedSettingsModal({
|
|
|
90
94
|
DialogContent,
|
|
91
95
|
{
|
|
92
96
|
"aria-label": "Settings",
|
|
93
|
-
|
|
97
|
+
showClose: false,
|
|
98
|
+
className: "st-modal flex h-[min(640px,calc(100dvh-2.5rem))] w-[calc(100%-2.5rem)] max-w-[920px] gap-0 overflow-hidden rounded-2xl border-0 p-0 text-[13.5px] shadow-[0_24px_60px_-20px_rgba(18,18,34,0.45),0_0_0_1px_rgba(0,0,0,0.04)]",
|
|
94
99
|
children: [
|
|
95
100
|
/* @__PURE__ */ jsxs(
|
|
96
101
|
"nav",
|
|
@@ -107,116 +112,152 @@ function UnifiedSettingsModal({
|
|
|
107
112
|
const next = e.key === "ArrowDown" ? (at + 1) % rowsEls.length : (at - 1 + rowsEls.length) % rowsEls.length;
|
|
108
113
|
rowsEls[next].focus();
|
|
109
114
|
},
|
|
110
|
-
className: "flex w-[
|
|
115
|
+
className: "flex w-[248px] shrink-0 flex-col overflow-hidden border-r border-[var(--st-rail-line)] bg-[var(--st-rail)] px-3 pb-2.5 pt-4",
|
|
111
116
|
children: [
|
|
112
|
-
name && /* @__PURE__ */ jsxs("div", { className: "
|
|
117
|
+
name && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5 px-1.5 pb-3.5 pt-0.5", children: [
|
|
113
118
|
/* @__PURE__ */ jsx(BusinessMark, { name, src: tenant?.businessLogo, size: 34 }),
|
|
114
119
|
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
115
|
-
/* @__PURE__ */ jsx("p", { className: "truncate text-
|
|
116
|
-
planLabel && /* @__PURE__ */ jsx("p", { className: "truncate text-
|
|
120
|
+
/* @__PURE__ */ jsx("p", { className: "max-w-[150px] truncate text-[14px] font-semibold leading-[18px] text-[var(--text-primary)]", children: name }),
|
|
121
|
+
planLabel && /* @__PURE__ */ jsx("p", { className: "truncate text-[12px] leading-4 text-[var(--st-ink-3)]", children: planLabel })
|
|
117
122
|
] })
|
|
118
123
|
] }),
|
|
119
|
-
/* @__PURE__ */ jsxs("div", { className: "mb-
|
|
120
|
-
/* @__PURE__ */ jsx(Search, { className: "size-
|
|
124
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-1.5 flex h-8 shrink-0 items-center gap-2 rounded-[10px] bg-[var(--bg-base)] px-2.5 text-[var(--st-ink-3)] shadow-[var(--st-search-ring)] focus-within:text-[var(--text-primary)]", children: [
|
|
125
|
+
/* @__PURE__ */ jsx(Search, { className: "size-[15px] shrink-0", strokeWidth: 1.8 }),
|
|
121
126
|
/* @__PURE__ */ jsx(
|
|
122
127
|
Input,
|
|
123
128
|
{
|
|
124
129
|
variant: "bare",
|
|
125
130
|
value: query,
|
|
126
131
|
onChange: (e) => setQuery(e.target.value),
|
|
127
|
-
placeholder: "Search",
|
|
132
|
+
placeholder: "Search settings",
|
|
128
133
|
"aria-label": "Search settings",
|
|
129
134
|
className: "min-w-0 flex-1"
|
|
130
135
|
}
|
|
131
136
|
)
|
|
132
137
|
] }),
|
|
133
|
-
|
|
134
|
-
"
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
/* @__PURE__ */
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
138
|
+
/* @__PURE__ */ jsxs("div", { className: "-mx-1 min-h-0 flex-1 overflow-y-auto px-1", children: [
|
|
139
|
+
shown.length === 0 && /* @__PURE__ */ jsxs("p", { className: "px-2 py-2 text-[13px] text-[var(--st-ink-3)]", children: [
|
|
140
|
+
"Nothing matches \u201C",
|
|
141
|
+
query,
|
|
142
|
+
"\u201D."
|
|
143
|
+
] }),
|
|
144
|
+
groupRows(railRows).map((group) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
145
|
+
/* @__PURE__ */ jsx("p", { className: "px-2 pb-1.5 pt-3.5 text-[12px] font-medium leading-5 text-[var(--st-ink-4)]", children: group.label }),
|
|
146
|
+
group.rows.map((r) => {
|
|
147
|
+
const selected = r.id === current?.id;
|
|
148
|
+
const Icon = r.icon;
|
|
149
|
+
if (r.href) {
|
|
150
|
+
return /* @__PURE__ */ jsxs(
|
|
151
|
+
Link,
|
|
152
|
+
{
|
|
153
|
+
"data-row": true,
|
|
154
|
+
href: r.href,
|
|
155
|
+
onClick: onClose,
|
|
156
|
+
className: `${ROW_CLASS} ${ROW_IDLE}`,
|
|
157
|
+
children: [
|
|
158
|
+
/* @__PURE__ */ jsx(Icon, { className: "size-4 shrink-0", strokeWidth: 1.75 }),
|
|
159
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: r.name }),
|
|
160
|
+
/* @__PURE__ */ jsx(ArrowUpRight, { className: "ml-auto size-[13px] shrink-0 opacity-[.55]" })
|
|
161
|
+
]
|
|
162
|
+
},
|
|
163
|
+
r.id
|
|
164
|
+
);
|
|
165
|
+
}
|
|
144
166
|
return /* @__PURE__ */ jsxs(
|
|
145
|
-
|
|
167
|
+
"button",
|
|
146
168
|
{
|
|
147
169
|
"data-row": true,
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
170
|
+
type: "button",
|
|
171
|
+
"aria-current": selected ? "page" : void 0,
|
|
172
|
+
onClick: () => {
|
|
173
|
+
setPicked(r.id);
|
|
174
|
+
setView(null);
|
|
175
|
+
},
|
|
176
|
+
className: `${ROW_CLASS} ${selected ? ROW_ON : ROW_IDLE}`,
|
|
151
177
|
children: [
|
|
152
|
-
/* @__PURE__ */ jsx(Icon, { className: "size-
|
|
178
|
+
/* @__PURE__ */ jsx(Icon, { className: "size-4 shrink-0", strokeWidth: 1.75 }),
|
|
153
179
|
/* @__PURE__ */ jsx("span", { className: "truncate", children: r.name })
|
|
154
180
|
]
|
|
155
181
|
},
|
|
156
182
|
r.id
|
|
157
183
|
);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
/* @__PURE__ */ jsx(Icon, { className: "size-[18px] shrink-0" }),
|
|
172
|
-
/* @__PURE__ */ jsx("span", { className: "truncate", children: r.name })
|
|
173
|
-
]
|
|
184
|
+
})
|
|
185
|
+
] }, group.key))
|
|
186
|
+
] }),
|
|
187
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-1.5 shrink-0 border-t border-[var(--st-rail-rule)] pt-2", children: [
|
|
188
|
+
legalRow && /* @__PURE__ */ jsxs(
|
|
189
|
+
"button",
|
|
190
|
+
{
|
|
191
|
+
type: "button",
|
|
192
|
+
"data-row": true,
|
|
193
|
+
"aria-current": legalRow.id === current?.id ? "page" : void 0,
|
|
194
|
+
onClick: () => {
|
|
195
|
+
setPicked(legalRow.id);
|
|
196
|
+
setView(null);
|
|
174
197
|
},
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
198
|
+
className: `${ROW_CLASS} ${legalRow.id === current?.id ? ROW_ON : ROW_IDLE}`,
|
|
199
|
+
children: [
|
|
200
|
+
/* @__PURE__ */ jsx(legalRow.icon, { className: "size-4 shrink-0", strokeWidth: 1.75 }),
|
|
201
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: legalRow.name })
|
|
202
|
+
]
|
|
203
|
+
}
|
|
204
|
+
),
|
|
205
|
+
/* @__PURE__ */ jsxs(
|
|
206
|
+
Link,
|
|
207
|
+
{
|
|
208
|
+
href: HELP_HREF,
|
|
209
|
+
onClick: onClose,
|
|
210
|
+
className: `${ROW_CLASS} ${ROW_IDLE}`,
|
|
211
|
+
children: [
|
|
212
|
+
/* @__PURE__ */ jsx(HelpIcon, { className: "size-4 shrink-0", strokeWidth: 1.75 }),
|
|
213
|
+
/* @__PURE__ */ jsx("span", { children: "Get help" }),
|
|
214
|
+
/* @__PURE__ */ jsx(ArrowUpRight, { className: "ml-auto size-[13px] shrink-0" })
|
|
215
|
+
]
|
|
216
|
+
}
|
|
217
|
+
)
|
|
218
|
+
] })
|
|
192
219
|
]
|
|
193
220
|
}
|
|
194
221
|
),
|
|
195
222
|
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [
|
|
196
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-
|
|
197
|
-
/* @__PURE__ */ jsxs("div", { className: "
|
|
198
|
-
view && /* @__PURE__ */
|
|
223
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-none items-start justify-between pb-4 pl-10 pr-6 pt-5", children: [
|
|
224
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
225
|
+
view && /* @__PURE__ */ jsxs(
|
|
199
226
|
"button",
|
|
200
227
|
{
|
|
201
228
|
type: "button",
|
|
202
229
|
onClick: drill.back,
|
|
203
230
|
"aria-label": `Back to ${current?.name ?? "Settings"}`,
|
|
204
|
-
className: "
|
|
205
|
-
children:
|
|
231
|
+
className: "-ml-0.5 mb-1.5 inline-flex items-center gap-1 text-[13px] font-medium text-[var(--st-ink-2)] transition-colors hover:text-[var(--text-primary)]",
|
|
232
|
+
children: [
|
|
233
|
+
/* @__PURE__ */ jsx(ChevronLeft, { className: "size-[15px]", strokeWidth: 2 }),
|
|
234
|
+
current?.name ?? "Settings"
|
|
235
|
+
]
|
|
206
236
|
}
|
|
207
237
|
),
|
|
208
238
|
/* @__PURE__ */ jsx(
|
|
209
239
|
DialogTitle,
|
|
210
240
|
{
|
|
211
241
|
"aria-label": view?.title ?? current?.name ?? "Settings",
|
|
212
|
-
className: "truncate text-
|
|
242
|
+
className: "truncate text-[18px] font-semibold leading-6 tracking-[-0.01em] text-[var(--text-primary)]",
|
|
213
243
|
children: /* @__PURE__ */ jsx(TextMorph, { as: "span", children: view?.title ?? current?.name ?? "Settings" })
|
|
214
244
|
}
|
|
215
|
-
)
|
|
245
|
+
),
|
|
246
|
+
!view && current?.subtitle && /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-[13px] leading-[18px] text-[var(--st-ink-3)]", children: current.subtitle })
|
|
216
247
|
] }),
|
|
217
|
-
/* @__PURE__ */
|
|
248
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5", children: [
|
|
249
|
+
/* @__PURE__ */ jsx("div", { ref: setStatusSlot, className: "flex items-center" }),
|
|
250
|
+
/* @__PURE__ */ jsx(
|
|
251
|
+
DialogClose,
|
|
252
|
+
{
|
|
253
|
+
"aria-label": "Close",
|
|
254
|
+
className: "grid size-8 place-items-center rounded-[9px] text-[var(--st-ink-2)] transition-colors hover:bg-[var(--st-row-hover)] hover:text-[var(--text-primary)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--focus-ring)]",
|
|
255
|
+
children: /* @__PURE__ */ jsx(X, { className: "size-[18px]", strokeWidth: 2 })
|
|
256
|
+
}
|
|
257
|
+
)
|
|
258
|
+
] })
|
|
218
259
|
] }),
|
|
219
|
-
/* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto px-
|
|
260
|
+
/* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto px-6 pb-7 outline-none", children: /* @__PURE__ */ jsx("div", { ref: scope, children: current && /* @__PURE__ */ jsx(SettingsDrillProvider, { value: drill, children: /* @__PURE__ */ jsx(SettingsSurface, { variant: "plain", children: /* @__PURE__ */ jsx(SettingsStatusSlot, { element: statusSlot, children: current.render ? current.render() : /* @__PURE__ */ jsx(
|
|
220
261
|
SettingsPanel,
|
|
221
262
|
{
|
|
222
263
|
id: current.id
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/shell/unified-settings-modal.tsx"],"sourcesContent":["'use client';\n\nimport { Fragment, useEffect, useMemo, useRef, useState, type ReactNode } from 'react';\nimport Link from 'next/link';\nimport { ArrowLeft, ArrowUpRight, Search } from 'lucide-react';\nimport { useAnimate, useReducedMotion } from 'motion/react';\nimport { BusinessMark } from '@withpotter/ui-kit/business-mark';\nimport { Dialog, DialogContent, DialogTitle } from '@withpotter/ui-kit/dialog';\nimport { Input } from '@withpotter/ui-kit/input';\nimport { TextMorph } from '@withpotter/ui-kit/text-morph';\nimport {\n SettingsStatusSlot,\n SettingsSurface,\n} from '@withpotter/pages-core/dashboard/settings/_components/settings-ui';\nimport {\n SettingsDrillProvider,\n type SettingsDrill,\n} from '@withpotter/pages-core/dashboard/settings/_components/settings-drill';\nimport { useAuth } from '@withpotter/lib/auth-context';\nimport { usePermissions } from '@withpotter/lib/use-permissions';\nimport { SettingsPanel } from './settings-panels';\nimport { groupRows } from './settings-groups';\nimport { useSidebarCards } from './sidebar-cards';\nimport { hostRowForHref, useSettingsHostRows } from './settings-host';\nimport {\n HELP_HREF,\n HELP_ICON,\n SETTINGS_ROWS,\n matchRows,\n rowForHref,\n type SettingsRowId,\n} from './settings-rows';\n\n/** A row of the window: the shared ones, or one a host added with the screen to draw. */\ntype ShownRow = Omit<(typeof SETTINGS_ROWS)[number], 'id'> & {\n id: string;\n render?: () => ReactNode;\n};\n\nconst ROW_CLASS =\n 'flex w-full items-center gap-3 rounded-lg px-3 py-3 text-left text-sm font-medium transition-colors';\n\n/**\n * Settings, yours and the business's, in one window.\n *\n * It is the account modal's shell (a rail on the left, the page on the right):\n * the business at the top of the rail with the plan it is on when the host says,\n * then the rows in two groups, Business and You. A search matches row names and the\n * sections inside them, so \"passkeys\" or \"time zone\" finds its row. Rows the\n * member may not open are not drawn. Get help leaves the window, so it carries\n * an arrow.\n *\n * It replaces the window that showed each page in a frame: the pages are drawn\n * here directly, so there is no second copy of the app running behind it.\n */\nexport function UnifiedSettingsModal({\n open,\n href,\n onClose,\n}: {\n open: boolean;\n /** A settings address to open on. A row's own page when it owns it. */\n href?: string | null;\n onClose: () => void;\n}) {\n const { can, loading } = usePermissions();\n const [query, setQuery] = useState('');\n // A level inside the open row (a policy's editor). Cleared when the row, or\n // the address the window was opened on, changes.\n const [view, setView] = useState<SettingsDrill['view']>(null);\n // The row chosen inside the window, and the address it was opened on, so\n // opening on a different address moves the selection (adjusted in render).\n const [picked, setPicked] = useState<string | null>(null);\n const [seen, setSeen] = useState<string | null | undefined>(undefined);\n if (open && seen !== href) {\n setSeen(href);\n setPicked(null);\n setQuery('');\n setView(null);\n }\n\n const host = useSettingsHostRows();\n const rows = useMemo(() => {\n const own: ShownRow[] = SETTINGS_ROWS.filter(\n (r) => !r.permission || loading || r.permission.some((p) => can(p)),\n );\n\n // A host's rows go where they say, or after General.\n return host.reduce<ShownRow[]>((list, extra) => {\n const at = list.findIndex((r) => r.id === (extra.after ?? 'general'));\n\n const into = at === -1 ? list.length : at + 1;\n\n return [...list.slice(0, into), extra, ...list.slice(into)];\n }, own);\n }, [can, loading, host]);\n const shown = matchRows(rows, query);\n\n const drill = useMemo<SettingsDrill>(\n () => ({\n view,\n open: (key, title) => setView({ key, title }),\n openRow: (row, key, title) => {\n setPicked(row as SettingsRowId);\n setView({ key, title });\n },\n back: () => setView(null),\n }),\n [view],\n );\n\n const wanted = picked ?? hostRowForHref(host, href)?.id ?? rowForHref(href);\n // An address that names a row the member cannot see falls to their first.\n const current = rows.find((r) => r.id === wanted) ?? rows[0];\n const HelpIcon = HELP_ICON;\n\n const [statusSlot, setStatusSlot] = useState<HTMLElement | null>(null);\n\n // The business the window is about, and the plan it is on when the host says (Olive does; other products say nothing).\n const { tenant } = useAuth();\n const planLabel = useSidebarCards()?.planLabel;\n const name = tenant?.businessName?.trim() ?? '';\n\n // Moving to another row, or into and out of a level inside one, brings the content in: it fades up from 8px below. The\n // content itself is not remounted, so a screen keeps its own state and scroll; only its wrapper moves.\n const [scope, animate] = useAnimate<HTMLDivElement>();\n const reduced = useReducedMotion();\n const shownKey = `${current?.id ?? ''}:${view?.key ?? ''}`;\n const lastKey = useRef(shownKey);\n\n useEffect(() => {\n if (lastKey.current === shownKey) return;\n\n lastKey.current = shownKey;\n\n if (reduced || !scope.current) return;\n\n void animate(scope.current, { opacity: [0, 1], y: [8, 0] }, { duration: 0.22, ease: [0.22, 1, 0.36, 1] });\n }, [shownKey, reduced, animate, scope]);\n\n return (\n <Dialog open={open} onOpenChange={(next) => !next && onClose()}>\n <DialogContent\n aria-label=\"Settings\"\n className=\"flex h-[min(78vh,640px)] max-w-[860px] gap-0 overflow-hidden p-0\"\n >\n <nav\n aria-label=\"Settings sections\"\n onKeyDown={(e) => {\n // Up and Down move between the rows, like the account modal's rail.\n if (e.key !== 'ArrowDown' && e.key !== 'ArrowUp') return;\n const rowsEls = Array.from(\n e.currentTarget.querySelectorAll<HTMLElement>('button[data-row]'),\n );\n const at = rowsEls.indexOf(document.activeElement as HTMLElement);\n if (at === -1) return;\n e.preventDefault();\n const next =\n e.key === 'ArrowDown'\n ? (at + 1) % rowsEls.length\n : (at - 1 + rowsEls.length) % rowsEls.length;\n rowsEls[next].focus();\n }}\n className=\"flex w-[224px] shrink-0 flex-col gap-1 overflow-y-auto border-r border-[var(--border-subtle)] bg-[var(--bg-muted)] p-3\"\n >\n {name && (\n <div className=\"mb-3 flex items-center gap-2.5 px-1 pt-0.5\">\n <BusinessMark name={name} src={tenant?.businessLogo} size={34} />\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-semibold text-[var(--text-primary)]\">\n {name}\n </p>\n {planLabel && (\n <p className=\"truncate text-xs text-[var(--text-tertiary)]\">\n {planLabel}\n </p>\n )}\n </div>\n </div>\n )}\n <div className=\"mb-2 flex items-center gap-2 rounded-lg border border-[var(--border-subtle)] bg-[var(--bg-base)] px-3 text-[var(--text-tertiary)] focus-within:text-[var(--text-primary)]\">\n <Search className=\"size-4 shrink-0\" />\n <Input\n variant=\"bare\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder=\"Search\"\n aria-label=\"Search settings\"\n className=\"min-w-0 flex-1\"\n />\n </div>\n {shown.length === 0 && (\n <p className=\"px-3 py-2 text-sm text-[var(--text-tertiary)]\">\n Nothing matches “{query}”.\n </p>\n )}\n {groupRows(shown).map((group) => (\n <Fragment key={group.key}>\n <p className=\"px-3 pb-1 pt-3 text-xs font-medium text-[var(--text-tertiary)]\">\n {group.label}\n </p>\n {group.rows.map((r) => {\n const selected = r.id === current?.id;\n const Icon = r.icon;\n if (r.href) {\n return (\n <Link\n key={r.id}\n data-row\n href={r.href}\n onClick={onClose}\n className={`${ROW_CLASS} text-[var(--text-secondary)] hover:bg-[var(--interactive-hover)] hover:text-[var(--text-primary)]`}\n >\n <Icon className=\"size-[18px] shrink-0\" />\n <span className=\"truncate\">{r.name}</span>\n </Link>\n );\n }\n return (\n <button\n key={r.id}\n data-row\n type=\"button\"\n aria-current={selected ? 'page' : undefined}\n onClick={() => {\n setPicked(r.id);\n setView(null);\n }}\n className={`${ROW_CLASS} ${\n selected\n ? 'bg-[var(--bg-base)] text-[var(--text-primary)] shadow-sm'\n : 'text-[var(--text-secondary)] hover:bg-[var(--interactive-hover)] hover:text-[var(--text-primary)]'\n }`}\n >\n <Icon className=\"size-[18px] shrink-0\" />\n <span className=\"truncate\">{r.name}</span>\n </button>\n );\n })}\n </Fragment>\n ))}\n <div className=\"mt-auto border-t border-[var(--border-subtle)] pt-2\">\n <Link\n href={HELP_HREF}\n onClick={onClose}\n className={`${ROW_CLASS} text-[var(--text-secondary)] hover:bg-[var(--interactive-hover)] hover:text-[var(--text-primary)]`}\n >\n <HelpIcon className=\"size-[18px] shrink-0\" />\n <span className=\"flex-1\">Get help</span>\n <ArrowUpRight className=\"size-4 shrink-0 text-[var(--text-tertiary)]\" />\n </Link>\n </div>\n </nav>\n\n <div className=\"flex min-w-0 flex-1 flex-col\">\n <div className=\"flex items-center justify-between gap-3 border-b border-[var(--border-subtle)] px-4 py-3\">\n <div className=\"flex min-w-0 items-center gap-2\">\n {view && (\n <button\n type=\"button\"\n onClick={drill.back}\n aria-label={`Back to ${current?.name ?? 'Settings'}`}\n className=\"grid size-7 shrink-0 place-items-center rounded-lg text-[var(--text-secondary)] transition-colors hover:bg-[var(--interactive-hover)] hover:text-[var(--text-primary)]\"\n >\n <ArrowLeft className=\"size-4\" />\n </button>\n )}\n <DialogTitle\n aria-label={view?.title ?? current?.name ?? 'Settings'}\n className=\"truncate text-md font-semibold text-[var(--text-primary)]\"\n >\n <TextMorph as=\"span\">\n {view?.title ?? current?.name ?? 'Settings'}\n </TextMorph>\n </DialogTitle>\n </div>\n <div className=\"mr-7 flex items-center gap-2\">\n <div ref={setStatusSlot} className=\"flex items-center\" />\n </div>\n </div>\n <div className=\"min-h-0 flex-1 overflow-y-auto px-1 py-4 outline-none\">\n <div ref={scope}>\n {current && (\n <SettingsDrillProvider value={drill}>\n <SettingsSurface variant=\"plain\">\n <SettingsStatusSlot element={statusSlot}>\n {current.render ? (\n current.render()\n ) : (\n <SettingsPanel\n key={current.id}\n id={current.id as SettingsRowId}\n />\n )}\n </SettingsStatusSlot>\n </SettingsSurface>\n </SettingsDrillProvider>\n )}\n </div>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/shell/unified-settings-modal.tsx"],"sourcesContent":["'use client';\n\nimport { Fragment, useEffect, useMemo, useRef, useState, type ReactNode } from 'react';\nimport Link from 'next/link';\nimport { ArrowUpRight, ChevronLeft, Search, X } from 'lucide-react';\nimport { useAnimate, useReducedMotion } from 'motion/react';\nimport { BusinessMark } from '@withpotter/ui-kit/business-mark';\nimport { Dialog, DialogClose, DialogContent, DialogTitle } from '@withpotter/ui-kit/dialog';\nimport { Input } from '@withpotter/ui-kit/input';\nimport { TextMorph } from '@withpotter/ui-kit/text-morph';\nimport {\n SettingsStatusSlot,\n SettingsSurface,\n} from '@withpotter/pages-core/dashboard/settings/_components/settings-ui';\nimport {\n SettingsDrillProvider,\n type SettingsDrill,\n} from '@withpotter/pages-core/dashboard/settings/_components/settings-drill';\nimport { useAuth } from '@withpotter/lib/auth-context';\nimport { usePermissions } from '@withpotter/lib/use-permissions';\nimport { SettingsPanel } from './settings-panels';\nimport { groupRows } from './settings-groups';\nimport { useSidebarCards } from './sidebar-cards';\nimport { hostRowForHref, useSettingsHostRows } from './settings-host';\nimport {\n HELP_HREF,\n HELP_ICON,\n SETTINGS_ROWS,\n matchRows,\n rowForHref,\n type SettingsRowId,\n} from './settings-rows';\n\n/** A row of the window: the shared ones, or one a host added with the screen to draw. */\ntype ShownRow = Omit<(typeof SETTINGS_ROWS)[number], 'id'> & {\n id: string;\n render?: () => ReactNode;\n};\n\n/**\n * A row of the rail: 34px tall, 13.5px, an icon 16px wide at 1.75 stroke. The values are the settings prototype's; the\n * colours come from the window's own palette (`.st-modal` in shell.css), which falls back to the theme in dark.\n */\nconst ROW_CLASS =\n 'mb-px flex h-[34px] w-full items-center gap-[11px] rounded-[9px] px-2 text-left text-[13.5px] font-medium leading-5 transition-colors';\nconst ROW_IDLE =\n 'text-[var(--st-ink-2)] hover:bg-[var(--st-row-hover)] hover:text-[var(--text-primary)]';\nconst ROW_ON =\n 'bg-[var(--bg-base)] text-[var(--text-primary)] shadow-[var(--st-row-on-ring)]';\n\n/**\n * Settings, yours and the business's, in one window.\n *\n * It is the account modal's shell (a rail on the left, the page on the right):\n * the business at the top of the rail with the plan it is on when the host says,\n * then the rows in two groups, Business and You. A search matches row names and the\n * sections inside them, so \"passkeys\" or \"time zone\" finds its row. Rows the\n * member may not open are not drawn. Get help leaves the window, so it carries\n * an arrow.\n *\n * It replaces the window that showed each page in a frame: the pages are drawn\n * here directly, so there is no second copy of the app running behind it.\n */\nexport function UnifiedSettingsModal({\n open,\n href,\n onClose,\n}: {\n open: boolean;\n /** A settings address to open on. A row's own page when it owns it. */\n href?: string | null;\n onClose: () => void;\n}) {\n const { can, loading } = usePermissions();\n const [query, setQuery] = useState('');\n // A level inside the open row (a policy's editor). Cleared when the row, or\n // the address the window was opened on, changes.\n const [view, setView] = useState<SettingsDrill['view']>(null);\n // The row chosen inside the window, and the address it was opened on, so\n // opening on a different address moves the selection (adjusted in render).\n const [picked, setPicked] = useState<string | null>(null);\n const [seen, setSeen] = useState<string | null | undefined>(undefined);\n if (open && seen !== href) {\n setSeen(href);\n setPicked(null);\n setQuery('');\n setView(null);\n }\n\n const host = useSettingsHostRows();\n const rows = useMemo(() => {\n const own: ShownRow[] = SETTINGS_ROWS.filter(\n (r) => !r.permission || loading || r.permission.some((p) => can(p)),\n );\n\n // A host's rows go where they say, or after General.\n return host.reduce<ShownRow[]>((list, extra) => {\n const at = list.findIndex((r) => r.id === (extra.after ?? 'general'));\n\n const into = at === -1 ? list.length : at + 1;\n\n return [...list.slice(0, into), extra, ...list.slice(into)];\n }, own);\n }, [can, loading, host]);\n const shown = matchRows(rows, query);\n // Legal is not on the rail: it sits at the foot, beside Get help.\n const railRows = shown.filter((r) => r.id !== 'legal');\n const legalRow = shown.find((r) => r.id === 'legal');\n\n const drill = useMemo<SettingsDrill>(\n () => ({\n view,\n open: (key, title) => setView({ key, title }),\n openRow: (row, key, title) => {\n setPicked(row as SettingsRowId);\n setView({ key, title });\n },\n back: () => setView(null),\n }),\n [view],\n );\n\n const wanted = picked ?? hostRowForHref(host, href)?.id ?? rowForHref(href);\n // An address that names a row the member cannot see falls to their first.\n const current = rows.find((r) => r.id === wanted) ?? rows[0];\n const HelpIcon = HELP_ICON;\n\n const [statusSlot, setStatusSlot] = useState<HTMLElement | null>(null);\n\n // The business the window is about, and the plan it is on when the host says (Olive does; other products say nothing).\n const { tenant } = useAuth();\n const planLabel = useSidebarCards()?.planLabel;\n const name = tenant?.businessName?.trim() ?? '';\n\n // Moving to another row, or into and out of a level inside one, brings the content in: it fades up from 8px below. The\n // content itself is not remounted, so a screen keeps its own state and scroll; only its wrapper moves.\n const [scope, animate] = useAnimate<HTMLDivElement>();\n const reduced = useReducedMotion();\n const shownKey = `${current?.id ?? ''}:${view?.key ?? ''}`;\n const lastKey = useRef(shownKey);\n\n useEffect(() => {\n if (lastKey.current === shownKey) return;\n\n lastKey.current = shownKey;\n\n if (reduced || !scope.current) return;\n\n void animate(scope.current, { opacity: [0, 1], y: [8, 0] }, { duration: 0.22, ease: [0.22, 1, 0.36, 1] });\n }, [shownKey, reduced, animate, scope]);\n\n return (\n <Dialog open={open} onOpenChange={(next) => !next && onClose()}>\n <DialogContent\n aria-label=\"Settings\"\n showClose={false}\n className=\"st-modal flex h-[min(640px,calc(100dvh-2.5rem))] w-[calc(100%-2.5rem)] max-w-[920px] gap-0 overflow-hidden rounded-2xl border-0 p-0 text-[13.5px] shadow-[0_24px_60px_-20px_rgba(18,18,34,0.45),0_0_0_1px_rgba(0,0,0,0.04)]\"\n >\n <nav\n aria-label=\"Settings sections\"\n onKeyDown={(e) => {\n // Up and Down move between the rows, like the account modal's rail.\n if (e.key !== 'ArrowDown' && e.key !== 'ArrowUp') return;\n const rowsEls = Array.from(\n e.currentTarget.querySelectorAll<HTMLElement>('button[data-row]'),\n );\n const at = rowsEls.indexOf(document.activeElement as HTMLElement);\n if (at === -1) return;\n e.preventDefault();\n const next =\n e.key === 'ArrowDown'\n ? (at + 1) % rowsEls.length\n : (at - 1 + rowsEls.length) % rowsEls.length;\n rowsEls[next].focus();\n }}\n className=\"flex w-[248px] shrink-0 flex-col overflow-hidden border-r border-[var(--st-rail-line)] bg-[var(--st-rail)] px-3 pb-2.5 pt-4\"\n >\n {name && (\n <div className=\"flex items-center gap-2.5 px-1.5 pb-3.5 pt-0.5\">\n <BusinessMark name={name} src={tenant?.businessLogo} size={34} />\n <div className=\"min-w-0\">\n <p className=\"max-w-[150px] truncate text-[14px] font-semibold leading-[18px] text-[var(--text-primary)]\">\n {name}\n </p>\n {planLabel && (\n <p className=\"truncate text-[12px] leading-4 text-[var(--st-ink-3)]\">\n {planLabel}\n </p>\n )}\n </div>\n </div>\n )}\n <div className=\"mb-1.5 flex h-8 shrink-0 items-center gap-2 rounded-[10px] bg-[var(--bg-base)] px-2.5 text-[var(--st-ink-3)] shadow-[var(--st-search-ring)] focus-within:text-[var(--text-primary)]\">\n <Search className=\"size-[15px] shrink-0\" strokeWidth={1.8} />\n <Input\n variant=\"bare\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder=\"Search settings\"\n aria-label=\"Search settings\"\n className=\"min-w-0 flex-1\"\n />\n </div>\n <div className=\"-mx-1 min-h-0 flex-1 overflow-y-auto px-1\">\n {shown.length === 0 && (\n <p className=\"px-2 py-2 text-[13px] text-[var(--st-ink-3)]\">\n Nothing matches “{query}”.\n </p>\n )}\n {groupRows(railRows).map((group) => (\n <Fragment key={group.key}>\n <p className=\"px-2 pb-1.5 pt-3.5 text-[12px] font-medium leading-5 text-[var(--st-ink-4)]\">\n {group.label}\n </p>\n {group.rows.map((r) => {\n const selected = r.id === current?.id;\n const Icon = r.icon;\n if (r.href) {\n return (\n <Link\n key={r.id}\n data-row\n href={r.href}\n onClick={onClose}\n className={`${ROW_CLASS} ${ROW_IDLE}`}\n >\n <Icon className=\"size-4 shrink-0\" strokeWidth={1.75} />\n <span className=\"truncate\">{r.name}</span>\n <ArrowUpRight className=\"ml-auto size-[13px] shrink-0 opacity-[.55]\" />\n </Link>\n );\n }\n return (\n <button\n key={r.id}\n data-row\n type=\"button\"\n aria-current={selected ? 'page' : undefined}\n onClick={() => {\n setPicked(r.id);\n setView(null);\n }}\n className={`${ROW_CLASS} ${selected ? ROW_ON : ROW_IDLE}`}\n >\n <Icon className=\"size-4 shrink-0\" strokeWidth={1.75} />\n <span className=\"truncate\">{r.name}</span>\n </button>\n );\n })}\n </Fragment>\n ))}\n </div>\n <div className=\"mt-1.5 shrink-0 border-t border-[var(--st-rail-rule)] pt-2\">\n {legalRow && (\n <button\n type=\"button\"\n data-row\n aria-current={legalRow.id === current?.id ? 'page' : undefined}\n onClick={() => {\n setPicked(legalRow.id);\n setView(null);\n }}\n className={`${ROW_CLASS} ${legalRow.id === current?.id ? ROW_ON : ROW_IDLE}`}\n >\n <legalRow.icon className=\"size-4 shrink-0\" strokeWidth={1.75} />\n <span className=\"truncate\">{legalRow.name}</span>\n </button>\n )}\n <Link\n href={HELP_HREF}\n onClick={onClose}\n className={`${ROW_CLASS} ${ROW_IDLE}`}\n >\n <HelpIcon className=\"size-4 shrink-0\" strokeWidth={1.75} />\n <span>Get help</span>\n <ArrowUpRight className=\"ml-auto size-[13px] shrink-0\" />\n </Link>\n </div>\n </nav>\n\n <div className=\"flex min-w-0 flex-1 flex-col\">\n <div className=\"flex flex-none items-start justify-between pb-4 pl-10 pr-6 pt-5\">\n <div className=\"min-w-0\">\n {view && (\n <button\n type=\"button\"\n onClick={drill.back}\n aria-label={`Back to ${current?.name ?? 'Settings'}`}\n className=\"-ml-0.5 mb-1.5 inline-flex items-center gap-1 text-[13px] font-medium text-[var(--st-ink-2)] transition-colors hover:text-[var(--text-primary)]\"\n >\n <ChevronLeft className=\"size-[15px]\" strokeWidth={2} />\n {current?.name ?? 'Settings'}\n </button>\n )}\n <DialogTitle\n aria-label={view?.title ?? current?.name ?? 'Settings'}\n className=\"truncate text-[18px] font-semibold leading-6 tracking-[-0.01em] text-[var(--text-primary)]\"\n >\n <TextMorph as=\"span\">\n {view?.title ?? current?.name ?? 'Settings'}\n </TextMorph>\n </DialogTitle>\n {!view && current?.subtitle && (\n <p className=\"mt-0.5 text-[13px] leading-[18px] text-[var(--st-ink-3)]\">\n {current.subtitle}\n </p>\n )}\n </div>\n <div className=\"flex items-center gap-2.5\">\n <div ref={setStatusSlot} className=\"flex items-center\" />\n <DialogClose\n aria-label=\"Close\"\n className=\"grid size-8 place-items-center rounded-[9px] text-[var(--st-ink-2)] transition-colors hover:bg-[var(--st-row-hover)] hover:text-[var(--text-primary)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--focus-ring)]\"\n >\n <X className=\"size-[18px]\" strokeWidth={2} />\n </DialogClose>\n </div>\n </div>\n <div className=\"min-h-0 flex-1 overflow-y-auto px-6 pb-7 outline-none\">\n <div ref={scope}>\n {current && (\n <SettingsDrillProvider value={drill}>\n <SettingsSurface variant=\"plain\">\n <SettingsStatusSlot element={statusSlot}>\n {current.render ? (\n current.render()\n ) : (\n <SettingsPanel\n key={current.id}\n id={current.id as SettingsRowId}\n />\n )}\n </SettingsStatusSlot>\n </SettingsSurface>\n </SettingsDrillProvider>\n )}\n </div>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n );\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@withpotter/components",
|
|
3
|
-
"version": "0.13.0-next.
|
|
3
|
+
"version": "0.13.0-next.109",
|
|
4
4
|
"description": "Box dashboard feature components: editors, mobile screens, pickers, and composite UI built on @withpotter/ui-kit.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"sideEffects": [
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
"@tiptap/react": "^3.22.5",
|
|
51
51
|
"@tiptap/starter-kit": "^3.22.5",
|
|
52
52
|
"@tiptap/suggestion": "^3.22.5",
|
|
53
|
-
"@withpotter/hooks": "^0.13.0-next.
|
|
54
|
-
"@withpotter/lib": "^0.13.0-next.
|
|
55
|
-
"@withpotter/ui-kit": "^0.13.0-next.
|
|
53
|
+
"@withpotter/hooks": "^0.13.0-next.109",
|
|
54
|
+
"@withpotter/lib": "^0.13.0-next.109",
|
|
55
|
+
"@withpotter/ui-kit": "^0.13.0-next.109",
|
|
56
56
|
"@xyflow/react": "^12.11.2",
|
|
57
57
|
"dompurify": "^3.3.1",
|
|
58
58
|
"jsqr": "1.4.0",
|