@withpotter/components 0.13.0-next.106 → 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.
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The rail of the settings window is two groups: the business's settings, and the person's own. A row belongs to
3
+ * Business unless it is one of the few that are about the signed-in person. A host's own rows are the business's.
4
+ */
5
+ export declare const SettingsGroupKey: {
6
+ readonly Business: "business";
7
+ readonly You: "you";
8
+ };
9
+ export type SettingsGroupKey = (typeof SettingsGroupKey)[keyof typeof SettingsGroupKey];
10
+ export declare const SETTINGS_GROUP_LABEL: Record<SettingsGroupKey, string>;
11
+ export declare function groupOf(id: string): SettingsGroupKey;
12
+ export interface SettingsGroup<T> {
13
+ key: SettingsGroupKey;
14
+ label: string;
15
+ rows: T[];
16
+ }
17
+ /**
18
+ * The rows split under their group labels, each group in the order the rows came in, Business first. A group with no
19
+ * rows (a search that matches none of them) is left out, so no label is ever drawn over nothing.
20
+ */
21
+ export declare function groupRows<T extends {
22
+ id: string;
23
+ }>(rows: T[]): SettingsGroup<T>[];
@@ -0,0 +1,22 @@
1
+ const SettingsGroupKey = {
2
+ Business: "business",
3
+ You: "you"
4
+ };
5
+ const SETTINGS_GROUP_LABEL = {
6
+ [SettingsGroupKey.Business]: "Business",
7
+ [SettingsGroupKey.You]: "You"
8
+ };
9
+ const PERSONAL = /* @__PURE__ */ new Set(["profile", "security", "notifications", "preferences"]);
10
+ function groupOf(id) {
11
+ return PERSONAL.has(id) ? SettingsGroupKey.You : SettingsGroupKey.Business;
12
+ }
13
+ function groupRows(rows) {
14
+ return [SettingsGroupKey.Business, SettingsGroupKey.You].map((key) => ({ key, label: SETTINGS_GROUP_LABEL[key], rows: rows.filter((row) => groupOf(row.id) === key) })).filter((group) => group.rows.length > 0);
15
+ }
16
+ export {
17
+ SETTINGS_GROUP_LABEL,
18
+ SettingsGroupKey,
19
+ groupOf,
20
+ groupRows
21
+ };
22
+ //# sourceMappingURL=settings-groups.js.map
@@ -0,0 +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(['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}>{children}</Context.Provider>;\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":";AAkCS;AAhCT,SAAS,eAAe,kBAAkC;AAuB1D,MAAM,UAAU,cAAiC,CAAC,CAAC;AAE5C,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AACF,GAGG;AACD,SAAO,oBAAC,QAAQ,UAAR,EAAiB,OAAO,MAAO,UAAS;AAClD;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":[]}
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 AccountPanel() {
38
+ function ProfilePanel() {
38
39
  return /* @__PURE__ */ jsxs("div", { className: "space-y-8", children: [
39
- /* @__PURE__ */ jsx(Section, { title: "Profile", children: /* @__PURE__ */ jsx(ProfileContent, {}) }),
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__ */ jsxs("div", { className: "space-y-8", children: [
62
- /* @__PURE__ */ jsx(Section, { title: "Appearance, language and region", children: /* @__PURE__ */ jsx(PreferencesContent, {}) }),
63
- /* @__PURE__ */ jsx(Section, { title: "Accessibility", action: /* @__PURE__ */ jsx(ResetAccessibility, {}), children: /* @__PURE__ */ jsx("div", { className: "px-4", children: /* @__PURE__ */ jsx(AccessibilityCard, {}) }) })
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
- features: () => /* @__PURE__ */ jsx(FeaturesContent, {}),
69
- account: () => /* @__PURE__ */ jsx(AccountPanel, {}),
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 * Everything about the person: profile, how they sign in, their data, and the\n * way out. Closing the account is last, as it is the one thing here that\n * cannot be undone.\n */\nfunction AccountPanel() {\n return (\n <div className=\"space-y-8\">\n <Section title=\"Profile\">\n <ProfileContent />\n </Section>\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 <SecurityContent />\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 <div className=\"space-y-8\">\n <Section title=\"Appearance, language and region\">\n <PreferencesContent />\n </Section>\n <Section title=\"Accessibility\" action={<ResetAccessibility />}>\n <div className=\"px-4\">\n <AccessibilityCard />\n </div>\n </Section>\n </div>\n );\n}\n\nconst PANELS: Record<SettingsRowId, () => ReactNode> = {\n general: () => <GeneralContent />,\n features: () => <FeaturesContent />,\n account: () => <AccountPanel />,\n team: () => <TeamContent />,\n notifications: () => <NotificationsContent />,\n 'demo-content': () => <DemoContentCard />,\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":";AAiCsB,cAchB,YAdgB;AA/BtB,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,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;AAOA,SAAS,eAAe;AACtB,SACE,qBAAC,SAAI,WAAU,aACb;AAAA,wBAAC,WAAQ,OAAM,WACb,8BAAC,kBAAe,GAClB;AAAA,IACA,oBAAC,WAAQ,OAAM,wBACb,8BAAC,SAAI,WAAU,QACb,8BAAC,iBAAc,GACjB,GACF;AAAA,IACA,oBAAC,WAAQ,OAAM,kBACb,8BAAC,sBAAmB,GACtB;AAAA,IACA,oBAAC,mBAAgB;AAAA,KACnB;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,qBAAC,SAAI,WAAU,aACb;AAAA,wBAAC,WAAQ,OAAM,mCACb,8BAAC,sBAAmB,GACtB;AAAA,IACA,oBAAC,WAAQ,OAAM,iBAAgB,QAAQ,oBAAC,sBAAmB,GACzD,8BAAC,SAAI,WAAU,QACb,8BAAC,qBAAkB,GACrB,GACF;AAAA,KACF;AAEJ;AAEA,MAAM,SAAiD;AAAA,EACrD,SAAS,MAAM,oBAAC,kBAAe;AAAA,EAC/B,UAAU,MAAM,oBAAC,mBAAgB;AAAA,EACjC,SAAS,MAAM,oBAAC,gBAAa;AAAA,EAC7B,MAAM,MAAM,oBAAC,eAAY;AAAA,EACzB,eAAe,MAAM,oBAAC,wBAAqB;AAAA,EAC3C,gBAAgB,MAAM,oBAAC,mBAAgB;AAAA,EACvC,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":[]}
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' | 'features' | 'account' | 'team' | 'notifications' | 'demo-content' | 'preferences' | 'payment-approvals' | 'billing' | 'usage' | 'policies' | 'legal';
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";
@@ -41,57 +41,88 @@ const SETTINGS_ROWS = [
41
41
  keywords: ["business name", "branding", "logo", "colours", "numbering"],
42
42
  permission: ["canManageBusinessProfile", "canManageSettings"]
43
43
  },
44
+ // A licensed box that bills outside the console hides the row.
45
+ ...process.env.NEXT_PUBLIC_BOX_HIDE_BILLING === "true" ? [] : [BILLING_ROW],
44
46
  {
45
- // The switches no business type implies: Blog today. Next to General
46
- // because it is the business's own setting, before the rows that are
47
- // about the person (Account, Preferences).
48
- id: "features",
49
- name: "Features",
50
- icon: ToggleLeft,
51
- paths: ["/dashboard/settings/features"],
52
- keywords: ["blog", "posts", "write", "publish", "switch", "website", "features"],
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"],
53
68
  permission: ["canManageSettings"]
54
69
  },
55
70
  {
56
- id: "account",
57
- name: "Account",
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",
58
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,
59
111
  paths: [
60
- "/dashboard/settings/profile",
61
112
  "/dashboard/settings/devices",
62
113
  "/dashboard/settings/data-privacy"
63
114
  ],
64
115
  keywords: [
65
- "profile",
66
- "name",
67
- "email",
68
- "photo",
69
- "security",
70
116
  "password",
71
117
  "passkeys",
72
118
  "two-factor",
73
119
  "sessions",
74
120
  "devices",
121
+ "sign in",
75
122
  "privacy",
76
- "export",
77
- "close account"
123
+ "export"
78
124
  ]
79
125
  },
80
- {
81
- id: "team",
82
- name: "Team & permissions",
83
- icon: Users,
84
- paths: [
85
- "/dashboard/settings/team",
86
- "/dashboard/settings/roles",
87
- "/dashboard/settings/activity"
88
- ],
89
- keywords: ["people", "invite", "roles", "activity", "seats"],
90
- permission: ["canManageTeam"],
91
- // A box with its own team screen (its roles and invitations come from
92
- // its engine, which does not serve this panel's routes) names it here.
93
- href: process.env.NEXT_PUBLIC_BOX_TEAM_PAGE || void 0
94
- },
95
126
  {
96
127
  id: "notifications",
97
128
  name: "Notifications",
@@ -99,16 +130,6 @@ const SETTINGS_ROWS = [
99
130
  paths: ["/dashboard/settings/notifications"],
100
131
  keywords: ["alerts", "low stock", "email sender", "digest", "reminders"]
101
132
  },
102
- {
103
- // Sample products, pages and pictures to start with, clear, or add again.
104
- // No permission gate, like the sidebar entry: everyone sees the state and
105
- // the card itself shows the actions only to the owner and admins.
106
- id: "demo-content",
107
- name: "Demo content",
108
- icon: Sparkles,
109
- paths: ["/dashboard/settings/demo-content"],
110
- keywords: ["sample", "products", "starter", "clear", "example data"]
111
- },
112
133
  {
113
134
  id: "preferences",
114
135
  name: "Preferences",
@@ -131,9 +152,6 @@ const SETTINGS_ROWS = [
131
152
  "text size"
132
153
  ]
133
154
  },
134
- ...process.env.NEXT_PUBLIC_BOX_PAYMENT_APPROVALS === "true" ? [PAYMENT_APPROVALS_ROW] : [],
135
- // A licensed box that bills outside the console hides the row.
136
- ...process.env.NEXT_PUBLIC_BOX_HIDE_BILLING === "true" ? [] : [BILLING_ROW],
137
155
  // A box that meters nothing in the console leaves Usage out.
138
156
  ...process.env.NEXT_PUBLIC_BOX_HIDE_USAGE === "true" ? [] : [
139
157
  {
@@ -145,14 +163,6 @@ const SETTINGS_ROWS = [
145
163
  permission: ["canManageSettings"]
146
164
  }
147
165
  ],
148
- {
149
- id: "policies",
150
- name: "Policies",
151
- icon: Scale,
152
- paths: ["/dashboard/settings/policies"],
153
- keywords: ["returns", "shipping", "privacy", "terms"],
154
- permission: ["canManageSettings"]
155
- },
156
166
  {
157
167
  id: "legal",
158
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\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 {\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 {\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":[]}
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * Settings, yours and the business's, in one window.
3
3
  *
4
- * It is the account modal's shell (a rail on the left, the page on the right)
5
- * with every row in one list. A search at the top matches row names and the
4
+ * It is the account modal's shell (a rail on the left, the page on the right):
5
+ * the business at the top of the rail with the plan it is on when the host says,
6
+ * then the rows in two groups, Business and You. A search matches row names and the
6
7
  * sections inside them, so "passkeys" or "time zone" finds its row. Rows the
7
8
  * member may not open are not drawn. Get help leaves the window, so it carries
8
9
  * an arrow.
@@ -1,8 +1,10 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
- import { useMemo, useState } from "react";
3
+ import { Fragment, useEffect, useMemo, useRef, useState } from "react";
4
4
  import Link from "next/link";
5
5
  import { ArrowLeft, ArrowUpRight, Search } from "lucide-react";
6
+ import { useAnimate, useReducedMotion } from "motion/react";
7
+ import { BusinessMark } from "@withpotter/ui-kit/business-mark";
6
8
  import { Dialog, DialogContent, DialogTitle } from "@withpotter/ui-kit/dialog";
7
9
  import { Input } from "@withpotter/ui-kit/input";
8
10
  import { TextMorph } from "@withpotter/ui-kit/text-morph";
@@ -13,8 +15,11 @@ import {
13
15
  import {
14
16
  SettingsDrillProvider
15
17
  } from "@withpotter/pages-core/dashboard/settings/_components/settings-drill";
18
+ import { useAuth } from "@withpotter/lib/auth-context";
16
19
  import { usePermissions } from "@withpotter/lib/use-permissions";
17
20
  import { SettingsPanel } from "./settings-panels";
21
+ import { groupRows } from "./settings-groups";
22
+ import { useSidebarCards } from "./sidebar-cards";
18
23
  import { hostRowForHref, useSettingsHostRows } from "./settings-host";
19
24
  import {
20
25
  HELP_HREF,
@@ -68,6 +73,19 @@ function UnifiedSettingsModal({
68
73
  const current = rows.find((r) => r.id === wanted) ?? rows[0];
69
74
  const HelpIcon = HELP_ICON;
70
75
  const [statusSlot, setStatusSlot] = useState(null);
76
+ const { tenant } = useAuth();
77
+ const planLabel = useSidebarCards()?.planLabel;
78
+ const name = tenant?.businessName?.trim() ?? "";
79
+ const [scope, animate] = useAnimate();
80
+ const reduced = useReducedMotion();
81
+ const shownKey = `${current?.id ?? ""}:${view?.key ?? ""}`;
82
+ const lastKey = useRef(shownKey);
83
+ useEffect(() => {
84
+ if (lastKey.current === shownKey) return;
85
+ lastKey.current = shownKey;
86
+ if (reduced || !scope.current) return;
87
+ void animate(scope.current, { opacity: [0, 1], y: [8, 0] }, { duration: 0.22, ease: [0.22, 1, 0.36, 1] });
88
+ }, [shownKey, reduced, animate, scope]);
71
89
  return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange: (next) => !next && onClose(), children: /* @__PURE__ */ jsxs(
72
90
  DialogContent,
73
91
  {
@@ -91,6 +109,13 @@ function UnifiedSettingsModal({
91
109
  },
92
110
  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",
93
111
  children: [
112
+ name && /* @__PURE__ */ jsxs("div", { className: "mb-3 flex items-center gap-2.5 px-1 pt-0.5", children: [
113
+ /* @__PURE__ */ jsx(BusinessMark, { name, src: tenant?.businessLogo, size: 34 }),
114
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
115
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-semibold text-[var(--text-primary)]", children: name }),
116
+ planLabel && /* @__PURE__ */ jsx("p", { className: "truncate text-xs text-[var(--text-tertiary)]", children: planLabel })
117
+ ] })
118
+ ] }),
94
119
  /* @__PURE__ */ jsxs("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)]", children: [
95
120
  /* @__PURE__ */ jsx(Search, { className: "size-4 shrink-0" }),
96
121
  /* @__PURE__ */ jsx(
@@ -105,23 +130,43 @@ function UnifiedSettingsModal({
105
130
  }
106
131
  )
107
132
  ] }),
108
- /* @__PURE__ */ jsx("p", { className: "px-3 pb-1 pt-1 text-xs font-medium text-[var(--text-tertiary)]", children: "Settings" }),
109
133
  shown.length === 0 && /* @__PURE__ */ jsxs("p", { className: "px-3 py-2 text-sm text-[var(--text-tertiary)]", children: [
110
134
  "Nothing matches \u201C",
111
135
  query,
112
136
  "\u201D."
113
137
  ] }),
114
- shown.map((r) => {
115
- const selected = r.id === current?.id;
116
- const Icon = r.icon;
117
- if (r.href) {
138
+ groupRows(shown).map((group) => /* @__PURE__ */ jsxs(Fragment, { children: [
139
+ /* @__PURE__ */ jsx("p", { className: "px-3 pb-1 pt-3 text-xs font-medium text-[var(--text-tertiary)]", children: group.label }),
140
+ group.rows.map((r) => {
141
+ const selected = r.id === current?.id;
142
+ const Icon = r.icon;
143
+ if (r.href) {
144
+ return /* @__PURE__ */ jsxs(
145
+ Link,
146
+ {
147
+ "data-row": true,
148
+ href: r.href,
149
+ onClick: onClose,
150
+ className: `${ROW_CLASS} text-[var(--text-secondary)] hover:bg-[var(--interactive-hover)] hover:text-[var(--text-primary)]`,
151
+ children: [
152
+ /* @__PURE__ */ jsx(Icon, { className: "size-[18px] shrink-0" }),
153
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: r.name })
154
+ ]
155
+ },
156
+ r.id
157
+ );
158
+ }
118
159
  return /* @__PURE__ */ jsxs(
119
- Link,
160
+ "button",
120
161
  {
121
162
  "data-row": true,
122
- href: r.href,
123
- onClick: onClose,
124
- className: `${ROW_CLASS} text-[var(--text-secondary)] hover:bg-[var(--interactive-hover)] hover:text-[var(--text-primary)]`,
163
+ type: "button",
164
+ "aria-current": selected ? "page" : void 0,
165
+ onClick: () => {
166
+ setPicked(r.id);
167
+ setView(null);
168
+ },
169
+ className: `${ROW_CLASS} ${selected ? "bg-[var(--bg-base)] text-[var(--text-primary)] shadow-sm" : "text-[var(--text-secondary)] hover:bg-[var(--interactive-hover)] hover:text-[var(--text-primary)]"}`,
125
170
  children: [
126
171
  /* @__PURE__ */ jsx(Icon, { className: "size-[18px] shrink-0" }),
127
172
  /* @__PURE__ */ jsx("span", { className: "truncate", children: r.name })
@@ -129,26 +174,8 @@ function UnifiedSettingsModal({
129
174
  },
130
175
  r.id
131
176
  );
132
- }
133
- return /* @__PURE__ */ jsxs(
134
- "button",
135
- {
136
- "data-row": true,
137
- type: "button",
138
- "aria-current": selected ? "page" : void 0,
139
- onClick: () => {
140
- setPicked(r.id);
141
- setView(null);
142
- },
143
- className: `${ROW_CLASS} ${selected ? "bg-[var(--bg-base)] text-[var(--text-primary)] shadow-sm" : "text-[var(--text-secondary)] hover:bg-[var(--interactive-hover)] hover:text-[var(--text-primary)]"}`,
144
- children: [
145
- /* @__PURE__ */ jsx(Icon, { className: "size-[18px] shrink-0" }),
146
- /* @__PURE__ */ jsx("span", { className: "truncate", children: r.name })
147
- ]
148
- },
149
- r.id
150
- );
151
- }),
177
+ })
178
+ ] }, group.key)),
152
179
  /* @__PURE__ */ jsx("div", { className: "mt-auto border-t border-[var(--border-subtle)] pt-2", children: /* @__PURE__ */ jsxs(
153
180
  Link,
154
181
  {
@@ -189,13 +216,13 @@ function UnifiedSettingsModal({
189
216
  ] }),
190
217
  /* @__PURE__ */ jsx("div", { className: "mr-7 flex items-center gap-2", children: /* @__PURE__ */ jsx("div", { ref: setStatusSlot, className: "flex items-center" }) })
191
218
  ] }),
192
- /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto px-1 py-4 outline-none", 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(
219
+ /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto px-1 py-4 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(
193
220
  SettingsPanel,
194
221
  {
195
222
  id: current.id
196
223
  },
197
224
  current.id
198
- ) }) }) }) })
225
+ ) }) }) }) }) })
199
226
  ] })
200
227
  ]
201
228
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/shell/unified-settings-modal.tsx"],"sourcesContent":["'use client';\n\nimport { useMemo, useState, type ReactNode } from 'react';\nimport Link from 'next/link';\nimport { ArrowLeft, ArrowUpRight, Search } from 'lucide-react';\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 { usePermissions } from '@withpotter/lib/use-permissions';\nimport { SettingsPanel } from './settings-panels';\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 * with every row in one list. A search at the top 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 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 <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 <p className=\"px-3 pb-1 pt-1 text-xs font-medium text-[var(--text-tertiary)]\">\n Settings\n </p>\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 {shown.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 <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 {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 </DialogContent>\n </Dialog>\n );\n}\n"],"mappings":";AAyIU,SACE,KADF;AAvIV,SAAS,SAAS,gBAAgC;AAClD,OAAO,UAAU;AACjB,SAAS,WAAW,cAAc,cAAc;AAChD,SAAS,QAAQ,eAAe,mBAAmB;AACnD,SAAS,aAAa;AACtB,SAAS,iBAAiB;AAC1B;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,OAEK;AACP,SAAS,sBAAsB;AAC/B,SAAS,qBAAqB;AAC9B,SAAS,gBAAgB,2BAA2B;AACpD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAQP,MAAM,YACJ;AAcK,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AACF,GAKG;AACD,QAAM,EAAE,KAAK,QAAQ,IAAI,eAAe;AACxC,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,EAAE;AAGrC,QAAM,CAAC,MAAM,OAAO,IAAI,SAAgC,IAAI;AAG5D,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAwB,IAAI;AACxD,QAAM,CAAC,MAAM,OAAO,IAAI,SAAoC,MAAS;AACrE,MAAI,QAAQ,SAAS,MAAM;AACzB,YAAQ,IAAI;AACZ,cAAU,IAAI;AACd,aAAS,EAAE;AACX,YAAQ,IAAI;AAAA,EACd;AAEA,QAAM,OAAO,oBAAoB;AACjC,QAAM,OAAO,QAAQ,MAAM;AACzB,UAAM,MAAkB,cAAc;AAAA,MACpC,CAAC,MAAM,CAAC,EAAE,cAAc,WAAW,EAAE,WAAW,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;AAAA,IACpE;AAGA,WAAO,KAAK,OAAmB,CAAC,MAAM,UAAU;AAC9C,YAAM,KAAK,KAAK,UAAU,CAAC,MAAM,EAAE,QAAQ,MAAM,SAAS,UAAU;AAEpE,YAAM,OAAO,OAAO,KAAK,KAAK,SAAS,KAAK;AAE5C,aAAO,CAAC,GAAG,KAAK,MAAM,GAAG,IAAI,GAAG,OAAO,GAAG,KAAK,MAAM,IAAI,CAAC;AAAA,IAC5D,GAAG,GAAG;AAAA,EACR,GAAG,CAAC,KAAK,SAAS,IAAI,CAAC;AACvB,QAAM,QAAQ,UAAU,MAAM,KAAK;AAEnC,QAAM,QAAQ;AAAA,IACZ,OAAO;AAAA,MACL;AAAA,MACA,MAAM,CAAC,KAAK,UAAU,QAAQ,EAAE,KAAK,MAAM,CAAC;AAAA,MAC5C,SAAS,CAAC,KAAK,KAAK,UAAU;AAC5B,kBAAU,GAAoB;AAC9B,gBAAQ,EAAE,KAAK,MAAM,CAAC;AAAA,MACxB;AAAA,MACA,MAAM,MAAM,QAAQ,IAAI;AAAA,IAC1B;AAAA,IACA,CAAC,IAAI;AAAA,EACP;AAEA,QAAM,SAAS,UAAU,eAAe,MAAM,IAAI,GAAG,MAAM,WAAW,IAAI;AAE1E,QAAM,UAAU,KAAK,KAAK,CAAC,MAAM,EAAE,OAAO,MAAM,KAAK,KAAK,CAAC;AAC3D,QAAM,WAAW;AAEjB,QAAM,CAAC,YAAY,aAAa,IAAI,SAA6B,IAAI;AAErE,SACE,oBAAC,UAAO,MAAY,cAAc,CAAC,SAAS,CAAC,QAAQ,QAAQ,GAC3D;AAAA,IAAC;AAAA;AAAA,MACC,cAAW;AAAA,MACX,WAAU;AAAA,MAEV;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,cAAW;AAAA,YACX,WAAW,CAAC,MAAM;AAEhB,kBAAI,EAAE,QAAQ,eAAe,EAAE,QAAQ,UAAW;AAClD,oBAAM,UAAU,MAAM;AAAA,gBACpB,EAAE,cAAc,iBAA8B,kBAAkB;AAAA,cAClE;AACA,oBAAM,KAAK,QAAQ,QAAQ,SAAS,aAA4B;AAChE,kBAAI,OAAO,GAAI;AACf,gBAAE,eAAe;AACjB,oBAAM,OACJ,EAAE,QAAQ,eACL,KAAK,KAAK,QAAQ,UAClB,KAAK,IAAI,QAAQ,UAAU,QAAQ;AAC1C,sBAAQ,IAAI,EAAE,MAAM;AAAA,YACtB;AAAA,YACA,WAAU;AAAA,YAEV;AAAA,mCAAC,SAAI,WAAU,6KACb;AAAA,oCAAC,UAAO,WAAU,mBAAkB;AAAA,gBACpC;AAAA,kBAAC;AAAA;AAAA,oBACC,SAAQ;AAAA,oBACR,OAAO;AAAA,oBACP,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK;AAAA,oBACxC,aAAY;AAAA,oBACZ,cAAW;AAAA,oBACX,WAAU;AAAA;AAAA,gBACZ;AAAA,iBACF;AAAA,cACA,oBAAC,OAAE,WAAU,kEAAiE,sBAE9E;AAAA,cACC,MAAM,WAAW,KAChB,qBAAC,OAAE,WAAU,iDAAgD;AAAA;AAAA,gBACzC;AAAA,gBAAM;AAAA,iBAC1B;AAAA,cAED,MAAM,IAAI,CAAC,MAAM;AAChB,sBAAM,WAAW,EAAE,OAAO,SAAS;AACnC,sBAAM,OAAO,EAAE;AACf,oBAAI,EAAE,MAAM;AACV,yBACE;AAAA,oBAAC;AAAA;AAAA,sBAEC,YAAQ;AAAA,sBACR,MAAM,EAAE;AAAA,sBACR,SAAS;AAAA,sBACT,WAAW,GAAG,SAAS;AAAA,sBAEvB;AAAA,4CAAC,QAAK,WAAU,wBAAuB;AAAA,wBACvC,oBAAC,UAAK,WAAU,YAAY,YAAE,MAAK;AAAA;AAAA;AAAA,oBAP9B,EAAE;AAAA,kBAQT;AAAA,gBAEJ;AACA,uBACE;AAAA,kBAAC;AAAA;AAAA,oBAEC,YAAQ;AAAA,oBACR,MAAK;AAAA,oBACL,gBAAc,WAAW,SAAS;AAAA,oBAClC,SAAS,MAAM;AACb,gCAAU,EAAE,EAAE;AACd,8BAAQ,IAAI;AAAA,oBACd;AAAA,oBACA,WAAW,GAAG,SAAS,IACrB,WACI,6DACA,mGACN;AAAA,oBAEA;AAAA,0CAAC,QAAK,WAAU,wBAAuB;AAAA,sBACvC,oBAAC,UAAK,WAAU,YAAY,YAAE,MAAK;AAAA;AAAA;AAAA,kBAf9B,EAAE;AAAA,gBAgBT;AAAA,cAEJ,CAAC;AAAA,cACD,oBAAC,SAAI,WAAU,uDACb;AAAA,gBAAC;AAAA;AAAA,kBACC,MAAM;AAAA,kBACN,SAAS;AAAA,kBACT,WAAW,GAAG,SAAS;AAAA,kBAEvB;AAAA,wCAAC,YAAS,WAAU,wBAAuB;AAAA,oBAC3C,oBAAC,UAAK,WAAU,UAAS,sBAAQ;AAAA,oBACjC,oBAAC,gBAAa,WAAU,+CAA8C;AAAA;AAAA;AAAA,cACxE,GACF;AAAA;AAAA;AAAA,QACF;AAAA,QAEA,qBAAC,SAAI,WAAU,gCACb;AAAA,+BAAC,SAAI,WAAU,4FACb;AAAA,iCAAC,SAAI,WAAU,mCACZ;AAAA,sBACC;AAAA,gBAAC;AAAA;AAAA,kBACC,MAAK;AAAA,kBACL,SAAS,MAAM;AAAA,kBACf,cAAY,WAAW,SAAS,QAAQ,UAAU;AAAA,kBAClD,WAAU;AAAA,kBAEV,8BAAC,aAAU,WAAU,UAAS;AAAA;AAAA,cAChC;AAAA,cAEF;AAAA,gBAAC;AAAA;AAAA,kBACC,cAAY,MAAM,SAAS,SAAS,QAAQ;AAAA,kBAC5C,WAAU;AAAA,kBAEV,8BAAC,aAAU,IAAG,QACX,gBAAM,SAAS,SAAS,QAAQ,YACnC;AAAA;AAAA,cACF;AAAA,eACF;AAAA,YACA,oBAAC,SAAI,WAAU,gCACb,8BAAC,SAAI,KAAK,eAAe,WAAU,qBAAoB,GACzD;AAAA,aACF;AAAA,UACA,oBAAC,SAAI,WAAU,yDACZ,qBACC,oBAAC,yBAAsB,OAAO,OAC5B,8BAAC,mBAAgB,SAAQ,SACvB,8BAAC,sBAAmB,SAAS,YAC1B,kBAAQ,SACP,QAAQ,OAAO,IAEf;AAAA,YAAC;AAAA;AAAA,cAEC,IAAI,QAAQ;AAAA;AAAA,YADP,QAAQ;AAAA,UAEf,GAEJ,GACF,GACF,GAEJ;AAAA,WACF;AAAA;AAAA;AAAA,EACF,GACF;AAEJ;","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 { 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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@withpotter/components",
3
- "version": "0.13.0-next.106",
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.106",
54
- "@withpotter/lib": "^0.13.0-next.106",
55
- "@withpotter/ui-kit": "^0.13.0-next.106",
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",