@withpotter/components 0.13.0-next.66 → 0.13.0-next.68
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-panels.js +2 -0
- package/dist/shell/settings-panels.js.map +1 -1
- package/dist/shell/settings-rows.d.ts +1 -1
- package/dist/shell/settings-rows.js +12 -0
- package/dist/shell/settings-rows.js.map +1 -1
- package/dist/sidebar/more-group.js +1 -1
- package/dist/sidebar/more-group.js.map +1 -1
- package/dist/sidebar.js +5 -5
- package/dist/sidebar.js.map +1 -1
- package/package.json +4 -4
|
@@ -19,6 +19,7 @@ import { BillingContent } from "@withpotter/pages-core/dashboard/settings/billin
|
|
|
19
19
|
import { LegalContent } from "@withpotter/pages-core/dashboard/settings/legal/_components/legal-content";
|
|
20
20
|
import { PaymentApprovalsContent } from "@withpotter/pages-core/dashboard/settings/payment-approvals/_components/payment-approvals-content";
|
|
21
21
|
import { DemoContentCard } from "@withpotter/pages-core/dashboard/settings/demo-content/_components/demo-content-card";
|
|
22
|
+
import { FeaturesContent } from "@withpotter/pages-core/dashboard/settings/features/_components/features-content";
|
|
22
23
|
const loading = () => /* @__PURE__ */ jsx(StateView, { kind: "loading", title: "Loading" });
|
|
23
24
|
function Section({
|
|
24
25
|
title,
|
|
@@ -64,6 +65,7 @@ function PreferencesPanel() {
|
|
|
64
65
|
}
|
|
65
66
|
const PANELS = {
|
|
66
67
|
general: () => /* @__PURE__ */ jsx(GeneralContent, {}),
|
|
68
|
+
features: () => /* @__PURE__ */ jsx(FeaturesContent, {}),
|
|
67
69
|
account: () => /* @__PURE__ */ jsx(AccountPanel, {}),
|
|
68
70
|
team: () => /* @__PURE__ */ jsx(TeamContent, {}),
|
|
69
71
|
notifications: () => /* @__PURE__ */ jsx(NotificationsContent, {}),
|
|
@@ -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 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 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":";
|
|
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":[]}
|
|
@@ -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' | 'account' | 'team' | 'notifications' | 'demo-content' | 'preferences' | 'payment-approvals' | 'billing' | 'usage' | 'policies' | 'legal';
|
|
15
|
+
export type SettingsRowId = 'general' | 'features' | 'account' | 'team' | 'notifications' | 'demo-content' | 'preferences' | 'payment-approvals' | 'billing' | 'usage' | 'policies' | 'legal';
|
|
16
16
|
export interface SettingsRow {
|
|
17
17
|
id: SettingsRowId;
|
|
18
18
|
name: string;
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
Scale,
|
|
10
10
|
Sparkles,
|
|
11
11
|
SlidersHorizontal,
|
|
12
|
+
ToggleLeft,
|
|
12
13
|
UserRound,
|
|
13
14
|
Users
|
|
14
15
|
} from "lucide-react";
|
|
@@ -40,6 +41,17 @@ const SETTINGS_ROWS = [
|
|
|
40
41
|
keywords: ["business name", "branding", "logo", "colours", "numbering"],
|
|
41
42
|
permission: ["canManageBusinessProfile", "canManageSettings"]
|
|
42
43
|
},
|
|
44
|
+
{
|
|
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"],
|
|
53
|
+
permission: ["canManageSettings"]
|
|
54
|
+
},
|
|
43
55
|
{
|
|
44
56
|
id: "account",
|
|
45
57
|
name: "Account",
|
|
@@ -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 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 | '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 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(rows: SettingsRow[], query: string): SettingsRow[] {\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":"AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AA2CP,MAAM,cAA2B;AAAA,EAC/B,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO,CAAC,6BAA6B;AAAA,EACrC,UAAU,CAAC,QAAQ,WAAW,WAAW,UAAU,QAAQ;AAAA,EAC3D,YAAY,CAAC,mBAAmB;AAClC;AAGA,MAAM,wBAAqC;AAAA,EACzC,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO,CAAC,uCAAuC;AAAA,EAC/C,UAAU,CAAC,YAAY,YAAY,YAAY,aAAa,SAAS;AACvE;AAEO,MAAM,gBAA+B;AAAA,EAC1C;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,UAAU,CAAC,iBAAiB,YAAY,QAAQ,WAAW,WAAW;AAAA,IACtE,YAAY,CAAC,4BAA4B,mBAAmB;AAAA,EAC9D;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,UAAU;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,UAAU,CAAC,UAAU,UAAU,SAAS,YAAY,OAAO;AAAA,IAC3D,YAAY,CAAC,eAAe;AAAA;AAAA;AAAA,IAG5B,MAAM,QAAQ,IAAI,6BAA6B;AAAA,EACjD;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,OAAO,CAAC,mCAAmC;AAAA,IAC3C,UAAU,CAAC,UAAU,aAAa,gBAAgB,UAAU,WAAW;AAAA,EACzE;AAAA,EACA;AAAA;AAAA;AAAA;AAAA,IAIE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,OAAO,CAAC,kCAAkC;AAAA,IAC1C,UAAU,CAAC,UAAU,YAAY,WAAW,SAAS,cAAc;AAAA,EACrE;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,OAAO;AAAA,MACL;AAAA,MACA;AAAA,IACF;AAAA,IACA,UAAU;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA,GAAI,QAAQ,IAAI,sCAAsC,SAClD,CAAC,qBAAqB,IACtB,CAAC;AAAA;AAAA,EAEL,GAAI,QAAQ,IAAI,iCAAiC,SAAS,CAAC,IAAI,CAAC,WAAW;AAAA;AAAA,EAE3E,GAAI,QAAQ,IAAI,+BAA+B,SAC3C,CAAC,IACD;AAAA,IACE;AAAA,MACE,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,MAAM;AAAA,MACN,OAAO,CAAC,2BAA2B;AAAA,MACnC,UAAU,CAAC,WAAW,YAAY,OAAO;AAAA,MACzC,YAAY,CAAC,mBAA4B;AAAA,IAC3C;AAAA,EACF;AAAA,EACJ;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,OAAO,CAAC,8BAA8B;AAAA,IACtC,UAAU,CAAC,WAAW,YAAY,WAAW,OAAO;AAAA,IACpD,YAAY,CAAC,mBAAmB;AAAA,EAClC;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,OAAO,CAAC,2BAA2B;AAAA,IACnC,UAAU,CAAC,SAAS,kBAAkB,UAAU;AAAA,EAClD;AACF;AAGO,MAAM,YAAY;AAClB,MAAM,YAAY;AAEzB,MAAM,SAAS,CAAC,SAAiB,KAAK,MAAM,MAAM,EAAE,CAAC;AAO9C,SAAS,WAAW,MAAgD;AACzE,QAAM,OAAO,OAAO,QAAQ,qBAAqB;AACjD,MAAI,OAAkD;AACtD,aAAW,OAAO,eAAe;AAC/B,eAAW,KAAK,IAAI,OAAO;AACzB,YAAM,OACJ,SAAS,KAAM,MAAM,yBAAyB,KAAK,WAAW,GAAG,CAAC,GAAG;AACvE,UAAI,SAAS,CAAC,QAAQ,EAAE,SAAS,KAAK;AACpC,eAAO,EAAE,IAAI,IAAI,IAAI,KAAK,EAAE,OAAO;AAAA,IACvC;AAAA,EACF;AACA,SAAO,MAAM,MAAM;AACrB;AAGO,SAAS,UAAU,MAAqB,OAA8B;AAC3E,QAAM,IAAI,MAAM,KAAK,EAAE,YAAY;AACnC,MAAI,CAAC,EAAG,QAAO;AACf,SAAO,KAAK;AAAA,IACV,CAAC,MACC,EAAE,KAAK,YAAY,EAAE,SAAS,CAAC,KAAK,EAAE,SAAS,KAAK,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAAA,EAC5E;AACF;","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 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(rows: SettingsRow[], query: string): SettingsRow[] {\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":"AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AA4CP,MAAM,cAA2B;AAAA,EAC/B,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO,CAAC,6BAA6B;AAAA,EACrC,UAAU,CAAC,QAAQ,WAAW,WAAW,UAAU,QAAQ;AAAA,EAC3D,YAAY,CAAC,mBAAmB;AAClC;AAGA,MAAM,wBAAqC;AAAA,EACzC,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO,CAAC,uCAAuC;AAAA,EAC/C,UAAU,CAAC,YAAY,YAAY,YAAY,aAAa,SAAS;AACvE;AAEO,MAAM,gBAA+B;AAAA,EAC1C;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,UAAU,CAAC,iBAAiB,YAAY,QAAQ,WAAW,WAAW;AAAA,IACtE,YAAY,CAAC,4BAA4B,mBAAmB;AAAA,EAC9D;AAAA,EACA;AAAA;AAAA;AAAA;AAAA,IAIE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,OAAO,CAAC,8BAA8B;AAAA,IACtC,UAAU,CAAC,QAAQ,SAAS,SAAS,WAAW,UAAU,WAAW,UAAU;AAAA,IAC/E,YAAY,CAAC,mBAAmB;AAAA,EAClC;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,UAAU;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,UAAU,CAAC,UAAU,UAAU,SAAS,YAAY,OAAO;AAAA,IAC3D,YAAY,CAAC,eAAe;AAAA;AAAA;AAAA,IAG5B,MAAM,QAAQ,IAAI,6BAA6B;AAAA,EACjD;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,OAAO,CAAC,mCAAmC;AAAA,IAC3C,UAAU,CAAC,UAAU,aAAa,gBAAgB,UAAU,WAAW;AAAA,EACzE;AAAA,EACA;AAAA;AAAA;AAAA;AAAA,IAIE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,OAAO,CAAC,kCAAkC;AAAA,IAC1C,UAAU,CAAC,UAAU,YAAY,WAAW,SAAS,cAAc;AAAA,EACrE;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,OAAO;AAAA,MACL;AAAA,MACA;AAAA,IACF;AAAA,IACA,UAAU;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA,GAAI,QAAQ,IAAI,sCAAsC,SAClD,CAAC,qBAAqB,IACtB,CAAC;AAAA;AAAA,EAEL,GAAI,QAAQ,IAAI,iCAAiC,SAAS,CAAC,IAAI,CAAC,WAAW;AAAA;AAAA,EAE3E,GAAI,QAAQ,IAAI,+BAA+B,SAC3C,CAAC,IACD;AAAA,IACE;AAAA,MACE,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,MAAM;AAAA,MACN,OAAO,CAAC,2BAA2B;AAAA,MACnC,UAAU,CAAC,WAAW,YAAY,OAAO;AAAA,MACzC,YAAY,CAAC,mBAA4B;AAAA,IAC3C;AAAA,EACF;AAAA,EACJ;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,OAAO,CAAC,8BAA8B;AAAA,IACtC,UAAU,CAAC,WAAW,YAAY,WAAW,OAAO;AAAA,IACpD,YAAY,CAAC,mBAAmB;AAAA,EAClC;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,OAAO,CAAC,2BAA2B;AAAA,IACnC,UAAU,CAAC,SAAS,kBAAkB,UAAU;AAAA,EAClD;AACF;AAGO,MAAM,YAAY;AAClB,MAAM,YAAY;AAEzB,MAAM,SAAS,CAAC,SAAiB,KAAK,MAAM,MAAM,EAAE,CAAC;AAO9C,SAAS,WAAW,MAAgD;AACzE,QAAM,OAAO,OAAO,QAAQ,qBAAqB;AACjD,MAAI,OAAkD;AACtD,aAAW,OAAO,eAAe;AAC/B,eAAW,KAAK,IAAI,OAAO;AACzB,YAAM,OACJ,SAAS,KAAM,MAAM,yBAAyB,KAAK,WAAW,GAAG,CAAC,GAAG;AACvE,UAAI,SAAS,CAAC,QAAQ,EAAE,SAAS,KAAK;AACpC,eAAO,EAAE,IAAI,IAAI,IAAI,KAAK,EAAE,OAAO;AAAA,IACvC;AAAA,EACF;AACA,SAAO,MAAM,MAAM;AACrB;AAGO,SAAS,UAAU,MAAqB,OAA8B;AAC3E,QAAM,IAAI,MAAM,KAAK,EAAE,YAAY;AACnC,MAAI,CAAC,EAAG,QAAO;AACf,SAAO,KAAK;AAAA,IACV,CAAC,MACC,EAAE,KAAK,YAAY,EAAE,SAAS,CAAC,KAAK,EAAE,SAAS,KAAK,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAAA,EAC5E;AACF;","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MoreHorizontal } from "lucide-react";
|
|
2
2
|
import { universalCoreModules } from "@withpotter/lib/use-platform-modules";
|
|
3
|
-
const MORE_IDS = [
|
|
3
|
+
const MORE_IDS = [];
|
|
4
4
|
function buildMoreGroup(enabledBehaviors, edits = {}) {
|
|
5
5
|
const items = [];
|
|
6
6
|
const hidden = new Set(edits.hiddenModules ?? []);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/sidebar/more-group.ts"],"sourcesContent":["import { MoreHorizontal } from 'lucide-react';\nimport { universalCoreModules } from '@withpotter/lib/use-platform-modules';\nimport type { NavGroup, NavItem } from './config';\n\n/**\n * What More holds, in order: the places a business uses now and then that have\n * no row of their own in the rail. Each keeps its behaviour gate from\n * `universalCoreModules
|
|
1
|
+
{"version":3,"sources":["../../src/sidebar/more-group.ts"],"sourcesContent":["import { MoreHorizontal } from 'lucide-react';\nimport { universalCoreModules } from '@withpotter/lib/use-platform-modules';\nimport type { NavGroup, NavItem } from './config';\n\n/**\n * What More holds, in order: the places a business uses now and then that have\n * no row of their own in the rail. Each keeps its behaviour gate from\n * `universalCoreModules`.\n *\n * Empty since Blog moved under Website (it was listed here AND as the last\n * child of Website, so a business with a blog got it twice). The hub is kept,\n * rather than deleted, because the rail's edit list, its row id and its place\n * at the bottom of the rail are all wired to it; with nothing to hold,\n * `buildMoreGroup` returns null and no row is drawn. A place that belongs to\n * no group goes here.\n */\nconst MORE_IDS: readonly string[] = [];\n\n/**\n * MORE — the last row of the general list, as an ordinary hub.\n *\n * It is a `NavGroup` like Marketing or Appointments and is drawn by the same\n * `renderHub`, so it opens in place with the same chevron, the same growing\n * list and the same highlight on the child you are on, and it shares their\n * one-open-at-a-time rule. There is no menu or pop-up of its own to keep in\n * step with them. `alwaysHub` keeps it a dropdown even when a business is left\n * with a single place to show.\n *\n * It honours the same hide, rename and order settings as the rest of the rail\n * (`hiddenModules`, `moduleLabels`, `moduleOrder`), so editing the sidebar\n * reaches it too.\n *\n * Null when none of its places apply, so the row is not drawn empty.\n */\nexport function buildMoreGroup(\n enabledBehaviors: readonly string[] | null | undefined,\n edits: {\n hiddenModules?: readonly string[];\n moduleLabels?: Record<string, string>;\n moduleOrder?: readonly string[];\n } = {},\n): NavGroup | null {\n const items: NavItem[] = [];\n const hidden = new Set(edits.hiddenModules ?? []);\n // The merchant's own order, where they gave one; otherwise ours.\n const order = [...MORE_IDS].sort((a, b) => {\n const at = edits.moduleOrder?.indexOf(a) ?? -1;\n const bt = edits.moduleOrder?.indexOf(b) ?? -1;\n return (at === -1 ? 999 : at) - (bt === -1 ? 999 : bt);\n });\n for (const id of order) {\n if (hidden.has(id)) continue;\n const m = universalCoreModules.find((x) => x.id === id);\n if (!m) continue;\n const need = m.requiredBehavior;\n if (need && enabledBehaviors) {\n const any = Array.isArray(need) ? need : [need];\n if (!any.some((b) => enabledBehaviors.includes(b))) continue;\n }\n items.push({\n id: m.id,\n name: edits.moduleLabels?.[m.id]?.trim() || m.name,\n href: m.href,\n icon: m.icon,\n });\n }\n if (items.length === 0) return null;\n return {\n id: 'more',\n label: 'More',\n icon: MoreHorizontal,\n items,\n alwaysHub: true,\n };\n}\n"],"mappings":"AAAA,SAAS,sBAAsB;AAC/B,SAAS,4BAA4B;AAerC,MAAM,WAA8B,CAAC;AAkB9B,SAAS,eACd,kBACA,QAII,CAAC,GACY;AACjB,QAAM,QAAmB,CAAC;AAC1B,QAAM,SAAS,IAAI,IAAI,MAAM,iBAAiB,CAAC,CAAC;AAEhD,QAAM,QAAQ,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,GAAG,MAAM;AACzC,UAAM,KAAK,MAAM,aAAa,QAAQ,CAAC,KAAK;AAC5C,UAAM,KAAK,MAAM,aAAa,QAAQ,CAAC,KAAK;AAC5C,YAAQ,OAAO,KAAK,MAAM,OAAO,OAAO,KAAK,MAAM;AAAA,EACrD,CAAC;AACD,aAAW,MAAM,OAAO;AACtB,QAAI,OAAO,IAAI,EAAE,EAAG;AACpB,UAAM,IAAI,qBAAqB,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE;AACtD,QAAI,CAAC,EAAG;AACR,UAAM,OAAO,EAAE;AACf,QAAI,QAAQ,kBAAkB;AAC5B,YAAM,MAAM,MAAM,QAAQ,IAAI,IAAI,OAAO,CAAC,IAAI;AAC9C,UAAI,CAAC,IAAI,KAAK,CAAC,MAAM,iBAAiB,SAAS,CAAC,CAAC,EAAG;AAAA,IACtD;AACA,UAAM,KAAK;AAAA,MACT,IAAI,EAAE;AAAA,MACN,MAAM,MAAM,eAAe,EAAE,EAAE,GAAG,KAAK,KAAK,EAAE;AAAA,MAC9C,MAAM,EAAE;AAAA,MACR,MAAM,EAAE;AAAA,IACV,CAAC;AAAA,EACH;AACA,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,MAAM;AAAA,IACN;AAAA,IACA,WAAW;AAAA,EACb;AACF;","names":[]}
|
package/dist/sidebar.js
CHANGED
|
@@ -82,18 +82,18 @@ function Sidebar({
|
|
|
82
82
|
);
|
|
83
83
|
const salesChannel = isSalesChannel(tenantSettings?.salesChannel) ? tenantSettings.salesChannel : null;
|
|
84
84
|
const inPersonOnly = salesChannel === "in_person";
|
|
85
|
+
const enabledBehaviors = useMemo(
|
|
86
|
+
() => resolveEnabledBehaviors(tenantSettings, template),
|
|
87
|
+
[tenantSettings, template]
|
|
88
|
+
);
|
|
85
89
|
const { coreNavigation } = usePlatformModules({
|
|
86
90
|
template,
|
|
87
91
|
dashboardState,
|
|
88
|
-
enabledBehaviors
|
|
92
|
+
enabledBehaviors,
|
|
89
93
|
hiddenModules: tenantSettings?.hiddenModules,
|
|
90
94
|
moduleLabels,
|
|
91
95
|
moduleOrder: tenantSettings?.moduleOrder
|
|
92
96
|
});
|
|
93
|
-
const enabledBehaviors = useMemo(
|
|
94
|
-
() => resolveEnabledBehaviors(tenantSettings, template),
|
|
95
|
-
[tenantSettings, template]
|
|
96
|
-
);
|
|
97
97
|
const intentConfig = useIntentConfig();
|
|
98
98
|
const { can, isOwner, loading: permissionsLoading } = usePermissions();
|
|
99
99
|
const capabilities = useCapabilities();
|
package/dist/sidebar.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/sidebar.tsx"],"sourcesContent":["'use client';\n\nimport { useCallback, useMemo, useState } from 'react';\nimport { PanelLeftClose, PanelLeftOpen } from 'lucide-react';\nimport { useRouter, useSearchParams } from 'next/navigation';\nimport { usePathname } from '@withpotter/lib/use-pathname';\nimport { ProductSwitcher } from './shell/product-switcher';\nimport { Logo } from '@withpotter/ui-kit/logo';\nimport './shell/shell.css';\nimport { useAuth } from '@withpotter/lib/auth-context';\nimport { useCapabilities } from '@withpotter/lib/capability-context';\nimport { effectiveModuleLabels } from '@withpotter/lib/effective-module-labels';\nimport { useCurrentTemplate } from '@withpotter/lib/template-context';\nimport { SidebarFooter } from '@withpotter/components/shell/sidebar-footer';\nimport { usePlatformModules } from '@withpotter/lib/use-platform-modules';\nimport { resolveEnabledBehaviors } from '@withpotter/lib/resolve-enabled-behaviors';\nimport { useIntentConfig } from '@withpotter/lib/intents/use-intent-config';\nimport { usePermissions } from '@withpotter/lib/use-permissions';\nimport { isSalesChannel } from '@withpotter/hooks/use-sales-channel';\nimport {\n ACCOUNT_ROUTE_PREFIXES,\n intentKeySet,\n type NavGroup,\n type NavItem,\n} from './sidebar/config';\nimport {\n isActive as isActiveFor,\n resolveActiveHref,\n type SidebarLocation,\n} from './sidebar/active-href';\nimport { buildInboxRow } from './sidebar/inbox-row';\nimport { RailBody } from './sidebar/rail-body';\nimport { EditableRailBody } from './sidebar/rail-editor';\nimport { usableHidden, type SectionId } from './sidebar/rail-edits';\nimport { buildNavGroups } from './sidebar/build-nav-groups';\nimport { buildRailRows } from './sidebar/rail-rows';\nimport {\n buildOrdersPaymentsHub,\n findOrdersRow,\n} from './sidebar/orders-payments-hub';\nimport { splitWorkRows } from './sidebar/work-rows';\nimport { buildMoreGroup } from './sidebar/more-group';\nimport { buildInventoryHub } from './sidebar/inventory-hub';\nimport { SidebarAsk } from './shell/sidebar-ask';\nimport { railContextFor } from './sidebar/route-context';\nimport { useNewNavRows } from './sidebar/use-new-nav-rows';\nimport { useResourceRows } from './sidebar/use-resource-rows';\nimport {\n FIXED_GROUP_IDS,\n flattenWorkGroups,\n groupItems,\n} from './sidebar/flatten-work-groups';\nimport { SettingsNav } from './sidebar/settings-nav';\nimport { createNavRenderers } from './sidebar/nav-renderers';\nimport { useNavBadges } from './sidebar/use-nav-badges';\nimport { useOpenHub } from './sidebar/use-open-hub';\n\nexport function Sidebar({\n collapsed = false,\n onToggleCollapse,\n}: {\n collapsed?: boolean;\n /** Collapses the rail; the button for it sits beside Search in the head. */\n onToggleCollapse?: () => void;\n}) {\n const pathname = usePathname();\n const searchParams = useSearchParams();\n const router = useRouter();\n /** \"Customize sidebar\" is on: the rail itself is editable in place. */\n const [editing, setEditing] = useState<SectionId | null>(null);\n const startEditing = useCallback((s: SectionId) => setEditing(s), []);\n const stopEditing = useCallback(() => setEditing(null), []);\n\n /**\n * Warm a route when the pointer arrives, not when it clicks: every link here\n * is `prefetch={false}`, so a click paid the full cost with nothing ready.\n * Prefetches the URL actually navigated to, query and all, because the\n * adaptive rows are intent-scoped (`/dashboard/catalog?intent=...`).\n */\n const warm = useCallback(\n (href: string) => {\n if (!href || href.startsWith('http')) return;\n router.prefetch(href);\n },\n [router],\n );\n const { tenant } = useAuth();\n const { inSettings, isAccountContext, businessContext } =\n railContextFor(pathname);\n // Editing in place needs the full rail, so it ends when the rail collapses or you\n // go into settings (and does not come back on its own when you return).\n if (editing && (collapsed || inSettings)) setEditing(null);\n // \"Back home\" means home: the dashboard, not wherever you last were.\n const settingsBack = () => router.push('/dashboard');\n const { hubChoice, setHubOpen } = useOpenHub(pathname);\n\n const { template, dashboardState } = useCurrentTemplate(tenant?.templateId);\n\n const tenantSettings = tenant?.settings as\n Record<string, unknown> | undefined;\n const moduleLabels = effectiveModuleLabels(tenantSettings);\n const hiddenModules = tenantSettings?.hiddenModules as string[] | undefined;\n /**\n * What the groups are built without. While editing, nothing is hidden, so a\n * hidden row is still on the rail (dimmed, to be shown again); the rail\n * itself drops hidden rows otherwise. Essential rows are never hidden, so a\n * stale `inbox` entry in the list does not hide the Inbox.\n */\n const navHidden = useMemo(\n () => (editing ? undefined : usableHidden(hiddenModules)),\n [editing, hiddenModules],\n );\n\n // How the tenant takes money (from onboarding). Refines the nav: an\n // in-person-only business doesn't ship, so fulfillment surfaces are hidden.\n // `null` (unset) never hides anything — this only ever removes what genuinely\n // doesn't apply.\n const salesChannel = isSalesChannel(tenantSettings?.salesChannel)\n ? tenantSettings.salesChannel\n : null;\n const inPersonOnly = salesChannel === 'in_person';\n\n // Still use usePlatformModules for behavior resolution and resource sidebar\n const { coreNavigation } = usePlatformModules({\n template,\n dashboardState,\n enabledBehaviors: tenantSettings?.enabledBehaviors as string[] | undefined,\n hiddenModules: tenantSettings?.hiddenModules as string[] | undefined,\n moduleLabels,\n moduleOrder: tenantSettings?.moduleOrder as string[] | undefined,\n });\n\n const enabledBehaviors = useMemo(\n () => resolveEnabledBehaviors(tenantSettings, template),\n [tenantSettings, template],\n );\n\n const intentConfig = useIntentConfig();\n const { can, isOwner, loading: permissionsLoading } = usePermissions();\n const capabilities = useCapabilities();\n\n /**\n * Which archetypes the CURRENT URL is narrowed to, verbatim.\n *\n * Not `intentConfig.primary.key`: that falls back to sessionStorage when the\n * URL carries no `?intent=`, so which row lit up depended on where you had\n * last been rather than where you are. And not a single key either — a\n * collapsed row carries every archetype behind it.\n */\n const currentIntent =\n intentConfig.mode === 'multi'\n ? intentKeySet(searchParams.get('intent'))\n : null;\n\n const location: SidebarLocation = {\n pathname,\n params: searchParams,\n currentIntent,\n };\n const isActive = (href: string) => isActiveFor(href, location);\n\n const getGroupItems = groupItems;\n\n // Build navigation groups. Memoized on its real inputs (NOT pathname) so the\n // whole tree — intent resolution, permission filtering, label overrides —\n // doesn't rebuild on every navigation. Only activeHref/isActive depend on the\n // route, and they're cheap.\n const declaredGroups: NavGroup[] = useMemo(\n () =>\n buildNavGroups({\n hiddenModules: navHidden,\n moduleLabels,\n enabledBehaviors,\n inPersonOnly,\n intentConfig,\n coreNavigation,\n template,\n can,\n isOwner,\n permissionsLoading,\n hasCapability: capabilities.has,\n }),\n [\n navHidden,\n moduleLabels,\n enabledBehaviors,\n inPersonOnly,\n intentConfig,\n coreNavigation,\n template,\n can,\n isOwner,\n permissionsLoading,\n capabilities,\n ],\n );\n\n /**\n * The bookable-things row, told apart: Courts and Coaches rather than one\n * row named after whichever shape the archetype guessed. Only expands when\n * this business actually has groups; see `use-resource-rows.ts`.\n */\n const groups = useResourceRows(declaredGroups);\n\n // Flat render model (console-style): work-surface items sit unlabeled with\n // Home; each fixed spine group is either a single link, or a hub that reveals\n // its children inline (disclosure, no chevron); Settings is pinned in the foot.\n const FIXED_IDS = FIXED_GROUP_IDS;\n /**\n * A flat group is a row, not just a container for rows.\n *\n * This took each non-fixed group's ITEMS and dropped the group itself, which\n * is right for an archetype group — Dine-in and Events are labels, and their\n * children are the destinations. It is wrong for the hoisted shared rows.\n *\n * `Catalog` and `Orders` come out of the collapse as groups carrying their\n * own `href`, because they belong to no archetype: Orders has no children at\n * all, and Catalog has exactly one (Collections). So this rendered **nothing**\n * for Orders and **only Collections** for Catalog — the two most-used\n * destinations in the app were absent from the sidebar of every\n * multi-archetype business, and the archetype rows beside them made it look\n * intentional.\n *\n * Every test in the suite asserts on what `buildNavGroups` returns, which was\n * correct the whole time. Nothing looked at what the sidebar drew.\n */\n // Shared, so the test and `scripts/archetype-shape.mts` see what is drawn\n // here rather than each modelling it. See `flatten-work-groups.ts`.\n const workItems = flattenWorkGroups(groups);\n const spineGroups = groups.filter(\n (g) => FIXED_IDS.has(g.id) && g.id !== 'settings',\n );\n\n // More is built here, ahead of the active-row list, because that list has to\n // include its rows: built after it they are never candidates, so nothing lights\n // on their pages and the hub never opens itself.\n const moduleOrder = tenantSettings?.moduleOrder as string[] | undefined;\n const moreGroup = useMemo(\n () =>\n buildMoreGroup(enabledBehaviors, {\n hiddenModules: navHidden,\n moduleLabels,\n moduleOrder,\n }),\n [enabledBehaviors, navHidden, moduleLabels, moduleOrder],\n );\n\n // Ahead of the active-row list for the same reason as More: its href has to\n // be a candidate or nothing lights on /dashboard/inbox.\n const inboxRow = buildInboxRow({\n hiddenModules: navHidden,\n moduleLabels,\n can,\n isOwner,\n permissionsLoading,\n });\n\n // Exactly one row carries the active highlight. If the path is inside a hub,\n // the hub's landing row lights up (its sub-pages are tabs, not sidebar rows);\n // otherwise the deepest matching flat row wins. Keeping it unique lets the\n // shared motion indicator spring cleanly between rows.\n // The deepest matching row wins — including hub CHILDREN, now that sub-pages\n // render inline in the sidebar rather than only as HubTabs. So the actual\n // current sub-page carries the highlight, not just the hub landing.\n // Exactly one row carries the active highlight — see `active-href.ts` for\n // how it is chosen. The list is every href the sidebar draws, hub children\n // included: Collections is attached as a child of the catalogue row, so when\n // children were missing from this list nothing lit on /dashboard/collections\n // and the hub holding the only row that leads there stayed shut.\n const activeHref = useMemo(\n () =>\n resolveActiveHref(\n [\n '/dashboard',\n ...(inboxRow ? [inboxRow.href] : []),\n ...workItems.flatMap((i) => [\n i.href,\n ...(i.children ?? []).map((c) => c.href),\n ]),\n ...spineGroups.flatMap((g) => [\n g.href,\n ...getGroupItems(g).map((i) => i.href),\n ]),\n ...(moreGroup ? getGroupItems(moreGroup).map((i) => i.href) : []),\n ].filter((h): h is string => !!h),\n location,\n ),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [pathname, searchParams, groups, moreGroup, inboxRow?.href],\n );\n\n const { inventoryHub, catalogMatches, collectionsItem, collectionsNested } =\n buildInventoryHub(workItems);\n\n /**\n * When the Inventory hub exists, catalog + collections move inside it; else\n * both still render flat, right where they were.\n *\n * THE ROWS THEMSELVES, not their ids. An id says what a row is called by its\n * archetype, not which screen it opens, and `subscriptions` calls its Plans\n * row `catalog` too — so a business running memberships beside anything with\n * a real catalogue had **Plans deleted from its sidebar**, in all nine\n * two-archetype combinations. Collections is consumed only when the hub took\n * it: dropping it always cost the row to a business with no catalogue row.\n */\n const ordersItem = findOrdersRow(workItems);\n const ordersPaymentsHub = buildOrdersPaymentsHub({\n ordersItem,\n moneyGroup: spineGroups.find((g) => g.id === 'payments') ?? null,\n can,\n });\n\n const consumed = new Set<unknown>([\n ...(inventoryHub\n ? [...catalogMatches, ...(collectionsItem ? [collectionsItem] : [])]\n : []),\n // With no catalogue row to hold it, Collections is drawn as a row of its\n // own by the rail; it must not also be drawn as a work row.\n ...(!inventoryHub && collectionsItem && !collectionsNested\n ? [collectionsItem]\n : []),\n ...(ordersPaymentsHub && ordersItem ? [ordersItem] : []),\n ]);\n /* General (unlabeled) leads; the rest drop into Operations. The rule is in\n `work-rows`. */\n const { primaryWork, extraWork } = splitWorkRows({\n workItems,\n consumed,\n hasInventoryHub: !!inventoryHub,\n });\n\n const hubById = new Map(spineGroups.map((g) => [g.id, g]));\n const operationsHub = hubById.get('operations');\n\n const badges = useNavBadges();\n /* Rows that appeared because the business added something this week — see\n `use-new-nav-rows`. */\n const newRows = useNewNavRows();\n\n // Plain render helpers (NOT inner components) — defining components inside\n // the render remounts them every pass, which breaks motion's shared-layout\n // highlight (it would jump from stale positions). Functions returning JSX\n // keep the elements stable under the parent, so the spring animates cleanly.\n const {\n renderHighlight,\n renderIcon,\n renderRow,\n renderHub,\n renderWorkRow,\n ownsRoute,\n } = createNavRenderers({\n collapsed,\n warm,\n activeHref,\n isActive,\n hubChoice,\n setHubOpen,\n getGroupItems,\n badges,\n isNew: (id: string) => newRows.has(id),\n });\n\n const { moneyRow, manageRows, growRows } = buildRailRows({\n inventoryHub,\n payHub: ordersPaymentsHub ?? hubById.get('payments'),\n primaryWork,\n extraWork,\n collectionsItem,\n collectionsNested,\n customersHub: hubById.get('customers'),\n hardwareHub: hubById.get('hardware'),\n operationsHub,\n growHubs: [\n hubById.get('marketing'),\n hubById.get('analytics'),\n hubById.get('website'),\n ],\n renderHub,\n renderRow,\n renderWorkRow,\n });\n\n /** What the rail is drawn from, in both modes. See `rail-body.tsx`. */\n const railProps = {\n collapsed,\n homeActive: activeHref === '/dashboard',\n toolkitActive: pathname.startsWith('/dashboard/toolkit'),\n highlight: renderHighlight,\n inboxRow,\n renderRow,\n renderHub,\n renderIcon,\n ownsRoute,\n moneyRow,\n manageRows,\n growRows,\n moreGroup,\n };\n\n return (\n <aside\n className={`pv-shell sidebar${collapsed ? ' collapsed' : ''} hidden lg:flex`}\n >\n {/* THE HEAD OF THE RAIL, one flat row (John, 2026-10-04): the business's\n mark and name at the left, the Search and Ask icons at the right. It\n was a bordered card with a second line, and Search a row of its own\n beneath it. */}\n {/* A branded box can put its own logo at the top of the rail\n (NEXT_PUBLIC_BOX_LOGO_IN_SHELL). Left out when the rail is collapsed,\n where there is no room for a wordmark. */}\n {process.env.NEXT_PUBLIC_BOX_LOGO_IN_SHELL === 'true' &&\n process.env.NEXT_PUBLIC_BOX_LOGO &&\n !collapsed && (\n <div className=\"px-3 pb-1 pt-3\">\n <Logo size=\"sm\" theme=\"light\" />\n </div>\n )}\n <div className=\"side-head\">\n {collapsed && onToggleCollapse && (\n /* The way back out of a collapsed rail: laid over the business\n mark, invisible until the pointer is on it, then the expand icon\n (John, 2026-10-11). */\n <button\n type=\"button\"\n onClick={onToggleCollapse}\n className=\"side-head-expand\"\n title=\"Expand sidebar (⌘\\)\"\n aria-label=\"Expand sidebar\"\n >\n <PanelLeftOpen width={18} height={18} />\n </button>\n )}\n {!businessContext ? (\n <ProductSwitcher variant=\"head\" />\n ) : (\n <div className=\"flex-1\" />\n )}\n {/* Search, at the right of the head, after Linear (John, 2026-10-11).\n It was a row under Home; it is an action, not a place, so it leaves\n the list. */}\n <div className=\"side-head-actions\">\n <SidebarAsk collapsed={collapsed} variant=\"icon\" />\n {onToggleCollapse && !collapsed && (\n <button\n type=\"button\"\n onClick={onToggleCollapse}\n className=\"side-head-icon\"\n title=\"Collapse sidebar (⌘\\)\"\n aria-label=\"Collapse sidebar\"\n >\n <PanelLeftClose width={16} height={16} />\n </button>\n )}\n </div>\n </div>\n\n {/* Navigation */}\n <nav className=\"nav\" aria-label=\"Main navigation\">\n {/* No loading skeleton: the general nav renders without the template. */}\n\n {/* General nav ⇄ Settings nav: settings drills in place. The pane is\n keyed on the boundary so it remounts and slides in (from the right\n going into settings, from the left coming back). Enter-only — no\n exit — so the nav never collapses and re-flows from the bottom. */}\n <div\n key={inSettings ? 'settings' : 'main'}\n className={'nav-swap ' + (inSettings ? 'from-right' : 'from-left')}\n >\n {inSettings ? (\n <SettingsNav\n businessContext={businessContext}\n pathname={pathname}\n collapsed={collapsed}\n enabledBehaviors={enabledBehaviors}\n tenant={tenant}\n onBack={settingsBack}\n />\n ) : editing ? (\n <EditableRailBody\n {...railProps}\n onDone={stopEditing}\n editSection={editing}\n savedLabels={moduleLabels}\n storedLabels={\n tenantSettings?.moduleLabels as\n Record<string, string> | undefined\n }\n />\n ) : (\n <RailBody\n {...railProps}\n onConfigure={startEditing}\n edits={{\n hidden: hiddenModules,\n order: moduleOrder,\n labels: moduleLabels,\n sectionLabels: tenantSettings?.sectionLabels as\n Record<string, string> | undefined,\n }}\n />\n )}\n </div>\n </nav>\n\n <SidebarFooter collapsed={collapsed} />\n </aside>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/sidebar.tsx"],"sourcesContent":["'use client';\n\nimport { useCallback, useMemo, useState } from 'react';\nimport { PanelLeftClose, PanelLeftOpen } from 'lucide-react';\nimport { useRouter, useSearchParams } from 'next/navigation';\nimport { usePathname } from '@withpotter/lib/use-pathname';\nimport { ProductSwitcher } from './shell/product-switcher';\nimport { Logo } from '@withpotter/ui-kit/logo';\nimport './shell/shell.css';\nimport { useAuth } from '@withpotter/lib/auth-context';\nimport { useCapabilities } from '@withpotter/lib/capability-context';\nimport { effectiveModuleLabels } from '@withpotter/lib/effective-module-labels';\nimport { useCurrentTemplate } from '@withpotter/lib/template-context';\nimport { SidebarFooter } from '@withpotter/components/shell/sidebar-footer';\nimport { usePlatformModules } from '@withpotter/lib/use-platform-modules';\nimport { resolveEnabledBehaviors } from '@withpotter/lib/resolve-enabled-behaviors';\nimport { useIntentConfig } from '@withpotter/lib/intents/use-intent-config';\nimport { usePermissions } from '@withpotter/lib/use-permissions';\nimport { isSalesChannel } from '@withpotter/hooks/use-sales-channel';\nimport {\n ACCOUNT_ROUTE_PREFIXES,\n intentKeySet,\n type NavGroup,\n type NavItem,\n} from './sidebar/config';\nimport {\n isActive as isActiveFor,\n resolveActiveHref,\n type SidebarLocation,\n} from './sidebar/active-href';\nimport { buildInboxRow } from './sidebar/inbox-row';\nimport { RailBody } from './sidebar/rail-body';\nimport { EditableRailBody } from './sidebar/rail-editor';\nimport { usableHidden, type SectionId } from './sidebar/rail-edits';\nimport { buildNavGroups } from './sidebar/build-nav-groups';\nimport { buildRailRows } from './sidebar/rail-rows';\nimport {\n buildOrdersPaymentsHub,\n findOrdersRow,\n} from './sidebar/orders-payments-hub';\nimport { splitWorkRows } from './sidebar/work-rows';\nimport { buildMoreGroup } from './sidebar/more-group';\nimport { buildInventoryHub } from './sidebar/inventory-hub';\nimport { SidebarAsk } from './shell/sidebar-ask';\nimport { railContextFor } from './sidebar/route-context';\nimport { useNewNavRows } from './sidebar/use-new-nav-rows';\nimport { useResourceRows } from './sidebar/use-resource-rows';\nimport {\n FIXED_GROUP_IDS,\n flattenWorkGroups,\n groupItems,\n} from './sidebar/flatten-work-groups';\nimport { SettingsNav } from './sidebar/settings-nav';\nimport { createNavRenderers } from './sidebar/nav-renderers';\nimport { useNavBadges } from './sidebar/use-nav-badges';\nimport { useOpenHub } from './sidebar/use-open-hub';\n\nexport function Sidebar({\n collapsed = false,\n onToggleCollapse,\n}: {\n collapsed?: boolean;\n /** Collapses the rail; the button for it sits beside Search in the head. */\n onToggleCollapse?: () => void;\n}) {\n const pathname = usePathname();\n const searchParams = useSearchParams();\n const router = useRouter();\n /** \"Customize sidebar\" is on: the rail itself is editable in place. */\n const [editing, setEditing] = useState<SectionId | null>(null);\n const startEditing = useCallback((s: SectionId) => setEditing(s), []);\n const stopEditing = useCallback(() => setEditing(null), []);\n\n /**\n * Warm a route when the pointer arrives, not when it clicks: every link here\n * is `prefetch={false}`, so a click paid the full cost with nothing ready.\n * Prefetches the URL actually navigated to, query and all, because the\n * adaptive rows are intent-scoped (`/dashboard/catalog?intent=...`).\n */\n const warm = useCallback(\n (href: string) => {\n if (!href || href.startsWith('http')) return;\n router.prefetch(href);\n },\n [router],\n );\n const { tenant } = useAuth();\n const { inSettings, isAccountContext, businessContext } =\n railContextFor(pathname);\n // Editing in place needs the full rail, so it ends when the rail collapses or you\n // go into settings (and does not come back on its own when you return).\n if (editing && (collapsed || inSettings)) setEditing(null);\n // \"Back home\" means home: the dashboard, not wherever you last were.\n const settingsBack = () => router.push('/dashboard');\n const { hubChoice, setHubOpen } = useOpenHub(pathname);\n\n const { template, dashboardState } = useCurrentTemplate(tenant?.templateId);\n\n const tenantSettings = tenant?.settings as\n Record<string, unknown> | undefined;\n const moduleLabels = effectiveModuleLabels(tenantSettings);\n const hiddenModules = tenantSettings?.hiddenModules as string[] | undefined;\n /**\n * What the groups are built without. While editing, nothing is hidden, so a\n * hidden row is still on the rail (dimmed, to be shown again); the rail\n * itself drops hidden rows otherwise. Essential rows are never hidden, so a\n * stale `inbox` entry in the list does not hide the Inbox.\n */\n const navHidden = useMemo(\n () => (editing ? undefined : usableHidden(hiddenModules)),\n [editing, hiddenModules],\n );\n\n // How the tenant takes money (from onboarding). Refines the nav: an\n // in-person-only business doesn't ship, so fulfillment surfaces are hidden.\n // `null` (unset) never hides anything — this only ever removes what genuinely\n // doesn't apply.\n const salesChannel = isSalesChannel(tenantSettings?.salesChannel)\n ? tenantSettings.salesChannel\n : null;\n const inPersonOnly = salesChannel === 'in_person';\n\n // The behaviors the business really has: its model, the merchant's own\n // switches (Settings > Features) and the template's defaults. Resolved BEFORE\n // the core list is built, because that list gates its modules on them. It was\n // handed the raw stored `enabledBehaviors` instead, which knows nothing of an\n // override, so a Blog switched on in Features never reached Website's Blog\n // row and showed only in More.\n const enabledBehaviors = useMemo(\n () => resolveEnabledBehaviors(tenantSettings, template),\n [tenantSettings, template],\n );\n\n // Still use usePlatformModules for behavior resolution and resource sidebar\n const { coreNavigation } = usePlatformModules({\n template,\n dashboardState,\n enabledBehaviors,\n hiddenModules: tenantSettings?.hiddenModules as string[] | undefined,\n moduleLabels,\n moduleOrder: tenantSettings?.moduleOrder as string[] | undefined,\n });\n\n const intentConfig = useIntentConfig();\n const { can, isOwner, loading: permissionsLoading } = usePermissions();\n const capabilities = useCapabilities();\n\n /**\n * Which archetypes the CURRENT URL is narrowed to, verbatim.\n *\n * Not `intentConfig.primary.key`: that falls back to sessionStorage when the\n * URL carries no `?intent=`, so which row lit up depended on where you had\n * last been rather than where you are. And not a single key either — a\n * collapsed row carries every archetype behind it.\n */\n const currentIntent =\n intentConfig.mode === 'multi'\n ? intentKeySet(searchParams.get('intent'))\n : null;\n\n const location: SidebarLocation = {\n pathname,\n params: searchParams,\n currentIntent,\n };\n const isActive = (href: string) => isActiveFor(href, location);\n\n const getGroupItems = groupItems;\n\n // Build navigation groups. Memoized on its real inputs (NOT pathname) so the\n // whole tree — intent resolution, permission filtering, label overrides —\n // doesn't rebuild on every navigation. Only activeHref/isActive depend on the\n // route, and they're cheap.\n const declaredGroups: NavGroup[] = useMemo(\n () =>\n buildNavGroups({\n hiddenModules: navHidden,\n moduleLabels,\n enabledBehaviors,\n inPersonOnly,\n intentConfig,\n coreNavigation,\n template,\n can,\n isOwner,\n permissionsLoading,\n hasCapability: capabilities.has,\n }),\n [\n navHidden,\n moduleLabels,\n enabledBehaviors,\n inPersonOnly,\n intentConfig,\n coreNavigation,\n template,\n can,\n isOwner,\n permissionsLoading,\n capabilities,\n ],\n );\n\n /**\n * The bookable-things row, told apart: Courts and Coaches rather than one\n * row named after whichever shape the archetype guessed. Only expands when\n * this business actually has groups; see `use-resource-rows.ts`.\n */\n const groups = useResourceRows(declaredGroups);\n\n // Flat render model (console-style): work-surface items sit unlabeled with\n // Home; each fixed spine group is either a single link, or a hub that reveals\n // its children inline (disclosure, no chevron); Settings is pinned in the foot.\n const FIXED_IDS = FIXED_GROUP_IDS;\n /**\n * A flat group is a row, not just a container for rows.\n *\n * This took each non-fixed group's ITEMS and dropped the group itself, which\n * is right for an archetype group — Dine-in and Events are labels, and their\n * children are the destinations. It is wrong for the hoisted shared rows.\n *\n * `Catalog` and `Orders` come out of the collapse as groups carrying their\n * own `href`, because they belong to no archetype: Orders has no children at\n * all, and Catalog has exactly one (Collections). So this rendered **nothing**\n * for Orders and **only Collections** for Catalog — the two most-used\n * destinations in the app were absent from the sidebar of every\n * multi-archetype business, and the archetype rows beside them made it look\n * intentional.\n *\n * Every test in the suite asserts on what `buildNavGroups` returns, which was\n * correct the whole time. Nothing looked at what the sidebar drew.\n */\n // Shared, so the test and `scripts/archetype-shape.mts` see what is drawn\n // here rather than each modelling it. See `flatten-work-groups.ts`.\n const workItems = flattenWorkGroups(groups);\n const spineGroups = groups.filter(\n (g) => FIXED_IDS.has(g.id) && g.id !== 'settings',\n );\n\n // More is built here, ahead of the active-row list, because that list has to\n // include its rows: built after it they are never candidates, so nothing lights\n // on their pages and the hub never opens itself.\n const moduleOrder = tenantSettings?.moduleOrder as string[] | undefined;\n const moreGroup = useMemo(\n () =>\n buildMoreGroup(enabledBehaviors, {\n hiddenModules: navHidden,\n moduleLabels,\n moduleOrder,\n }),\n [enabledBehaviors, navHidden, moduleLabels, moduleOrder],\n );\n\n // Ahead of the active-row list for the same reason as More: its href has to\n // be a candidate or nothing lights on /dashboard/inbox.\n const inboxRow = buildInboxRow({\n hiddenModules: navHidden,\n moduleLabels,\n can,\n isOwner,\n permissionsLoading,\n });\n\n // Exactly one row carries the active highlight. If the path is inside a hub,\n // the hub's landing row lights up (its sub-pages are tabs, not sidebar rows);\n // otherwise the deepest matching flat row wins. Keeping it unique lets the\n // shared motion indicator spring cleanly between rows.\n // The deepest matching row wins — including hub CHILDREN, now that sub-pages\n // render inline in the sidebar rather than only as HubTabs. So the actual\n // current sub-page carries the highlight, not just the hub landing.\n // Exactly one row carries the active highlight — see `active-href.ts` for\n // how it is chosen. The list is every href the sidebar draws, hub children\n // included: Collections is attached as a child of the catalogue row, so when\n // children were missing from this list nothing lit on /dashboard/collections\n // and the hub holding the only row that leads there stayed shut.\n const activeHref = useMemo(\n () =>\n resolveActiveHref(\n [\n '/dashboard',\n ...(inboxRow ? [inboxRow.href] : []),\n ...workItems.flatMap((i) => [\n i.href,\n ...(i.children ?? []).map((c) => c.href),\n ]),\n ...spineGroups.flatMap((g) => [\n g.href,\n ...getGroupItems(g).map((i) => i.href),\n ]),\n ...(moreGroup ? getGroupItems(moreGroup).map((i) => i.href) : []),\n ].filter((h): h is string => !!h),\n location,\n ),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [pathname, searchParams, groups, moreGroup, inboxRow?.href],\n );\n\n const { inventoryHub, catalogMatches, collectionsItem, collectionsNested } =\n buildInventoryHub(workItems);\n\n /**\n * When the Inventory hub exists, catalog + collections move inside it; else\n * both still render flat, right where they were.\n *\n * THE ROWS THEMSELVES, not their ids. An id says what a row is called by its\n * archetype, not which screen it opens, and `subscriptions` calls its Plans\n * row `catalog` too — so a business running memberships beside anything with\n * a real catalogue had **Plans deleted from its sidebar**, in all nine\n * two-archetype combinations. Collections is consumed only when the hub took\n * it: dropping it always cost the row to a business with no catalogue row.\n */\n const ordersItem = findOrdersRow(workItems);\n const ordersPaymentsHub = buildOrdersPaymentsHub({\n ordersItem,\n moneyGroup: spineGroups.find((g) => g.id === 'payments') ?? null,\n can,\n });\n\n const consumed = new Set<unknown>([\n ...(inventoryHub\n ? [...catalogMatches, ...(collectionsItem ? [collectionsItem] : [])]\n : []),\n // With no catalogue row to hold it, Collections is drawn as a row of its\n // own by the rail; it must not also be drawn as a work row.\n ...(!inventoryHub && collectionsItem && !collectionsNested\n ? [collectionsItem]\n : []),\n ...(ordersPaymentsHub && ordersItem ? [ordersItem] : []),\n ]);\n /* General (unlabeled) leads; the rest drop into Operations. The rule is in\n `work-rows`. */\n const { primaryWork, extraWork } = splitWorkRows({\n workItems,\n consumed,\n hasInventoryHub: !!inventoryHub,\n });\n\n const hubById = new Map(spineGroups.map((g) => [g.id, g]));\n const operationsHub = hubById.get('operations');\n\n const badges = useNavBadges();\n /* Rows that appeared because the business added something this week — see\n `use-new-nav-rows`. */\n const newRows = useNewNavRows();\n\n // Plain render helpers (NOT inner components) — defining components inside\n // the render remounts them every pass, which breaks motion's shared-layout\n // highlight (it would jump from stale positions). Functions returning JSX\n // keep the elements stable under the parent, so the spring animates cleanly.\n const {\n renderHighlight,\n renderIcon,\n renderRow,\n renderHub,\n renderWorkRow,\n ownsRoute,\n } = createNavRenderers({\n collapsed,\n warm,\n activeHref,\n isActive,\n hubChoice,\n setHubOpen,\n getGroupItems,\n badges,\n isNew: (id: string) => newRows.has(id),\n });\n\n const { moneyRow, manageRows, growRows } = buildRailRows({\n inventoryHub,\n payHub: ordersPaymentsHub ?? hubById.get('payments'),\n primaryWork,\n extraWork,\n collectionsItem,\n collectionsNested,\n customersHub: hubById.get('customers'),\n hardwareHub: hubById.get('hardware'),\n operationsHub,\n growHubs: [\n hubById.get('marketing'),\n hubById.get('analytics'),\n hubById.get('website'),\n ],\n renderHub,\n renderRow,\n renderWorkRow,\n });\n\n /** What the rail is drawn from, in both modes. See `rail-body.tsx`. */\n const railProps = {\n collapsed,\n homeActive: activeHref === '/dashboard',\n toolkitActive: pathname.startsWith('/dashboard/toolkit'),\n highlight: renderHighlight,\n inboxRow,\n renderRow,\n renderHub,\n renderIcon,\n ownsRoute,\n moneyRow,\n manageRows,\n growRows,\n moreGroup,\n };\n\n return (\n <aside\n className={`pv-shell sidebar${collapsed ? ' collapsed' : ''} hidden lg:flex`}\n >\n {/* THE HEAD OF THE RAIL, one flat row (John, 2026-10-04): the business's\n mark and name at the left, the Search and Ask icons at the right. It\n was a bordered card with a second line, and Search a row of its own\n beneath it. */}\n {/* A branded box can put its own logo at the top of the rail\n (NEXT_PUBLIC_BOX_LOGO_IN_SHELL). Left out when the rail is collapsed,\n where there is no room for a wordmark. */}\n {process.env.NEXT_PUBLIC_BOX_LOGO_IN_SHELL === 'true' &&\n process.env.NEXT_PUBLIC_BOX_LOGO &&\n !collapsed && (\n <div className=\"px-3 pb-1 pt-3\">\n <Logo size=\"sm\" theme=\"light\" />\n </div>\n )}\n <div className=\"side-head\">\n {collapsed && onToggleCollapse && (\n /* The way back out of a collapsed rail: laid over the business\n mark, invisible until the pointer is on it, then the expand icon\n (John, 2026-10-11). */\n <button\n type=\"button\"\n onClick={onToggleCollapse}\n className=\"side-head-expand\"\n title=\"Expand sidebar (⌘\\)\"\n aria-label=\"Expand sidebar\"\n >\n <PanelLeftOpen width={18} height={18} />\n </button>\n )}\n {!businessContext ? (\n <ProductSwitcher variant=\"head\" />\n ) : (\n <div className=\"flex-1\" />\n )}\n {/* Search, at the right of the head, after Linear (John, 2026-10-11).\n It was a row under Home; it is an action, not a place, so it leaves\n the list. */}\n <div className=\"side-head-actions\">\n <SidebarAsk collapsed={collapsed} variant=\"icon\" />\n {onToggleCollapse && !collapsed && (\n <button\n type=\"button\"\n onClick={onToggleCollapse}\n className=\"side-head-icon\"\n title=\"Collapse sidebar (⌘\\)\"\n aria-label=\"Collapse sidebar\"\n >\n <PanelLeftClose width={16} height={16} />\n </button>\n )}\n </div>\n </div>\n\n {/* Navigation */}\n <nav className=\"nav\" aria-label=\"Main navigation\">\n {/* No loading skeleton: the general nav renders without the template. */}\n\n {/* General nav ⇄ Settings nav: settings drills in place. The pane is\n keyed on the boundary so it remounts and slides in (from the right\n going into settings, from the left coming back). Enter-only — no\n exit — so the nav never collapses and re-flows from the bottom. */}\n <div\n key={inSettings ? 'settings' : 'main'}\n className={'nav-swap ' + (inSettings ? 'from-right' : 'from-left')}\n >\n {inSettings ? (\n <SettingsNav\n businessContext={businessContext}\n pathname={pathname}\n collapsed={collapsed}\n enabledBehaviors={enabledBehaviors}\n tenant={tenant}\n onBack={settingsBack}\n />\n ) : editing ? (\n <EditableRailBody\n {...railProps}\n onDone={stopEditing}\n editSection={editing}\n savedLabels={moduleLabels}\n storedLabels={\n tenantSettings?.moduleLabels as\n Record<string, string> | undefined\n }\n />\n ) : (\n <RailBody\n {...railProps}\n onConfigure={startEditing}\n edits={{\n hidden: hiddenModules,\n order: moduleOrder,\n labels: moduleLabels,\n sectionLabels: tenantSettings?.sectionLabels as\n Record<string, string> | undefined,\n }}\n />\n )}\n </div>\n </nav>\n\n <SidebarFooter collapsed={collapsed} />\n </aside>\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.68",
|
|
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.68",
|
|
54
|
+
"@withpotter/lib": "^0.13.0-next.68",
|
|
55
|
+
"@withpotter/ui-kit": "^0.13.0-next.68",
|
|
56
56
|
"@xyflow/react": "^12.11.2",
|
|
57
57
|
"dompurify": "^3.3.1",
|
|
58
58
|
"jsqr": "1.4.0",
|