@withpotter/components 0.13.0-next.107 → 0.13.0-next.108
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 +10 -1
- package/dist/shell/settings-host.js +13 -1
- package/dist/shell/settings-host.js.map +1 -1
- package/dist/shell/settings-panels.js +25 -11
- package/dist/shell/settings-panels.js.map +1 -1
- package/dist/shell/settings-rows.d.ts +1 -1
- package/dist/shell/settings-rows.js +65 -56
- package/dist/shell/settings-rows.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":[]}
|
|
@@ -19,10 +19,19 @@ export interface HostSettingsRow {
|
|
|
19
19
|
/** The screen drawn in the window. */
|
|
20
20
|
render: () => ReactNode;
|
|
21
21
|
}
|
|
22
|
-
export declare function SettingsHostProvider({ rows, children, }: {
|
|
22
|
+
export declare function SettingsHostProvider({ rows, generalRows, children, }: {
|
|
23
23
|
rows: HostSettingsRow[];
|
|
24
|
+
/**
|
|
25
|
+
* Rows a host adds to the Business card of General, below the business's own details. Olive shows its CAC
|
|
26
|
+
* certificate there. A host that passes nothing gets the card it had.
|
|
27
|
+
*/
|
|
28
|
+
generalRows?: () => ReactNode;
|
|
24
29
|
children: ReactNode;
|
|
25
30
|
}): import("react").JSX.Element;
|
|
31
|
+
/** What a host drew into a slot, or null when there is no host, or the host has nothing to show right now. */
|
|
32
|
+
export declare function slotContent(render: (() => ReactNode) | null | undefined): ReactNode;
|
|
33
|
+
/** The host's rows for the Business card of General, drawn by the card itself. */
|
|
34
|
+
export declare function useGeneralHostRows(): ReactNode;
|
|
26
35
|
export declare function useSettingsHostRows(): HostSettingsRow[];
|
|
27
36
|
/** The host row that owns an address: the address is one of its paths or sits below one. */
|
|
28
37
|
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 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":";AA2CM;AAzCN,SAAS,eAAe,kBAAkC;AAuB1D,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,6 +8,7 @@ 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";
|
|
13
14
|
import { PoliciesContent } from "@withpotter/pages-core/dashboard/settings/policies/_components/policies-content";
|
|
@@ -34,14 +35,25 @@ function Section({
|
|
|
34
35
|
children
|
|
35
36
|
] });
|
|
36
37
|
}
|
|
37
|
-
function
|
|
38
|
+
function ProfilePanel() {
|
|
38
39
|
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, {}) }),
|
|
40
|
+
/* @__PURE__ */ jsx(ProfileContent, {}),
|
|
42
41
|
/* @__PURE__ */ jsx(SecurityContent, {})
|
|
43
42
|
] });
|
|
44
43
|
}
|
|
44
|
+
function SecurityPanel() {
|
|
45
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-8", children: [
|
|
46
|
+
/* @__PURE__ */ jsx(Section, { title: "Sign-in and security", children: /* @__PURE__ */ jsx("div", { className: "px-4", children: /* @__PURE__ */ jsx(SignInContent, {}) }) }),
|
|
47
|
+
/* @__PURE__ */ jsx(Section, { title: "Data & privacy", children: /* @__PURE__ */ jsx(DataPrivacyContent, {}) })
|
|
48
|
+
] });
|
|
49
|
+
}
|
|
50
|
+
function ExtrasPanel() {
|
|
51
|
+
const { can } = usePermissions();
|
|
52
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-8", children: [
|
|
53
|
+
can("canManageSettings") && /* @__PURE__ */ jsx(FeaturesContent, {}),
|
|
54
|
+
/* @__PURE__ */ jsx(Section, { title: "Sample content", children: /* @__PURE__ */ jsx(DemoContentCard, {}) })
|
|
55
|
+
] });
|
|
56
|
+
}
|
|
45
57
|
function ResetAccessibility() {
|
|
46
58
|
const a11y = useAccessibility();
|
|
47
59
|
return /* @__PURE__ */ jsxs(
|
|
@@ -58,18 +70,20 @@ function ResetAccessibility() {
|
|
|
58
70
|
);
|
|
59
71
|
}
|
|
60
72
|
function PreferencesPanel() {
|
|
61
|
-
return /* @__PURE__ */
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
73
|
+
return /* @__PURE__ */ jsx(
|
|
74
|
+
PreferencesContent,
|
|
75
|
+
{
|
|
76
|
+
accessibility: /* @__PURE__ */ jsx(AccessibilityCard, { action: /* @__PURE__ */ jsx(ResetAccessibility, {}) })
|
|
77
|
+
}
|
|
78
|
+
);
|
|
65
79
|
}
|
|
66
80
|
const PANELS = {
|
|
67
81
|
general: () => /* @__PURE__ */ jsx(GeneralContent, {}),
|
|
68
|
-
|
|
69
|
-
|
|
82
|
+
extras: () => /* @__PURE__ */ jsx(ExtrasPanel, {}),
|
|
83
|
+
profile: () => /* @__PURE__ */ jsx(ProfilePanel, {}),
|
|
84
|
+
security: () => /* @__PURE__ */ jsx(SecurityPanel, {}),
|
|
70
85
|
team: () => /* @__PURE__ */ jsx(TeamContent, {}),
|
|
71
86
|
notifications: () => /* @__PURE__ */ jsx(NotificationsContent, {}),
|
|
72
|
-
"demo-content": () => /* @__PURE__ */ jsx(DemoContentCard, {}),
|
|
73
87
|
preferences: () => /* @__PURE__ */ jsx(PreferencesPanel, {}),
|
|
74
88
|
"payment-approvals": () => /* @__PURE__ */ jsx(PaymentApprovalsContent, {}),
|
|
75
89
|
billing: () => /* @__PURE__ */ jsx(BillingContent, {}),
|
|
@@ -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 { 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 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":";AAkCsB,cAchB,YAdgB;AAhCtB,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,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,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,7 +12,7 @@ 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' | 'usage' | 'policies' | 'legal';
|
|
16
16
|
export interface SettingsRow {
|
|
17
17
|
id: SettingsRowId;
|
|
18
18
|
name: string;
|
|
@@ -6,10 +6,10 @@ import {
|
|
|
6
6
|
CircleHelp,
|
|
7
7
|
CreditCard,
|
|
8
8
|
FileText,
|
|
9
|
+
Lock,
|
|
9
10
|
Scale,
|
|
10
11
|
Sparkles,
|
|
11
12
|
SlidersHorizontal,
|
|
12
|
-
ToggleLeft,
|
|
13
13
|
UserRound,
|
|
14
14
|
Users
|
|
15
15
|
} from "lucide-react";
|
|
@@ -28,16 +28,6 @@ const PAYMENT_APPROVALS_ROW = {
|
|
|
28
28
|
paths: ["/dashboard/settings/payment-approvals"],
|
|
29
29
|
keywords: ["approval", "accepted", "rejected", "decisions", "history"]
|
|
30
30
|
};
|
|
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
31
|
const SETTINGS_ROWS = [
|
|
42
32
|
{
|
|
43
33
|
id: "general",
|
|
@@ -51,57 +41,88 @@ const SETTINGS_ROWS = [
|
|
|
51
41
|
keywords: ["business name", "branding", "logo", "colours", "numbering"],
|
|
52
42
|
permission: ["canManageBusinessProfile", "canManageSettings"]
|
|
53
43
|
},
|
|
44
|
+
// A licensed box that bills outside the console hides the row.
|
|
45
|
+
...process.env.NEXT_PUBLIC_BOX_HIDE_BILLING === "true" ? [] : [BILLING_ROW],
|
|
54
46
|
{
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
47
|
+
id: "team",
|
|
48
|
+
name: "Team & permissions",
|
|
49
|
+
icon: Users,
|
|
50
|
+
paths: [
|
|
51
|
+
"/dashboard/settings/team",
|
|
52
|
+
"/dashboard/settings/roles",
|
|
53
|
+
"/dashboard/settings/activity"
|
|
54
|
+
],
|
|
55
|
+
keywords: ["people", "invite", "roles", "activity", "seats"],
|
|
56
|
+
permission: ["canManageTeam"],
|
|
57
|
+
// A box with its own team screen (its roles and invitations come from
|
|
58
|
+
// its engine, which does not serve this panel's routes) names it here.
|
|
59
|
+
href: process.env.NEXT_PUBLIC_BOX_TEAM_PAGE || void 0
|
|
60
|
+
},
|
|
61
|
+
...process.env.NEXT_PUBLIC_BOX_PAYMENT_APPROVALS === "true" ? [PAYMENT_APPROVALS_ROW] : [],
|
|
62
|
+
{
|
|
63
|
+
id: "policies",
|
|
64
|
+
name: "Policies",
|
|
65
|
+
icon: Scale,
|
|
66
|
+
paths: ["/dashboard/settings/policies"],
|
|
67
|
+
keywords: ["returns", "shipping", "privacy", "terms"],
|
|
63
68
|
permission: ["canManageSettings"]
|
|
64
69
|
},
|
|
65
70
|
{
|
|
66
|
-
|
|
67
|
-
|
|
71
|
+
// The switches no business type implies (Blog today) and the sample
|
|
72
|
+
// content to start with, clear, or add again. One row for both. No
|
|
73
|
+
// permission gate on the row, like the Demo content entry in the sidebar:
|
|
74
|
+
// everyone sees the state, the panel shows the switches only to those who
|
|
75
|
+
// may change them, and the demo card shows its actions only to the owner
|
|
76
|
+
// and admins.
|
|
77
|
+
id: "extras",
|
|
78
|
+
name: "Extras",
|
|
79
|
+
icon: Sparkles,
|
|
80
|
+
paths: [
|
|
81
|
+
"/dashboard/settings/features",
|
|
82
|
+
"/dashboard/settings/demo-content"
|
|
83
|
+
],
|
|
84
|
+
keywords: [
|
|
85
|
+
"blog",
|
|
86
|
+
"posts",
|
|
87
|
+
"write",
|
|
88
|
+
"publish",
|
|
89
|
+
"switch",
|
|
90
|
+
"website",
|
|
91
|
+
"features",
|
|
92
|
+
"sample",
|
|
93
|
+
"products",
|
|
94
|
+
"starter",
|
|
95
|
+
"clear",
|
|
96
|
+
"example data",
|
|
97
|
+
"demo content"
|
|
98
|
+
]
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
id: "profile",
|
|
102
|
+
name: "Profile",
|
|
68
103
|
icon: UserRound,
|
|
104
|
+
paths: ["/dashboard/settings/profile"],
|
|
105
|
+
keywords: ["name", "email", "photo", "phone", "close account"]
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: "security",
|
|
109
|
+
name: "Security",
|
|
110
|
+
icon: Lock,
|
|
69
111
|
paths: [
|
|
70
|
-
"/dashboard/settings/profile",
|
|
71
112
|
"/dashboard/settings/devices",
|
|
72
113
|
"/dashboard/settings/data-privacy"
|
|
73
114
|
],
|
|
74
115
|
keywords: [
|
|
75
|
-
"profile",
|
|
76
|
-
"name",
|
|
77
|
-
"email",
|
|
78
|
-
"photo",
|
|
79
|
-
"security",
|
|
80
116
|
"password",
|
|
81
117
|
"passkeys",
|
|
82
118
|
"two-factor",
|
|
83
119
|
"sessions",
|
|
84
120
|
"devices",
|
|
121
|
+
"sign in",
|
|
85
122
|
"privacy",
|
|
86
|
-
"export"
|
|
87
|
-
"close account"
|
|
123
|
+
"export"
|
|
88
124
|
]
|
|
89
125
|
},
|
|
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
126
|
{
|
|
106
127
|
id: "notifications",
|
|
107
128
|
name: "Notifications",
|
|
@@ -109,7 +130,6 @@ const SETTINGS_ROWS = [
|
|
|
109
130
|
paths: ["/dashboard/settings/notifications"],
|
|
110
131
|
keywords: ["alerts", "low stock", "email sender", "digest", "reminders"]
|
|
111
132
|
},
|
|
112
|
-
DEMO_CONTENT_ROW,
|
|
113
133
|
{
|
|
114
134
|
id: "preferences",
|
|
115
135
|
name: "Preferences",
|
|
@@ -132,9 +152,6 @@ const SETTINGS_ROWS = [
|
|
|
132
152
|
"text size"
|
|
133
153
|
]
|
|
134
154
|
},
|
|
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
155
|
// A box that meters nothing in the console leaves Usage out.
|
|
139
156
|
...process.env.NEXT_PUBLIC_BOX_HIDE_USAGE === "true" ? [] : [
|
|
140
157
|
{
|
|
@@ -146,14 +163,6 @@ const SETTINGS_ROWS = [
|
|
|
146
163
|
permission: ["canManageSettings"]
|
|
147
164
|
}
|
|
148
165
|
],
|
|
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
166
|
{
|
|
158
167
|
id: "legal",
|
|
159
168
|
name: "Legal",
|
|
@@ -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 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 | '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\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 // A licensed box that bills outside the console hides the row.\n ...(process.env.NEXT_PUBLIC_BOX_HIDE_BILLING === 'true' ? [] : [BILLING_ROW]),\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 ...(process.env.NEXT_PUBLIC_BOX_PAYMENT_APPROVALS === 'true'\n ? [PAYMENT_APPROVALS_ROW]\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 // 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 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 icon: UserRound,\n paths: ['/dashboard/settings/profile'],\n keywords: ['name', 'email', 'photo', 'phone', 'close account'],\n },\n {\n id: 'security',\n name: 'Security',\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 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 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 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 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/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.108",
|
|
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.108",
|
|
54
|
+
"@withpotter/lib": "^0.13.0-next.108",
|
|
55
|
+
"@withpotter/ui-kit": "^0.13.0-next.108",
|
|
56
56
|
"@xyflow/react": "^12.11.2",
|
|
57
57
|
"dompurify": "^3.3.1",
|
|
58
58
|
"jsqr": "1.4.0",
|