@withpotter/components 0.13.0-next.124 → 0.13.0-next.59
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/billing/overview-tab.js +3 -4
- package/dist/billing/overview-tab.js.map +1 -1
- package/dist/billing/plans-tab.js +12 -6
- package/dist/billing/plans-tab.js.map +1 -1
- package/dist/charts/index.d.ts +1 -1
- package/dist/charts/index.js +1 -3
- package/dist/charts/index.js.map +1 -1
- package/dist/dashboard/activity-feed.d.ts +0 -2
- package/dist/dashboard/activity-feed.js +1 -4
- package/dist/dashboard/activity-feed.js.map +1 -1
- package/dist/dashboard/home/ask-controls.js +0 -1
- package/dist/dashboard/home/ask-controls.js.map +1 -1
- package/dist/dashboard/home/ask-mode-chip.js +4 -4
- package/dist/dashboard/home/ask-mode-chip.js.map +1 -1
- package/dist/dashboard/home/home-ask.js +3 -3
- package/dist/dashboard/home/home-ask.js.map +1 -1
- package/dist/dashboard/home/home-quick-actions.js +1 -9
- package/dist/dashboard/home/home-quick-actions.js.map +1 -1
- package/dist/dashboard/home/widgets/activity-widget.js +43 -56
- package/dist/dashboard/home/widgets/activity-widget.js.map +1 -1
- package/dist/dashboard/home/widgets/home-widgets.js +2 -3
- package/dist/dashboard/home/widgets/home-widgets.js.map +1 -1
- package/dist/dashboard/home/widgets/money-widgets.js +4 -9
- package/dist/dashboard/home/widgets/money-widgets.js.map +1 -1
- package/dist/dashboard/home/widgets/registry.d.ts +3 -6
- package/dist/dashboard/home/widgets/registry.js +2 -12
- package/dist/dashboard/home/widgets/registry.js.map +1 -1
- package/dist/dashboard/home/widgets/team-widget.js +3 -15
- package/dist/dashboard/home/widgets/team-widget.js.map +1 -1
- package/dist/dashboard/home/widgets/types.d.ts +1 -13
- package/dist/dashboard/home/widgets/types.js.map +1 -1
- package/dist/dashboard/home/widgets/use-home-layout.js +4 -9
- package/dist/dashboard/home/widgets/use-home-layout.js.map +1 -1
- package/dist/dashboard/home/widgets/website-widget.js +1 -9
- package/dist/dashboard/home/widgets/website-widget.js.map +1 -1
- package/dist/dashboard/page-transition.d.ts +0 -11
- package/dist/dashboard/page-transition.js +2 -9
- package/dist/dashboard/page-transition.js.map +1 -1
- package/dist/features/gift-card-artwork.js +1 -1
- package/dist/features/gift-card-artwork.js.map +1 -1
- package/dist/features/gift-cards/gift-cards-feature.js +518 -656
- package/dist/features/gift-cards/gift-cards-feature.js.map +1 -1
- package/dist/features/product-form.js +1 -1
- package/dist/features/product-form.js.map +1 -1
- package/dist/mobile/screens/billing-flows.js +6 -7
- package/dist/mobile/screens/billing-flows.js.map +1 -1
- package/dist/mobile/screens/billing.js +2 -2
- package/dist/mobile/screens/billing.js.map +1 -1
- package/dist/notifications-dropdown.js +1 -11
- package/dist/notifications-dropdown.js.map +1 -1
- package/dist/shell/notification-kinds.d.ts +2 -11
- package/dist/shell/notification-kinds.js +1 -16
- package/dist/shell/notification-kinds.js.map +1 -1
- package/dist/shell/notification-row.d.ts +2 -6
- package/dist/shell/notification-row.js +33 -59
- package/dist/shell/notification-row.js.map +1 -1
- package/dist/shell/product-switcher.js +0 -3
- package/dist/shell/product-switcher.js.map +1 -1
- package/dist/shell/settings-modal-context.d.ts +0 -2
- package/dist/shell/settings-modal-context.js +2 -4
- package/dist/shell/settings-modal-context.js.map +1 -1
- package/dist/shell/settings-panels.js +9 -29
- package/dist/shell/settings-panels.js.map +1 -1
- package/dist/shell/settings-rows.d.ts +2 -7
- package/dist/shell/settings-rows.js +36 -93
- package/dist/shell/settings-rows.js.map +1 -1
- package/dist/shell/shell.css +0 -192
- package/dist/shell/sidebar-footer.js +2 -6
- package/dist/shell/sidebar-footer.js.map +1 -1
- package/dist/shell/unified-settings-modal.d.ts +2 -3
- package/dist/shell/unified-settings-modal.js +74 -151
- package/dist/shell/unified-settings-modal.js.map +1 -1
- package/dist/sidebar/config.js +3 -19
- package/dist/sidebar/config.js.map +1 -1
- package/dist/sidebar/more-group.js +1 -1
- package/dist/sidebar/more-group.js.map +1 -1
- package/dist/sidebar/nav-renderers.js +0 -2
- package/dist/sidebar/nav-renderers.js.map +1 -1
- package/dist/sidebar/nav-section.js +3 -5
- package/dist/sidebar/nav-section.js.map +1 -1
- package/dist/sidebar/setup-progress.d.ts +1 -3
- package/dist/sidebar/setup-progress.js +2 -2
- package/dist/sidebar/setup-progress.js.map +1 -1
- package/dist/sidebar/spine-groups.js +1 -7
- package/dist/sidebar/spine-groups.js.map +1 -1
- package/dist/sidebar.js +5 -5
- package/dist/sidebar.js.map +1 -1
- package/dist/templates/template-live-thumb.js +5 -16
- package/dist/templates/template-live-thumb.js.map +1 -1
- package/package.json +5 -5
- package/dist/dashboard/activity-paid.d.ts +0 -10
- package/dist/dashboard/activity-paid.js +0 -7
- package/dist/dashboard/activity-paid.js.map +0 -1
- package/dist/dashboard/home/host-quick-actions.d.ts +0 -19
- package/dist/dashboard/home/host-quick-actions.js +0 -12
- package/dist/dashboard/home/host-quick-actions.js.map +0 -1
- package/dist/dashboard/home/widgets/host-widgets.d.ts +0 -6
- package/dist/dashboard/home/widgets/host-widgets.js +0 -16
- package/dist/dashboard/home/widgets/host-widgets.js.map +0 -1
- package/dist/dashboard/home/widgets/member-label.d.ts +0 -11
- package/dist/dashboard/home/widgets/member-label.js +0 -10
- package/dist/dashboard/home/widgets/member-label.js.map +0 -1
- package/dist/dashboard/home/widgets/middle-ellipsis.d.ts +0 -5
- package/dist/dashboard/home/widgets/middle-ellipsis.js +0 -11
- package/dist/dashboard/home/widgets/middle-ellipsis.js.map +0 -1
- package/dist/dashboard/home/widgets/revenue-series.d.ts +0 -12
- package/dist/dashboard/home/widgets/revenue-series.js +0 -26
- package/dist/dashboard/home/widgets/revenue-series.js.map +0 -1
- package/dist/dashboard/page-transition-key.d.ts +0 -7
- package/dist/dashboard/page-transition-key.js +0 -11
- package/dist/dashboard/page-transition-key.js.map +0 -1
- package/dist/features/gift-cards/gift-card-logic.d.ts +0 -41
- package/dist/features/gift-cards/gift-card-logic.js +0 -65
- package/dist/features/gift-cards/gift-card-logic.js.map +0 -1
- package/dist/features/gift-cards/hint.d.ts +0 -9
- package/dist/features/gift-cards/hint.js +0 -25
- package/dist/features/gift-cards/hint.js.map +0 -1
- package/dist/shell/notification-actions.d.ts +0 -12
- package/dist/shell/notification-actions.js +0 -18
- package/dist/shell/notification-actions.js.map +0 -1
- package/dist/shell/settings-groups.d.ts +0 -23
- package/dist/shell/settings-groups.js +0 -22
- package/dist/shell/settings-groups.js.map +0 -1
- package/dist/shell/settings-host.d.ts +0 -39
- package/dist/shell/settings-host.js +0 -37
- package/dist/shell/settings-host.js.map +0 -1
- package/dist/shell/sidebar-cards.d.ts +0 -32
- package/dist/shell/sidebar-cards.js +0 -37
- package/dist/shell/sidebar-cards.js.map +0 -1
- package/dist/templates/template-tile-source.d.ts +0 -24
- package/dist/templates/template-tile-source.js +0 -18
- package/dist/templates/template-tile-source.js.map +0 -1
|
@@ -20,8 +20,6 @@ const isSettingsPath = (path) => !!path && (path === SETTINGS_PREFIX || path.sta
|
|
|
20
20
|
const SettingsModalContext = createContext({
|
|
21
21
|
openSettings: () => {
|
|
22
22
|
},
|
|
23
|
-
closeSettings: () => {
|
|
24
|
-
},
|
|
25
23
|
prewarmSettings: () => {
|
|
26
24
|
}
|
|
27
25
|
});
|
|
@@ -117,8 +115,8 @@ function SettingsModalProvider({ children }) {
|
|
|
117
115
|
router.replace(lastOutside.current);
|
|
118
116
|
}, [pathname, openSettings, router]);
|
|
119
117
|
const api = useMemo(
|
|
120
|
-
() => ({ openSettings,
|
|
121
|
-
[openSettings,
|
|
118
|
+
() => ({ openSettings, prewarmSettings }),
|
|
119
|
+
[openSettings, prewarmSettings]
|
|
122
120
|
);
|
|
123
121
|
return /* @__PURE__ */ jsxs(SettingsModalContext.Provider, { value: api, children: [
|
|
124
122
|
children,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/shell/settings-modal-context.tsx"],"sourcesContent":["'use client';\n\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from 'react';\nimport { usePathname, useRouter } from 'next/navigation';\nimport { isEmbeddedFrame } from '@withpotter/lib/embedded-frame';\nimport { isMobileViewport } from '@withpotter/hooks/use-mobile';\nimport { UnifiedSettingsModal } from './unified-settings-modal';\nimport { movedSettingsTarget } from './moved-settings';\n\nconst SETTINGS_PREFIX = '/dashboard/settings';\n\n/** `/dashboard/settings` and anything under it, but not `/dashboard/settingsX`. */\nconst isSettingsPath = (path: string | null | undefined) =>\n !!path &&\n (path === SETTINGS_PREFIX || path.startsWith(`${SETTINGS_PREFIX}/`));\n\ninterface SettingsModalApi {\n /** Open the settings window, on a section when given a settings address. */\n openSettings: (href?: string) => void;\n /** Close it, for a screen inside it that leaves for another page. */\n closeSettings: () => void;\n /**\n * Warm the window before it is opened: a hidden copy of the settings page\n * loads once so the real one boots with its code fetched and its route\n * compiled. Cheap to call repeatedly; only the first call in a page load does\n * anything. Call it on the signals that precede opening (a menu opening, a\n * pointer arriving on a settings link).\n */\n prewarmSettings: () => void;\n}\n\nconst SettingsModalContext = createContext<SettingsModalApi>({\n openSettings: () => {},\n closeSettings: () => {},\n prewarmSettings: () => {},\n});\n\nexport const useSettingsModal = () => useContext(SettingsModalContext);\n\n/**\n * Settings is a window, never a page.\n *\n * The workspace menu always opened it as one; everything else that pointed at a\n * settings address (a setup step, the Customize sidebar row, a link in an email,\n * a typed URL) still navigated to the page. This owns the one modal and makes\n * every road into settings arrive in it:\n *\n * - a CLICK on a link to a settings address is caught before it navigates and\n * opens the window on that section;\n * - anything else that lands on a settings route (`router.push`, a pasted\n * address, back/forward) is sent back to where you were and the window opens\n * on it, with the page itself never drawn (see the layout).\n *\n * The framed copy of a page is the one place a settings route is allowed to\n * render, so nothing here runs inside it.\n */\nexport function SettingsModalProvider({ children }: { children: ReactNode }) {\n const router = useRouter();\n const pathname = usePathname();\n const [state, setState] = useState<{ open: boolean; href: string | null }>({\n open: false,\n href: null,\n });\n\n // The window is for a desktop screen. On a phone settings stays what it was:\n // pages you go to, with the mobile shell around them.\n const openSettings = useCallback(\n (href?: string) => {\n // An address that moved out of settings goes to its page, not the window.\n const moved = href\n ? movedSettingsTarget(\n href.split(/[?#]/)[0],\n href.includes('?') ? `?${href.split('?')[1].split('#')[0]}` : '',\n )\n : null;\n if (moved) {\n router.push(moved);\n return;\n }\n if (isMobileViewport()) {\n router.push(href ?? '/dashboard/settings');\n return;\n }\n setState({ open: true, href: href ?? null });\n },\n [router],\n );\n // Kept so callers that warm the window on hover still compile: the window no\n // longer boots a frame, so there is nothing to warm.\n const prewarmSettings = useCallback(() => {}, []);\n\n const close = useCallback(() => setState((s) => ({ ...s, open: false })), []);\n\n // Where you were before settings, so closing leaves you there.\n const lastOutside = useRef('/dashboard');\n useEffect(() => {\n if (!isSettingsPath(pathname) && pathname) lastOutside.current = pathname;\n }, [pathname]);\n\n // A click on a link to settings: open the window instead of going there.\n useEffect(() => {\n if (isEmbeddedFrame()) return;\n const onClick = (e: MouseEvent) => {\n if (isMobileViewport()) return;\n if (e.defaultPrevented || e.button !== 0) return;\n if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;\n const a = (e.target as Element | null)?.closest?.('a[href]');\n if (!a || a.getAttribute('target') === '_blank') return;\n let url: URL;\n try {\n url = new URL(a.getAttribute('href') ?? '', window.location.href);\n } catch {\n return;\n }\n if (url.origin !== window.location.origin) return;\n if (!isSettingsPath(url.pathname)) return;\n const moved = movedSettingsTarget(url.pathname, url.search);\n if (moved) {\n e.preventDefault();\n router.push(moved);\n return;\n }\n // Not stopped: the link's own handlers still run (a dialog closing as you\n // leave it, say). Next skips its navigation for a prevented click.\n e.preventDefault();\n openSettings(url.pathname + url.search + url.hash);\n };\n // Capture, so this runs before the framework's own link handling.\n document.addEventListener('click', onClick, true);\n return () => document.removeEventListener('click', onClick, true);\n }, [openSettings, router]);\n\n // A pointer or keyboard focus arriving on a link to settings is the last\n // moment before the click; start warming then.\n useEffect(() => {\n if (isEmbeddedFrame()) return;\n const onNear = (e: Event) => {\n const a = (e.target as Element | null)?.closest?.('a[href]');\n const href = a?.getAttribute('href');\n const path = href?.split(/[?#]/)[0];\n if (path && isSettingsPath(path) && !movedSettingsTarget(path)) {\n prewarmSettings();\n }\n };\n document.addEventListener('pointerover', onNear, true);\n document.addEventListener('focusin', onNear, true);\n return () => {\n document.removeEventListener('pointerover', onNear, true);\n document.removeEventListener('focusin', onNear, true);\n };\n }, [prewarmSettings]);\n\n // Anything else that lands on a settings route: back to where you were, with\n // the window open on it. Before paint, so the page is never seen.\n useLayoutEffect(() => {\n if (isEmbeddedFrame() || isMobileViewport()) return;\n if (!isSettingsPath(pathname)) return;\n const moved = movedSettingsTarget(pathname, window.location.search);\n if (moved) {\n router.replace(moved);\n return;\n }\n // eslint-disable-next-line react-hooks/set-state-in-effect -- before paint, so the page is never drawn\n openSettings(\n pathname +\n (typeof window !== 'undefined'\n ? window.location.search + window.location.hash\n : ''),\n );\n router.replace(lastOutside.current);\n }, [pathname, openSettings, router]);\n\n const api = useMemo(\n () => ({ openSettings, closeSettings: close, prewarmSettings }),\n [openSettings, close, prewarmSettings],\n );\n\n return (\n <SettingsModalContext.Provider value={api}>\n {children}\n <UnifiedSettingsModal\n open={state.open}\n href={state.href}\n onClose={close}\n />\n </SettingsModalContext.Provider>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/shell/settings-modal-context.tsx"],"sourcesContent":["'use client';\n\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from 'react';\nimport { usePathname, useRouter } from 'next/navigation';\nimport { isEmbeddedFrame } from '@withpotter/lib/embedded-frame';\nimport { isMobileViewport } from '@withpotter/hooks/use-mobile';\nimport { UnifiedSettingsModal } from './unified-settings-modal';\nimport { movedSettingsTarget } from './moved-settings';\n\nconst SETTINGS_PREFIX = '/dashboard/settings';\n\n/** `/dashboard/settings` and anything under it, but not `/dashboard/settingsX`. */\nconst isSettingsPath = (path: string | null | undefined) =>\n !!path &&\n (path === SETTINGS_PREFIX || path.startsWith(`${SETTINGS_PREFIX}/`));\n\ninterface SettingsModalApi {\n /** Open the settings window, on a section when given a settings address. */\n openSettings: (href?: string) => void;\n /**\n * Warm the window before it is opened: a hidden copy of the settings page\n * loads once so the real one boots with its code fetched and its route\n * compiled. Cheap to call repeatedly; only the first call in a page load does\n * anything. Call it on the signals that precede opening (a menu opening, a\n * pointer arriving on a settings link).\n */\n prewarmSettings: () => void;\n}\n\nconst SettingsModalContext = createContext<SettingsModalApi>({\n openSettings: () => {},\n prewarmSettings: () => {},\n});\n\nexport const useSettingsModal = () => useContext(SettingsModalContext);\n\n/**\n * Settings is a window, never a page.\n *\n * The workspace menu always opened it as one; everything else that pointed at a\n * settings address (a setup step, the Customize sidebar row, a link in an email,\n * a typed URL) still navigated to the page. This owns the one modal and makes\n * every road into settings arrive in it:\n *\n * - a CLICK on a link to a settings address is caught before it navigates and\n * opens the window on that section;\n * - anything else that lands on a settings route (`router.push`, a pasted\n * address, back/forward) is sent back to where you were and the window opens\n * on it, with the page itself never drawn (see the layout).\n *\n * The framed copy of a page is the one place a settings route is allowed to\n * render, so nothing here runs inside it.\n */\nexport function SettingsModalProvider({ children }: { children: ReactNode }) {\n const router = useRouter();\n const pathname = usePathname();\n const [state, setState] = useState<{ open: boolean; href: string | null }>({\n open: false,\n href: null,\n });\n\n // The window is for a desktop screen. On a phone settings stays what it was:\n // pages you go to, with the mobile shell around them.\n const openSettings = useCallback(\n (href?: string) => {\n // An address that moved out of settings goes to its page, not the window.\n const moved = href\n ? movedSettingsTarget(\n href.split(/[?#]/)[0],\n href.includes('?') ? `?${href.split('?')[1].split('#')[0]}` : '',\n )\n : null;\n if (moved) {\n router.push(moved);\n return;\n }\n if (isMobileViewport()) {\n router.push(href ?? '/dashboard/settings');\n return;\n }\n setState({ open: true, href: href ?? null });\n },\n [router],\n );\n // Kept so callers that warm the window on hover still compile: the window no\n // longer boots a frame, so there is nothing to warm.\n const prewarmSettings = useCallback(() => {}, []);\n\n const close = useCallback(() => setState((s) => ({ ...s, open: false })), []);\n\n // Where you were before settings, so closing leaves you there.\n const lastOutside = useRef('/dashboard');\n useEffect(() => {\n if (!isSettingsPath(pathname) && pathname) lastOutside.current = pathname;\n }, [pathname]);\n\n // A click on a link to settings: open the window instead of going there.\n useEffect(() => {\n if (isEmbeddedFrame()) return;\n const onClick = (e: MouseEvent) => {\n if (isMobileViewport()) return;\n if (e.defaultPrevented || e.button !== 0) return;\n if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;\n const a = (e.target as Element | null)?.closest?.('a[href]');\n if (!a || a.getAttribute('target') === '_blank') return;\n let url: URL;\n try {\n url = new URL(a.getAttribute('href') ?? '', window.location.href);\n } catch {\n return;\n }\n if (url.origin !== window.location.origin) return;\n if (!isSettingsPath(url.pathname)) return;\n const moved = movedSettingsTarget(url.pathname, url.search);\n if (moved) {\n e.preventDefault();\n router.push(moved);\n return;\n }\n // Not stopped: the link's own handlers still run (a dialog closing as you\n // leave it, say). Next skips its navigation for a prevented click.\n e.preventDefault();\n openSettings(url.pathname + url.search + url.hash);\n };\n // Capture, so this runs before the framework's own link handling.\n document.addEventListener('click', onClick, true);\n return () => document.removeEventListener('click', onClick, true);\n }, [openSettings, router]);\n\n // A pointer or keyboard focus arriving on a link to settings is the last\n // moment before the click; start warming then.\n useEffect(() => {\n if (isEmbeddedFrame()) return;\n const onNear = (e: Event) => {\n const a = (e.target as Element | null)?.closest?.('a[href]');\n const href = a?.getAttribute('href');\n const path = href?.split(/[?#]/)[0];\n if (path && isSettingsPath(path) && !movedSettingsTarget(path)) {\n prewarmSettings();\n }\n };\n document.addEventListener('pointerover', onNear, true);\n document.addEventListener('focusin', onNear, true);\n return () => {\n document.removeEventListener('pointerover', onNear, true);\n document.removeEventListener('focusin', onNear, true);\n };\n }, [prewarmSettings]);\n\n // Anything else that lands on a settings route: back to where you were, with\n // the window open on it. Before paint, so the page is never seen.\n useLayoutEffect(() => {\n if (isEmbeddedFrame() || isMobileViewport()) return;\n if (!isSettingsPath(pathname)) return;\n const moved = movedSettingsTarget(pathname, window.location.search);\n if (moved) {\n router.replace(moved);\n return;\n }\n // eslint-disable-next-line react-hooks/set-state-in-effect -- before paint, so the page is never drawn\n openSettings(\n pathname +\n (typeof window !== 'undefined'\n ? window.location.search + window.location.hash\n : ''),\n );\n router.replace(lastOutside.current);\n }, [pathname, openSettings, router]);\n\n const api = useMemo(\n () => ({ openSettings, prewarmSettings }),\n [openSettings, prewarmSettings],\n );\n\n return (\n <SettingsModalContext.Provider value={api}>\n {children}\n <UnifiedSettingsModal\n open={state.open}\n href={state.href}\n onClose={close}\n />\n </SettingsModalContext.Provider>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -8,10 +8,8 @@ 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";
|
|
12
11
|
import { RotateCcw } from "lucide-react";
|
|
13
12
|
import { AccessibilityCard } from "@withpotter/pages-core/dashboard/settings/accessibility/_components/accessibility-card";
|
|
14
|
-
import { TaxesSettingsContent } from "@withpotter/pages-core/dashboard/settings/taxes/_components/taxes-content";
|
|
15
13
|
import { PoliciesContent } from "@withpotter/pages-core/dashboard/settings/policies/_components/policies-content";
|
|
16
14
|
import { UsageContent } from "@withpotter/pages-core/dashboard/settings/usage/_components/usage-content";
|
|
17
15
|
import { GeneralContent } from "@withpotter/pages-core/dashboard/settings/_components/general-content";
|
|
@@ -20,8 +18,6 @@ import { TeamContent } from "@withpotter/pages-core/dashboard/settings/team/_com
|
|
|
20
18
|
import { BillingContent } from "@withpotter/pages-core/dashboard/settings/billing/_components/billing-content";
|
|
21
19
|
import { LegalContent } from "@withpotter/pages-core/dashboard/settings/legal/_components/legal-content";
|
|
22
20
|
import { PaymentApprovalsContent } from "@withpotter/pages-core/dashboard/settings/payment-approvals/_components/payment-approvals-content";
|
|
23
|
-
import { DemoContentCard } from "@withpotter/pages-core/dashboard/settings/demo-content/_components/demo-content-card";
|
|
24
|
-
import { FeaturesContent } from "@withpotter/pages-core/dashboard/settings/features/_components/features-content";
|
|
25
21
|
const loading = () => /* @__PURE__ */ jsx(StateView, { kind: "loading", title: "Loading" });
|
|
26
22
|
function Section({
|
|
27
23
|
title,
|
|
@@ -36,23 +32,12 @@ function Section({
|
|
|
36
32
|
children
|
|
37
33
|
] });
|
|
38
34
|
}
|
|
39
|
-
function
|
|
40
|
-
return /* @__PURE__ */ jsxs("div", { className: "space-y-8", children: [
|
|
41
|
-
/* @__PURE__ */ jsx(ProfileContent, {}),
|
|
42
|
-
/* @__PURE__ */ jsx(SecurityContent, {})
|
|
43
|
-
] });
|
|
44
|
-
}
|
|
45
|
-
function SecurityPanel() {
|
|
35
|
+
function AccountPanel() {
|
|
46
36
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-8", children: [
|
|
37
|
+
/* @__PURE__ */ jsx(Section, { title: "Profile", children: /* @__PURE__ */ jsx(ProfileContent, {}) }),
|
|
47
38
|
/* @__PURE__ */ jsx(Section, { title: "Sign-in and security", children: /* @__PURE__ */ jsx("div", { className: "px-4", children: /* @__PURE__ */ jsx(SignInContent, {}) }) }),
|
|
48
|
-
/* @__PURE__ */ jsx(Section, { title: "Data & privacy", children: /* @__PURE__ */ jsx(DataPrivacyContent, {}) })
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
function ExtrasPanel() {
|
|
52
|
-
const { can } = usePermissions();
|
|
53
|
-
return /* @__PURE__ */ jsxs("div", { className: "space-y-8", children: [
|
|
54
|
-
can("canManageSettings") && /* @__PURE__ */ jsx(FeaturesContent, {}),
|
|
55
|
-
/* @__PURE__ */ jsx(Section, { title: "Sample content", children: /* @__PURE__ */ jsx(DemoContentCard, {}) })
|
|
39
|
+
/* @__PURE__ */ jsx(Section, { title: "Data & privacy", children: /* @__PURE__ */ jsx(DataPrivacyContent, {}) }),
|
|
40
|
+
/* @__PURE__ */ jsx(SecurityContent, {})
|
|
56
41
|
] });
|
|
57
42
|
}
|
|
58
43
|
function ResetAccessibility() {
|
|
@@ -71,24 +56,19 @@ function ResetAccessibility() {
|
|
|
71
56
|
);
|
|
72
57
|
}
|
|
73
58
|
function PreferencesPanel() {
|
|
74
|
-
return /* @__PURE__ */
|
|
75
|
-
PreferencesContent,
|
|
76
|
-
{
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
);
|
|
59
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-8", children: [
|
|
60
|
+
/* @__PURE__ */ jsx(Section, { title: "Appearance, language and region", children: /* @__PURE__ */ jsx(PreferencesContent, {}) }),
|
|
61
|
+
/* @__PURE__ */ jsx(Section, { title: "Accessibility", action: /* @__PURE__ */ jsx(ResetAccessibility, {}), children: /* @__PURE__ */ jsx("div", { className: "px-4", children: /* @__PURE__ */ jsx(AccessibilityCard, {}) }) })
|
|
62
|
+
] });
|
|
80
63
|
}
|
|
81
64
|
const PANELS = {
|
|
82
65
|
general: () => /* @__PURE__ */ jsx(GeneralContent, {}),
|
|
83
|
-
|
|
84
|
-
profile: () => /* @__PURE__ */ jsx(ProfilePanel, {}),
|
|
85
|
-
security: () => /* @__PURE__ */ jsx(SecurityPanel, {}),
|
|
66
|
+
account: () => /* @__PURE__ */ jsx(AccountPanel, {}),
|
|
86
67
|
team: () => /* @__PURE__ */ jsx(TeamContent, {}),
|
|
87
68
|
notifications: () => /* @__PURE__ */ jsx(NotificationsContent, {}),
|
|
88
69
|
preferences: () => /* @__PURE__ */ jsx(PreferencesPanel, {}),
|
|
89
70
|
"payment-approvals": () => /* @__PURE__ */ jsx(PaymentApprovalsContent, {}),
|
|
90
71
|
billing: () => /* @__PURE__ */ jsx(BillingContent, {}),
|
|
91
|
-
taxes: () => /* @__PURE__ */ jsx(TaxesSettingsContent, {}),
|
|
92
72
|
usage: () => /* @__PURE__ */ jsx(UsageContent, {}),
|
|
93
73
|
policies: () => /* @__PURE__ */ jsx(PoliciesContent, {}),
|
|
94
74
|
legal: () => /* @__PURE__ */ jsx(LegalContent, {})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/shell/settings-panels.tsx"],"sourcesContent":["'use client';\n\nimport { StateView } from '@withpotter/ui-kit/state-view';\nimport { Suspense, type ReactNode } from 'react';\nimport { ProfileContent } from '@withpotter/pages-core/dashboard/settings/profile/_components/profile-content';\nimport { SecurityContent } from '@withpotter/pages-core/dashboard/settings/profile/_components/security-content';\nimport { SignInContent } from '@withpotter/pages-core/dashboard/settings/devices/_components/sign-in-content';\nimport { DataPrivacyContent } from '@withpotter/pages-core/dashboard/settings/data-privacy/_components/data-privacy-content';\nimport { PreferencesContent } from '@withpotter/pages-core/dashboard/settings/language/_components/preferences-content';\nimport { useAccessibility } from '@withpotter/lib/accessibility-context';\nimport {
|
|
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 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 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":";AA+BsB,cAchB,YAdgB;AA7BtB,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;AAYxC,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,SAAS,MAAM,oBAAC,gBAAa;AAAA,EAC7B,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,12 +12,10 @@ import type { PermissionKey } from '@withpotter/lib/use-permissions';
|
|
|
12
12
|
* `keywords` are what search also matches: the names of the sections inside a
|
|
13
13
|
* row, so "passkeys" or "time zone" finds the row that holds them.
|
|
14
14
|
*/
|
|
15
|
-
export type SettingsRowId = 'general' | '
|
|
15
|
+
export type SettingsRowId = 'general' | 'account' | 'team' | 'notifications' | 'preferences' | 'payment-approvals' | 'billing' | 'usage' | 'policies' | 'legal';
|
|
16
16
|
export interface SettingsRow {
|
|
17
17
|
id: SettingsRowId;
|
|
18
18
|
name: string;
|
|
19
|
-
/** The line under the title when the row is open. */
|
|
20
|
-
subtitle?: string;
|
|
21
19
|
icon: LucideIcon;
|
|
22
20
|
paths: string[];
|
|
23
21
|
keywords: string[];
|
|
@@ -40,7 +38,4 @@ export declare const HELP_ICON: import("react").ForwardRefExoticComponent<Omit<i
|
|
|
40
38
|
*/
|
|
41
39
|
export declare function rowForHref(href: string | null | undefined): SettingsRowId;
|
|
42
40
|
/** Rows whose name or inner sections match what was typed. */
|
|
43
|
-
export declare function matchRows
|
|
44
|
-
name: string;
|
|
45
|
-
keywords: string[];
|
|
46
|
-
}>(rows: T[], query: string): T[];
|
|
41
|
+
export declare function matchRows(rows: SettingsRow[], query: string): SettingsRow[];
|
|
@@ -6,10 +6,7 @@ import {
|
|
|
6
6
|
CircleHelp,
|
|
7
7
|
CreditCard,
|
|
8
8
|
FileText,
|
|
9
|
-
Lock,
|
|
10
|
-
ReceiptText,
|
|
11
9
|
Scale,
|
|
12
|
-
Sparkles,
|
|
13
10
|
SlidersHorizontal,
|
|
14
11
|
UserRound,
|
|
15
12
|
Users
|
|
@@ -17,25 +14,14 @@ import {
|
|
|
17
14
|
const BILLING_ROW = {
|
|
18
15
|
id: "billing",
|
|
19
16
|
name: "Billing & plan",
|
|
20
|
-
subtitle: "Your plan, how you pay, and your payments.",
|
|
21
17
|
icon: CreditCard,
|
|
22
18
|
paths: ["/dashboard/settings/billing"],
|
|
23
19
|
keywords: ["plan", "invoice", "credits", "top-up", "cancel"],
|
|
24
20
|
permission: ["canManageSettings"]
|
|
25
21
|
};
|
|
26
|
-
const TAXES_ROW = {
|
|
27
|
-
id: "taxes",
|
|
28
|
-
name: "Taxes",
|
|
29
|
-
subtitle: "What you charge, and the number shown on receipts.",
|
|
30
|
-
icon: ReceiptText,
|
|
31
|
-
paths: ["/dashboard/settings/taxes"],
|
|
32
|
-
keywords: ["vat", "tax rate", "registration number", "receipts"],
|
|
33
|
-
permission: ["canManageSettings"]
|
|
34
|
-
};
|
|
35
22
|
const PAYMENT_APPROVALS_ROW = {
|
|
36
23
|
id: "payment-approvals",
|
|
37
24
|
name: "Payment approvals",
|
|
38
|
-
subtitle: "Who approves money going out.",
|
|
39
25
|
icon: CircleCheck,
|
|
40
26
|
paths: ["/dashboard/settings/payment-approvals"],
|
|
41
27
|
keywords: ["approval", "accepted", "rejected", "decisions", "history"]
|
|
@@ -44,7 +30,6 @@ const SETTINGS_ROWS = [
|
|
|
44
30
|
{
|
|
45
31
|
id: "general",
|
|
46
32
|
name: "General",
|
|
47
|
-
subtitle: "Your business details and store address.",
|
|
48
33
|
icon: Building2,
|
|
49
34
|
paths: [
|
|
50
35
|
"/dashboard/settings",
|
|
@@ -54,99 +39,49 @@ const SETTINGS_ROWS = [
|
|
|
54
39
|
keywords: ["business name", "branding", "logo", "colours", "numbering"],
|
|
55
40
|
permission: ["canManageBusinessProfile", "canManageSettings"]
|
|
56
41
|
},
|
|
57
|
-
// A licensed box that bills outside the console hides the row.
|
|
58
|
-
...process.env.NEXT_PUBLIC_BOX_HIDE_BILLING === "true" ? [] : [BILLING_ROW],
|
|
59
|
-
// A box that keeps a tax (VAT) setting has it as its own row, after the plan.
|
|
60
|
-
...process.env.NEXT_PUBLIC_BOX_TAXES_IN_POLICIES === "true" ? [TAXES_ROW] : [],
|
|
61
|
-
{
|
|
62
|
-
id: "team",
|
|
63
|
-
name: "Team & permissions",
|
|
64
|
-
subtitle: "Who can do what in this business.",
|
|
65
|
-
icon: Users,
|
|
66
|
-
paths: [
|
|
67
|
-
"/dashboard/settings/team",
|
|
68
|
-
"/dashboard/settings/roles",
|
|
69
|
-
"/dashboard/settings/activity"
|
|
70
|
-
],
|
|
71
|
-
keywords: ["people", "invite", "roles", "activity", "seats"],
|
|
72
|
-
permission: ["canManageTeam"],
|
|
73
|
-
// A box with its own team screen (its roles and invitations come from
|
|
74
|
-
// its engine, which does not serve this panel's routes) names it here.
|
|
75
|
-
href: process.env.NEXT_PUBLIC_BOX_TEAM_PAGE || void 0
|
|
76
|
-
},
|
|
77
|
-
...process.env.NEXT_PUBLIC_BOX_PAYMENT_APPROVALS === "true" ? [PAYMENT_APPROVALS_ROW] : [],
|
|
78
|
-
{
|
|
79
|
-
id: "policies",
|
|
80
|
-
name: "Policies",
|
|
81
|
-
subtitle: "What your customers agree to.",
|
|
82
|
-
icon: Scale,
|
|
83
|
-
paths: ["/dashboard/settings/policies"],
|
|
84
|
-
keywords: ["returns", "shipping", "privacy", "terms"],
|
|
85
|
-
permission: ["canManageSettings"]
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
// The switches no business type implies (Blog today) and the sample
|
|
89
|
-
// content to start with, clear, or add again. One row for both. No
|
|
90
|
-
// permission gate on the row, like the Demo content entry in the sidebar:
|
|
91
|
-
// everyone sees the state, the panel shows the switches only to those who
|
|
92
|
-
// may change them, and the demo card shows its actions only to the owner
|
|
93
|
-
// and admins.
|
|
94
|
-
id: "extras",
|
|
95
|
-
name: "Extras",
|
|
96
|
-
subtitle: "Switches and sample content for your store.",
|
|
97
|
-
icon: Sparkles,
|
|
98
|
-
paths: [
|
|
99
|
-
"/dashboard/settings/features",
|
|
100
|
-
"/dashboard/settings/demo-content"
|
|
101
|
-
],
|
|
102
|
-
keywords: [
|
|
103
|
-
"blog",
|
|
104
|
-
"posts",
|
|
105
|
-
"write",
|
|
106
|
-
"publish",
|
|
107
|
-
"switch",
|
|
108
|
-
"website",
|
|
109
|
-
"features",
|
|
110
|
-
"sample",
|
|
111
|
-
"products",
|
|
112
|
-
"starter",
|
|
113
|
-
"clear",
|
|
114
|
-
"example data",
|
|
115
|
-
"demo content"
|
|
116
|
-
]
|
|
117
|
-
},
|
|
118
42
|
{
|
|
119
|
-
id: "
|
|
120
|
-
name: "
|
|
121
|
-
subtitle: "Your name and how we reach you.",
|
|
43
|
+
id: "account",
|
|
44
|
+
name: "Account",
|
|
122
45
|
icon: UserRound,
|
|
123
|
-
paths: ["/dashboard/settings/profile"],
|
|
124
|
-
keywords: ["name", "email", "photo", "phone", "close account"]
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
id: "security",
|
|
128
|
-
name: "Security",
|
|
129
|
-
subtitle: "How you sign in, and where you are signed in.",
|
|
130
|
-
icon: Lock,
|
|
131
46
|
paths: [
|
|
47
|
+
"/dashboard/settings/profile",
|
|
132
48
|
"/dashboard/settings/devices",
|
|
133
49
|
"/dashboard/settings/data-privacy"
|
|
134
50
|
],
|
|
135
51
|
keywords: [
|
|
52
|
+
"profile",
|
|
53
|
+
"name",
|
|
54
|
+
"email",
|
|
55
|
+
"photo",
|
|
56
|
+
"security",
|
|
136
57
|
"password",
|
|
137
58
|
"passkeys",
|
|
138
59
|
"two-factor",
|
|
139
60
|
"sessions",
|
|
140
61
|
"devices",
|
|
141
|
-
"sign in",
|
|
142
62
|
"privacy",
|
|
143
|
-
"export"
|
|
63
|
+
"export",
|
|
64
|
+
"close account"
|
|
144
65
|
]
|
|
145
66
|
},
|
|
67
|
+
{
|
|
68
|
+
id: "team",
|
|
69
|
+
name: "Team & permissions",
|
|
70
|
+
icon: Users,
|
|
71
|
+
paths: [
|
|
72
|
+
"/dashboard/settings/team",
|
|
73
|
+
"/dashboard/settings/roles",
|
|
74
|
+
"/dashboard/settings/activity"
|
|
75
|
+
],
|
|
76
|
+
keywords: ["people", "invite", "roles", "activity", "seats"],
|
|
77
|
+
permission: ["canManageTeam"],
|
|
78
|
+
// A box with its own team screen (its roles and invitations come from
|
|
79
|
+
// its engine, which does not serve this panel's routes) names it here.
|
|
80
|
+
href: process.env.NEXT_PUBLIC_BOX_TEAM_PAGE || void 0
|
|
81
|
+
},
|
|
146
82
|
{
|
|
147
83
|
id: "notifications",
|
|
148
84
|
name: "Notifications",
|
|
149
|
-
subtitle: "What we tell you, and what your customers hear from you.",
|
|
150
85
|
icon: Bell,
|
|
151
86
|
paths: ["/dashboard/settings/notifications"],
|
|
152
87
|
keywords: ["alerts", "low stock", "email sender", "digest", "reminders"]
|
|
@@ -154,7 +89,6 @@ const SETTINGS_ROWS = [
|
|
|
154
89
|
{
|
|
155
90
|
id: "preferences",
|
|
156
91
|
name: "Preferences",
|
|
157
|
-
subtitle: "How the dashboard looks and behaves for you.",
|
|
158
92
|
icon: SlidersHorizontal,
|
|
159
93
|
paths: [
|
|
160
94
|
"/dashboard/settings/language",
|
|
@@ -174,22 +108,31 @@ const SETTINGS_ROWS = [
|
|
|
174
108
|
"text size"
|
|
175
109
|
]
|
|
176
110
|
},
|
|
111
|
+
...process.env.NEXT_PUBLIC_BOX_PAYMENT_APPROVALS === "true" ? [PAYMENT_APPROVALS_ROW] : [],
|
|
112
|
+
// A licensed box that bills outside the console hides the row.
|
|
113
|
+
...process.env.NEXT_PUBLIC_BOX_HIDE_BILLING === "true" ? [] : [BILLING_ROW],
|
|
177
114
|
// A box that meters nothing in the console leaves Usage out.
|
|
178
115
|
...process.env.NEXT_PUBLIC_BOX_HIDE_USAGE === "true" ? [] : [
|
|
179
116
|
{
|
|
180
117
|
id: "usage",
|
|
181
118
|
name: "Usage",
|
|
182
|
-
subtitle: "What you have used this month.",
|
|
183
119
|
icon: ChartColumn,
|
|
184
120
|
paths: ["/dashboard/settings/usage"],
|
|
185
121
|
keywords: ["credits", "delivery", "spend"],
|
|
186
122
|
permission: ["canManageSettings"]
|
|
187
123
|
}
|
|
188
124
|
],
|
|
125
|
+
{
|
|
126
|
+
id: "policies",
|
|
127
|
+
name: "Policies",
|
|
128
|
+
icon: Scale,
|
|
129
|
+
paths: ["/dashboard/settings/policies"],
|
|
130
|
+
keywords: ["returns", "shipping", "privacy", "terms"],
|
|
131
|
+
permission: ["canManageSettings"]
|
|
132
|
+
},
|
|
189
133
|
{
|
|
190
134
|
id: "legal",
|
|
191
135
|
name: "Legal",
|
|
192
|
-
subtitle: "Your terms and privacy pages.",
|
|
193
136
|
icon: FileText,
|
|
194
137
|
paths: ["/dashboard/settings/legal"],
|
|
195
138
|
keywords: ["terms", "privacy policy", "licences"]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/shell/settings-rows.ts"],"sourcesContent":["import {\n Bell,\n Building2,\n ChartColumn,\n CircleCheck,\n CircleHelp,\n CreditCard,\n FileText,\n Lock,\n ReceiptText,\n Scale,\n Sparkles,\n SlidersHorizontal,\n UserRound,\n Users,\n type LucideIcon,\n} from 'lucide-react';\nimport type { PermissionKey } from '@withpotter/lib/use-permissions';\n\n/**\n * The rows of the settings window, in the order they are drawn.\n *\n * One list for the whole window: yours and the business's, mixed, because the\n * person looking for \"Notifications\" or \"Language\" should not have to know\n * whose setting it is. Rows that leave the window (`external`) carry an arrow.\n *\n * `paths` are the addresses a row owns. A link to any of them, or to anything\n * below them (a team member, a credits page), opens the window on that row.\n * `keywords` are what search also matches: the names of the sections inside a\n * row, so \"passkeys\" or \"time zone\" finds the row that holds them.\n */\nexport type SettingsRowId =\n | 'general'\n | 'extras'\n | 'profile'\n | 'security'\n | 'team'\n | 'notifications'\n | 'preferences'\n | 'payment-approvals'\n | 'billing'\n | 'taxes'\n | 'usage'\n | 'policies'\n | 'legal';\n\nexport interface SettingsRow {\n id: SettingsRowId;\n name: string;\n /** The line under the title when the row is open. */\n subtitle?: string;\n icon: LucideIcon;\n paths: string[];\n keywords: string[];\n /** Hidden from a member who lacks every one of these. Empty: everyone. */\n permission?: PermissionKey[];\n /**\n * A page this row leaves the window for, where a host has its own screen for\n * the subject. The row then is a link, and the panel is never drawn.\n */\n href?: string;\n}\n\nconst BILLING_ROW: SettingsRow = {\n id: 'billing',\n name: 'Billing & plan',\n subtitle: 'Your plan, how you pay, and your payments.',\n icon: CreditCard,\n paths: ['/dashboard/settings/billing'],\n keywords: ['plan', 'invoice', 'credits', 'top-up', 'cancel'],\n permission: ['canManageSettings'],\n};\n\nconst TAXES_ROW: SettingsRow = {\n id: 'taxes',\n name: 'Taxes',\n subtitle: 'What you charge, and the number shown on receipts.',\n icon: ReceiptText,\n paths: ['/dashboard/settings/taxes'],\n keywords: ['vat', 'tax rate', 'registration number', 'receipts'],\n permission: ['canManageSettings'],\n};\n\n// A box whose card payments wait for a second person lists the decisions.\nconst PAYMENT_APPROVALS_ROW: SettingsRow = {\n id: 'payment-approvals',\n name: 'Payment approvals',\n subtitle: 'Who approves money going out.',\n icon: CircleCheck,\n paths: ['/dashboard/settings/payment-approvals'],\n keywords: ['approval', 'accepted', 'rejected', 'decisions', 'history'],\n};\n\nexport const SETTINGS_ROWS: SettingsRow[] = [\n {\n id: 'general',\n name: 'General',\n subtitle: 'Your business details and store address.',\n icon: Building2,\n paths: [\n '/dashboard/settings',\n '/dashboard/settings/branding',\n '/dashboard/settings/regional',\n ],\n keywords: ['business name', 'branding', 'logo', 'colours', 'numbering'],\n permission: ['canManageBusinessProfile', 'canManageSettings'],\n },\n // A licensed box that bills outside the console hides the row.\n ...(process.env.NEXT_PUBLIC_BOX_HIDE_BILLING === 'true' ? [] : [BILLING_ROW]),\n // A box that keeps a tax (VAT) setting has it as its own row, after the plan.\n ...(process.env.NEXT_PUBLIC_BOX_TAXES_IN_POLICIES === 'true' ? [TAXES_ROW] : []),\n {\n id: 'team',\n name: 'Team & permissions',\n subtitle: 'Who can do what in this business.',\n icon: Users,\n paths: [\n '/dashboard/settings/team',\n '/dashboard/settings/roles',\n '/dashboard/settings/activity',\n ],\n keywords: ['people', 'invite', 'roles', 'activity', 'seats'],\n permission: ['canManageTeam'],\n // A box with its own team screen (its roles and invitations come from\n // its engine, which does not serve this panel's routes) names it here.\n href: process.env.NEXT_PUBLIC_BOX_TEAM_PAGE || undefined,\n },\n ...(process.env.NEXT_PUBLIC_BOX_PAYMENT_APPROVALS === 'true'\n ? [PAYMENT_APPROVALS_ROW]\n : []),\n {\n id: 'policies',\n name: 'Policies',\n subtitle: 'What your customers agree to.',\n icon: Scale,\n paths: ['/dashboard/settings/policies'],\n keywords: ['returns', 'shipping', 'privacy', 'terms'],\n permission: ['canManageSettings'],\n },\n {\n // The switches no business type implies (Blog today) and the sample\n // content to start with, clear, or add again. One row for both. No\n // permission gate on the row, like the Demo content entry in the sidebar:\n // everyone sees the state, the panel shows the switches only to those who\n // may change them, and the demo card shows its actions only to the owner\n // and admins.\n id: 'extras',\n name: 'Extras',\n subtitle: 'Switches and sample content for your store.',\n icon: Sparkles,\n paths: [\n '/dashboard/settings/features',\n '/dashboard/settings/demo-content',\n ],\n keywords: [\n 'blog',\n 'posts',\n 'write',\n 'publish',\n 'switch',\n 'website',\n 'features',\n 'sample',\n 'products',\n 'starter',\n 'clear',\n 'example data',\n 'demo content',\n ],\n },\n {\n id: 'profile',\n name: 'Profile',\n subtitle: 'Your name and how we reach you.',\n icon: UserRound,\n paths: ['/dashboard/settings/profile'],\n keywords: ['name', 'email', 'photo', 'phone', 'close account'],\n },\n {\n id: 'security',\n name: 'Security',\n subtitle: 'How you sign in, and where you are signed in.',\n icon: Lock,\n paths: [\n '/dashboard/settings/devices',\n '/dashboard/settings/data-privacy',\n ],\n keywords: [\n 'password',\n 'passkeys',\n 'two-factor',\n 'sessions',\n 'devices',\n 'sign in',\n 'privacy',\n 'export',\n ],\n },\n {\n id: 'notifications',\n name: 'Notifications',\n subtitle: 'What we tell you, and what your customers hear from you.',\n icon: Bell,\n paths: ['/dashboard/settings/notifications'],\n keywords: ['alerts', 'low stock', 'email sender', 'digest', 'reminders'],\n },\n {\n id: 'preferences',\n name: 'Preferences',\n subtitle: 'How the dashboard looks and behaves for you.',\n icon: SlidersHorizontal,\n paths: [\n '/dashboard/settings/language',\n '/dashboard/settings/accessibility',\n ],\n keywords: [\n 'appearance',\n 'theme',\n 'dark',\n 'light',\n 'language',\n 'time zone',\n 'date format',\n 'accessibility',\n 'high contrast',\n 'reduce motion',\n 'text size',\n ],\n },\n // A box that meters nothing in the console leaves Usage out.\n ...(process.env.NEXT_PUBLIC_BOX_HIDE_USAGE === 'true'\n ? []\n : [\n {\n id: 'usage' as const,\n name: 'Usage',\n subtitle: 'What you have used this month.',\n icon: ChartColumn,\n paths: ['/dashboard/settings/usage'],\n keywords: ['credits', 'delivery', 'spend'],\n permission: ['canManageSettings' as const],\n },\n ]),\n {\n id: 'legal',\n name: 'Legal',\n subtitle: 'Your terms and privacy pages.',\n icon: FileText,\n paths: ['/dashboard/settings/legal'],\n keywords: ['terms', 'privacy policy', 'licences'],\n },\n];\n\n/** Where \"Get help\" goes. It leaves the window, so it is not a row above. */\nexport const HELP_HREF = '/dashboard/help';\nexport const HELP_ICON = CircleHelp;\n\nconst pathOf = (full: string) => full.split(/[?#]/)[0];\n\n/**\n * The row that owns an address: the longest of its paths that equals the\n * address or is a parent of it. General's own address is a prefix of every\n * other settings address, so it only owns itself.\n */\nexport function rowForHref(href: string | null | undefined): SettingsRowId {\n const path = pathOf(href ?? '/dashboard/settings');\n let best: { id: SettingsRowId; len: number } | null = null;\n for (const row of SETTINGS_ROWS) {\n for (const p of row.paths) {\n const owns =\n path === p || (p !== '/dashboard/settings' && path.startsWith(`${p}/`));\n if (owns && (!best || p.length > best.len))\n best = { id: row.id, len: p.length };\n }\n }\n return best?.id ?? 'general';\n}\n\n/** Rows whose name or inner sections match what was typed. */\nexport function matchRows<T extends { name: string; keywords: string[] }>(\n rows: T[],\n query: string,\n): T[] {\n const q = query.trim().toLowerCase();\n if (!q) return rows;\n return rows.filter(\n (r) =>\n r.name.toLowerCase().includes(q) || r.keywords.some((k) => k.includes(q)),\n );\n}\n"],"mappings":"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","names":[]}
|
|
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 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 | '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 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":"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","names":[]}
|