shutro-erp 0.3.2 → 0.3.3

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.
Files changed (130) hide show
  1. package/dist/account_portal/.kosh/bindings.js +1 -1
  2. package/dist/account_portal/.kosh/schema.d.ts +29 -1
  3. package/dist/accounting/.kosh/bindings.js +1 -1
  4. package/dist/accounting/.kosh/schema.d.ts +29 -1
  5. package/dist/assistant/index.js +1 -1
  6. package/dist/contacts/.kosh/bindings.js +1 -1
  7. package/dist/contacts/.kosh/schema.d.ts +29 -1
  8. package/dist/l10n_bd/.kosh/bindings.js +1 -1
  9. package/dist/l10n_bd/.kosh/schema.d.ts +29 -1
  10. package/dist/l10n_in/.kosh/bindings.js +1 -1
  11. package/dist/l10n_in/.kosh/schema.d.ts +29 -4
  12. package/dist/l10n_in/module.d.ts +0 -62
  13. package/dist/l10n_in/module.js +2 -4
  14. package/dist/payment/.kosh/bindings.js +1 -1
  15. package/dist/payment/.kosh/schema.d.ts +30 -2
  16. package/dist/payment/bkash.d.ts +28 -0
  17. package/dist/payment/bkash.js +169 -0
  18. package/dist/payment/index.d.ts +4 -0
  19. package/dist/payment/index.js +80 -24
  20. package/dist/payment/module.d.ts +7 -4
  21. package/dist/payment/module.js +3 -0
  22. package/dist/payment/money.d.ts +7 -0
  23. package/dist/payment/money.js +37 -0
  24. package/dist/payment/providers.d.ts +13 -2
  25. package/dist/payment/razorpay.d.ts +12 -0
  26. package/dist/payment/razorpay.js +149 -0
  27. package/dist/payment/sslcommerz.js +1 -1
  28. package/dist/payment/stripe.d.ts +15 -0
  29. package/dist/payment/stripe.js +182 -0
  30. package/dist/permissions/admin.d.ts +3 -0
  31. package/dist/permissions/admin.js +108 -10
  32. package/dist/permissions/index.d.ts +1 -0
  33. package/dist/permissions/index.js +3 -0
  34. package/dist/permissions/roles.d.ts +2 -0
  35. package/dist/permissions/roles.js +236 -0
  36. package/dist/portal/.kosh/bindings.js +1 -1
  37. package/dist/portal/.kosh/schema.d.ts +29 -1
  38. package/dist/product/.kosh/bindings.js +1 -1
  39. package/dist/product/.kosh/schema.d.ts +29 -1
  40. package/dist/project/.kosh/bindings.js +1 -1
  41. package/dist/project/.kosh/schema.d.ts +29 -1
  42. package/dist/project_portal/.kosh/bindings.js +1 -1
  43. package/dist/project_portal/.kosh/schema.d.ts +29 -1
  44. package/dist/purchase/.kosh/bindings.js +1 -1
  45. package/dist/purchase/.kosh/schema.d.ts +29 -1
  46. package/dist/purchase_account/.kosh/bindings.js +1 -1
  47. package/dist/purchase_account/.kosh/schema.d.ts +29 -1
  48. package/dist/purchase_portal/.kosh/bindings.js +1 -1
  49. package/dist/purchase_portal/.kosh/schema.d.ts +29 -1
  50. package/dist/sale_account/.kosh/bindings.js +1 -1
  51. package/dist/sale_account/.kosh/schema.d.ts +29 -1
  52. package/dist/sale_portal/.kosh/bindings.js +1 -1
  53. package/dist/sale_portal/.kosh/schema.d.ts +29 -1
  54. package/dist/sale_project/.kosh/bindings.js +1 -1
  55. package/dist/sale_project/.kosh/schema.d.ts +29 -1
  56. package/dist/sales/.kosh/bindings.js +1 -1
  57. package/dist/sales/.kosh/schema.d.ts +29 -1
  58. package/dist/server/auth/base.js +22 -0
  59. package/dist/server/auth/routes.d.ts +3 -0
  60. package/dist/server/auth/routes.js +29 -0
  61. package/dist/server/auth/service.d.ts +52 -0
  62. package/dist/server/auth/service.js +168 -3
  63. package/dist/server/identity.d.ts +2 -0
  64. package/dist/server/index.d.ts +1 -1
  65. package/dist/server/server.js +11 -3
  66. package/dist/settings/company.d.ts +34 -0
  67. package/dist/settings/company.js +187 -0
  68. package/dist/settings/index.d.ts +10 -10
  69. package/dist/settings/index.js +159 -30
  70. package/dist/settings/people.d.ts +16 -0
  71. package/dist/settings/people.js +505 -0
  72. package/dist/settings/security.d.ts +13 -0
  73. package/dist/settings/security.js +158 -0
  74. package/dist/settings/web/AuditPage.d.ts +8 -0
  75. package/dist/settings/web/AuditPage.js +74 -0
  76. package/dist/settings/web/CompaniesPage.d.ts +8 -0
  77. package/dist/settings/web/CompaniesPage.js +52 -0
  78. package/dist/settings/web/CompanyPage.d.ts +9 -0
  79. package/dist/settings/web/CompanyPage.js +94 -0
  80. package/dist/settings/web/OverviewPage.d.ts +9 -0
  81. package/dist/settings/web/OverviewPage.js +66 -0
  82. package/dist/settings/web/PeoplePage.d.ts +9 -0
  83. package/dist/settings/web/PeoplePage.js +163 -0
  84. package/dist/settings/web/ProfilePage.d.ts +14 -0
  85. package/dist/settings/web/ProfilePage.js +194 -0
  86. package/dist/settings/web/RolesEditor.d.ts +35 -0
  87. package/dist/settings/web/RolesEditor.js +49 -0
  88. package/dist/settings/web/RolesPage.d.ts +9 -0
  89. package/dist/settings/web/RolesPage.js +296 -0
  90. package/dist/settings/web/SecurityPage.d.ts +8 -0
  91. package/dist/settings/web/SecurityPage.js +81 -0
  92. package/dist/settings/web/index.d.ts +9 -0
  93. package/dist/settings/web/index.js +26 -1
  94. package/dist/settings/web/messages.js +566 -0
  95. package/dist/ui/api.d.ts +4 -0
  96. package/dist/ui/app.d.ts +5 -0
  97. package/dist/ui/app.js +108 -31
  98. package/dist/ui/components/primitives.d.ts +9 -1
  99. package/dist/ui/components/primitives.js +12 -4
  100. package/dist/ui/context.d.ts +10 -1
  101. package/dist/ui/context.js +60 -2
  102. package/dist/ui/i18n/messages.d.ts +46 -0
  103. package/dist/ui/i18n/messages.js +92 -0
  104. package/dist/ui/index.d.ts +2 -1
  105. package/dist/ui/index.js +2 -1
  106. package/dist/ui/lib/accent.d.ts +13 -0
  107. package/dist/ui/lib/accent.js +36 -0
  108. package/dist/ui/shell/Activate.d.ts +6 -0
  109. package/dist/ui/shell/Activate.js +39 -0
  110. package/dist/ui/shell/CommandPalette.d.ts +7 -1
  111. package/dist/ui/shell/CommandPalette.js +16 -2
  112. package/dist/ui/shell/Sidebar.d.ts +27 -0
  113. package/dist/ui/shell/Sidebar.js +84 -0
  114. package/dist/ui/shell/TwoFactorSetup.d.ts +5 -0
  115. package/dist/ui/shell/TwoFactorSetup.js +32 -0
  116. package/dist/ui/shell/shortcuts.d.ts +3 -0
  117. package/dist/ui/shell/shortcuts.js +13 -4
  118. package/dist/ui/styles.css +70 -28
  119. package/dist/ui/views/DateField.d.ts +10 -0
  120. package/dist/ui/views/DateField.js +129 -0
  121. package/dist/ui/views/FormView.js +161 -21
  122. package/dist/ui/views/ListView.d.ts +2 -0
  123. package/dist/ui/views/ListView.js +8 -3
  124. package/dist/ui/views/StatusChip.d.ts +10 -0
  125. package/dist/ui/views/StatusChip.js +22 -0
  126. package/dist/ui/views/widgets.d.ts +3 -0
  127. package/dist/ui/views/widgets.js +26 -8
  128. package/dist/views/types.d.ts +7 -0
  129. package/dist/views/validate.js +4 -0
  130. package/package.json +2 -2
package/dist/ui/app.js CHANGED
@@ -12,28 +12,32 @@ import { compileSearch, decodeSearch, encodeSearch } from '../search/index.js';
12
12
  import { onlineManager, QueryClient, QueryClientProvider, useQuery, useQueryClient, } from '@tanstack/react-query';
13
13
  import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client';
14
14
  import { createRootRoute, createRoute, createRouter, Link, Navigate, Outlet, RouterProvider, useNavigate, useParams, useSearch, } from '@tanstack/react-router';
15
- import { CalendarDays, ChartColumn, ChartGantt, Columns3, Grid2x2, Grid3x3, Languages, LayoutDashboard, List, LogOut, Map as MapIcon, Monitor, Moon, Network, Plus, Search, Sheet, Sun, Table2, UserRound, } from 'lucide-react';
15
+ import { CalendarDays, ChartColumn, ChartGantt, ChevronRight, ChevronsUpDown, Columns3, Grid2x2, Languages, LayoutDashboard, List, LogOut, Map as MapIcon, Monitor, Moon, Network, PanelLeft, Plus, Search, Sheet, Sun, Table2, } from 'lucide-react';
16
16
  import { Direction } from 'radix-ui';
17
17
  import { createContext, useCallback, useContext, useEffect, useMemo, useState, } from 'react';
18
18
  import { I18nextProvider, useTranslation } from 'react-i18next';
19
19
  import { ApiError } from './api.js';
20
- import { Alert, Button, Menu, MenuCheckboxItem, MenuContent, MenuItem, MenuLabel, MenuSeparator, MenuTrigger, Select, Spinner, } from './components/primitives.js';
20
+ import { Alert, Button, Menu, MenuCheckboxItem, MenuContent, MenuItem, MenuLabel, MenuSeparator, MenuTrigger, Spinner, } from './components/primitives.js';
21
21
  import { KoshProvider, useKosh } from './context.js';
22
22
  import { DesignProvider } from './design.js';
23
23
  import { addMessages, createI18n, directionOf } from './i18n/index.js';
24
24
  import { LANGUAGES } from './i18n/messages.js';
25
+ import { cn } from './lib/cn.js';
25
26
  import { claimOfflineCache, createOfflinePersister, dropOfflineCache, OFFLINE_MAX_AGE_MS, offlinePersistOptions, } from './offline/persist.js';
26
27
  import { OfflineQueue } from './offline/queue.js';
27
28
  import { setPrefill, takePrefill } from './prefill.js';
28
29
  import { onAppUpdate, registerServiceWorker } from './pwa/register.js';
29
30
  import { createRealtimeClient } from './realtime.js';
30
31
  import { SearchBar } from './search/SearchBar.js';
32
+ import { Activate } from './shell/Activate.js';
31
33
  import { CommandPalette, PaletteContext } from './shell/CommandPalette.js';
32
34
  import { Home } from './shell/Home.js';
33
35
  import { Login } from './shell/Login.js';
34
36
  import { OfflineMenu } from './shell/OfflineMenu.js';
35
37
  import { rememberRecord } from './shell/recent.js';
38
+ import { Sidebar, SidebarDrawer, useSidebarCollapsed } from './shell/Sidebar.js';
36
39
  import { ShortcutsHelp, useShortcuts } from './shell/shortcuts.js';
40
+ import { TwoFactorSetup } from './shell/TwoFactorSetup.js';
37
41
  import { ToastProvider, useToast } from './shell/toast.js';
38
42
  import { neighbours } from './views/listMemory.js';
39
43
  /**
@@ -100,6 +104,11 @@ function useServices() {
100
104
  throw new Error('Shutro ERP routes render inside <KoshApp>.');
101
105
  return value;
102
106
  }
107
+ /** The app's language and its switch (as the language menu does: direction, the server's labels). */
108
+ export function useLanguage() {
109
+ const { lang, setLang } = useServices();
110
+ return { lang, setLang };
111
+ }
103
112
  // ------------------------------------------------------------------------------------------------- shell
104
113
  function firstAction(menus) {
105
114
  for (const m of menus) {
@@ -111,61 +120,91 @@ function firstAction(menus) {
111
120
  }
112
121
  return undefined;
113
122
  }
114
- function MenuLinks({ app, menus, }) {
115
- const navigate = useNavigate();
116
- const entries = menus.flatMap((root) => (root.action === undefined ? root.children : [root]));
117
- return (_jsx("ul", { className: "flex flex-wrap items-center gap-1", children: entries.map((m) => m.children.length > 0 ? (_jsx("li", { children: _jsxs(Menu, { children: [_jsx(MenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "sm", children: m.label }) }), _jsx(MenuContent, { align: "start", children: m.children.map((c) => c.action === undefined ? null : (_jsx(MenuItem, { onSelect: () => void navigate({
118
- to: '/$app/a/$action',
119
- params: { app, action: c.action },
120
- }), children: c.label }, c.id))) })] }) }, m.id)) : m.action === undefined ? null : (_jsx("li", { children: _jsx(Link, { to: "/$app/a/$action", params: { app, action: m.action }, className: "rounded-md px-3 py-1.5 text-sm hover:bg-muted", activeProps: { 'aria-current': 'page', className: 'bg-muted font-medium' }, children: m.label }) }, m.id))) }));
121
- }
122
123
  function ThemeMenu() {
123
124
  const { t } = useTranslation();
124
- const { theme, setTheme } = useKosh();
125
+ const { theme, setTheme, density, setDensity } = useKosh();
125
126
  const icons = { light: Sun, dark: Moon, system: Monitor };
126
127
  const Icon = icons[theme];
127
- return (_jsxs(Menu, { children: [_jsx(MenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": t('shell.theme'), children: _jsx(Icon, { "aria-hidden": true }) }) }), _jsxs(MenuContent, { children: [_jsx(MenuLabel, { children: t('shell.theme') }), ['light', 'dark', 'system'].map((choice) => (_jsx(MenuCheckboxItem, { checked: theme === choice, onCheckedChange: () => setTheme(choice), children: t(`shell.${choice}`) }, choice)))] })] }));
128
+ return (_jsxs(Menu, { children: [_jsx(MenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": t('shell.theme'), children: _jsx(Icon, { "aria-hidden": true }) }) }), _jsxs(MenuContent, { children: [_jsx(MenuLabel, { children: t('shell.theme') }), ['light', 'dark', 'system'].map((choice) => (_jsx(MenuCheckboxItem, { checked: theme === choice, onCheckedChange: () => setTheme(choice), children: t(`shell.${choice}`) }, choice))), _jsx(MenuSeparator, {}), _jsx(MenuLabel, { children: t('shell.density') }), ['comfortable', 'compact'].map((choice) => (_jsx(MenuCheckboxItem, { checked: density === choice, onCheckedChange: () => setDensity(choice), children: t(`views.${choice}`) }, choice)))] })] }));
128
129
  }
129
130
  function LanguageMenu() {
130
131
  const { t } = useTranslation();
131
132
  const { lang, setLang } = useServices();
132
133
  return (_jsxs(Menu, { children: [_jsx(MenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": t('shell.language'), children: _jsx(Languages, { "aria-hidden": true }) }) }), _jsxs(MenuContent, { children: [_jsx(MenuLabel, { children: t('shell.language') }), LANGUAGES.map((l) => (_jsx(MenuCheckboxItem, { checked: lang === l.code, onCheckedChange: () => setLang(l.code), children: _jsx("span", { lang: l.code.replace('_', '-'), children: l.name }) }, l.code)))] })] }));
133
134
  }
134
- function CompanySwitcher({ me, company, onChange, }) {
135
+ /** The company at the top of the sidebar: its initial and name, or the switcher when the user has several. */
136
+ function CompanyBlock({ me, company, collapsed, onChange, }) {
135
137
  const { t } = useTranslation();
136
138
  const { api } = useServices();
137
139
  const names = useQuery({
138
140
  queryKey: ['names', 'res.company', me.companies],
139
141
  queryFn: () => api.names('res.company', { ids: me.companies }),
140
- enabled: me.companies.length > 1,
142
+ enabled: me.companies.length > 0,
141
143
  });
144
+ const name = names.data?.find((c) => c.id === company)?.displayName ?? t('app.name');
145
+ const badge = (_jsx("span", { "aria-hidden": true, className: "grid size-7 shrink-0 place-items-center rounded-md bg-primary text-xs font-semibold text-primary-foreground", children: name.slice(0, 1).toUpperCase() }));
146
+ if (collapsed)
147
+ return _jsx("span", { title: name, children: badge });
142
148
  if (me.companies.length < 2)
143
- return null;
144
- return (_jsx(Select, { label: t('shell.company'), className: "h-8 w-48", value: String(company), options: (names.data ?? []).map((c) => ({ value: String(c.id), label: c.displayName })), onChange: (v) => onChange(Number(v)) }));
149
+ return (_jsxs("div", { className: "flex items-center gap-2.5 px-1 py-1", children: [badge, _jsx("span", { className: "truncate text-sm font-semibold", children: name })] }));
150
+ // Several companies: a switcher (also in the palette: "Switch to …").
151
+ return (_jsxs(Menu, { children: [_jsx(MenuTrigger, { asChild: true, children: _jsxs("button", { type: "button", "aria-label": t('shell.switchCompany', { name }), className: "flex w-full items-center gap-2.5 rounded-md px-1 py-1 text-start hover:bg-muted", children: [badge, _jsx("span", { className: "min-w-0 flex-1 truncate text-sm font-semibold", children: name }), _jsx(ChevronsUpDown, { className: "size-4 shrink-0 text-muted-foreground", "aria-hidden": true })] }) }), _jsxs(MenuContent, { align: "start", className: "w-64", children: [_jsx(MenuLabel, { children: t('shell.company') }), (names.data ?? []).map((c) => (_jsx(MenuCheckboxItem, { checked: c.id === company, onCheckedChange: () => {
152
+ if (c.id !== company)
153
+ onChange(c.id);
154
+ }, children: _jsxs("span", { className: "flex items-center gap-2", children: [_jsx("span", { "aria-hidden": true, className: "grid size-5 place-items-center rounded bg-muted text-[10px] font-semibold", children: c.displayName.slice(0, 1).toUpperCase() }), c.displayName] }) }, c.id)))] })] }));
145
155
  }
146
- function Shell() {
147
- const { api, registry, lang, realtime, offline, persister } = useServices();
156
+ /** The user at the bottom of the sidebar: their pages and signing out. */
157
+ function UserMenu({ user, collapsed, }) {
148
158
  const { t } = useTranslation();
159
+ const { api, registry, persister } = useServices();
149
160
  const queryClient = useQueryClient();
150
161
  const navigate = useNavigate();
162
+ const initials = user.name
163
+ .split(/\s+/)
164
+ .map((w) => w.slice(0, 1))
165
+ .join('')
166
+ .slice(0, 2)
167
+ .toUpperCase();
168
+ return (_jsxs(Menu, { children: [_jsx(MenuTrigger, { asChild: true, children: _jsxs("button", { type: "button", "aria-label": user.name, className: cn('flex w-full min-w-0 items-center gap-2 rounded-md px-1.5 py-1.5 text-sm hover:bg-muted', collapsed && 'justify-center'), children: [_jsx("span", { "aria-hidden": true, className: "grid size-7 shrink-0 place-items-center rounded-full bg-muted text-xs font-medium", children: initials }), collapsed ? null : _jsx("span", { className: "truncate", children: user.name })] }) }), _jsxs(MenuContent, { align: "start", side: "top", children: [_jsx(MenuLabel, { children: user.name }), registry.userPages().map((p) => (_jsx(MenuItem, { onSelect: () => void navigate({ to: '/me/$page', params: { page: p.name } }), children: t(p.label) }, p.name))), _jsxs(MenuItem, { onSelect: async () => {
169
+ await api.logout();
170
+ queryClient.clear();
171
+ await dropOfflineCache(persister);
172
+ await navigate({ to: '/login' });
173
+ }, children: [_jsx(LogOut, { className: "size-4", "aria-hidden": true }), t('auth.signOut')] })] })] }));
174
+ }
175
+ function Shell() {
176
+ const { api, registry, lang, realtime, offline } = useServices();
177
+ const { t } = useTranslation();
178
+ const queryClient = useQueryClient();
151
179
  const params = useParams({ strict: false });
152
180
  const me = useQuery({ queryKey: ['me'], queryFn: () => api.me(), retry: false });
153
181
  const [company, setCompany] = useState(undefined);
154
182
  const [palette, setPalette] = useState(false);
155
183
  const [help, setHelp] = useState(false);
156
- const shortcutHandlers = useMemo(() => ({ palette: () => setPalette(true), help: () => setHelp(true) }), []);
184
+ const [drawer, setDrawer] = useState(false);
185
+ const [collapsed, setCollapsed] = useSidebarCollapsed();
186
+ const shortcutHandlers = useMemo(() => ({
187
+ palette: () => setPalette(true),
188
+ help: () => setHelp(true),
189
+ sidebar: () => setCollapsed(!collapsed),
190
+ }), [collapsed, setCollapsed]);
157
191
  useShortcuts(shortcutHandlers);
158
192
  const openHelp = useCallback(() => setHelp(true), []);
159
193
  const openPalette = useCallback(() => setPalette(true), []);
160
194
  const apps = useQuery({
161
195
  queryKey: ['apps', company],
162
196
  queryFn: () => api.apps(),
163
- enabled: me.isSuccess,
197
+ enabled: me.isSuccess && me.data.twoFactorSetupRequired !== true,
198
+ });
199
+ const companyNames = useQuery({
200
+ queryKey: ['names', 'res.company', me.data?.companies],
201
+ queryFn: () => api.names('res.company', { ids: me.data?.companies ?? [] }),
202
+ enabled: (me.data?.companies.length ?? 0) > 1,
164
203
  });
165
204
  const menus = useQuery({
166
205
  queryKey: ['menus', params.app, company],
167
206
  queryFn: () => api.menus(params.app),
168
- enabled: me.isSuccess && params.app !== undefined,
207
+ enabled: me.isSuccess && me.data.twoFactorSetupRequired !== true && params.app !== undefined,
169
208
  });
170
209
  if (me.error instanceof ApiError && me.error.status === 401)
171
210
  return _jsx(Navigate, { to: "/login" });
@@ -174,19 +213,47 @@ function Shell() {
174
213
  return _jsx(Alert, { children: me.error.message });
175
214
  if (me.data === undefined)
176
215
  return _jsx(Spinner, { label: t('app.loading') });
216
+ // The policy asks everyone for a second factor (RFC 0019 §3.5): nothing else until it is set up.
217
+ if (me.data.twoFactorSetupRequired === true)
218
+ return _jsx(TwoFactorSetup, { api: api });
177
219
  const user = me.data;
178
220
  const current = company ?? user.company ?? user.companies[0] ?? 0;
179
221
  const app = apps.data?.find((a) => a.code === params.app);
180
- return (_jsx(KoshProvider, { api: api, registry: registry, uid: user.id, companyId: current, lang: lang, tz: Intl.DateTimeFormat().resolvedOptions().timeZone, grouping: user.groupings?.[String(current)] ?? 'international', ...(realtime === undefined ? {} : { realtime }), ...(offline === undefined ? {} : { offline }), children: _jsx(ToastProvider, { children: _jsxs(DesignProvider, { children: [_jsx(OfflineAgent, { uid: user.id }), _jsxs(PaletteContext.Provider, { value: openPalette, children: [_jsx("a", { href: "#content", className: "sr-only focus:not-sr-only focus:absolute focus:start-2 focus:top-2 focus:z-50 focus:rounded focus:bg-surface focus:px-3 focus:py-2", children: t('shell.skip') }), _jsx("header", { className: "sticky top-0 z-30 border-b bg-surface", children: _jsxs("div", { className: "flex h-12 items-center gap-2 px-3", children: [_jsxs(Menu, { children: [_jsx(MenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": t('shell.apps'), children: _jsx(Grid3x3, { "aria-hidden": true }) }) }), _jsxs(MenuContent, { align: "start", children: [_jsx(MenuItem, { onSelect: () => void navigate({ to: '/' }), children: t('shell.apps') }), _jsx(MenuSeparator, {}), (apps.data ?? []).map((a) => (_jsx(MenuItem, { onSelect: () => void navigate({ to: '/$app', params: { app: a.code } }), children: a.label }, a.code)))] })] }), _jsx(Link, { to: "/", className: "font-semibold", children: app?.label ?? t('app.name') }), _jsx("nav", { "aria-label": t('shell.menu'), className: "ms-2 flex-1", children: params.app === undefined ? null : (_jsx(MenuLinks, { app: params.app, menus: menus.data ?? [] })) }), _jsxs(Button, { variant: "secondary", size: "sm", className: "hidden h-8 w-56 justify-between text-muted-foreground sm:flex", onClick: () => setPalette(true), "aria-keyshortcuts": "Control+K Meta+K", children: [_jsxs("span", { className: "flex items-center gap-2", children: [_jsx(Search, { className: "size-4", "aria-hidden": true }), t('palette.button')] }), _jsx("kbd", { className: "rounded border bg-surface px-1.5 text-xs", children: "Ctrl K" })] }), _jsx(Button, { variant: "ghost", size: "icon", className: "sm:hidden", "aria-label": t('palette.button'), onClick: () => setPalette(true), children: _jsx(Search, { "aria-hidden": true }) }), _jsx(OfflineMenu, {}), registry.headerItems().map(({ name, component: Item }) => (_jsx(Item, { api: api }, name))), _jsx(CompanySwitcher, { me: user, company: current, onChange: (id) => {
181
- api.setCompany(id);
182
- setCompany(id);
183
- void queryClient.invalidateQueries();
184
- } }), _jsx(LanguageMenu, {}), _jsx(ThemeMenu, {}), _jsxs(Menu, { children: [_jsx(MenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": user.name, children: _jsx(UserRound, { "aria-hidden": true }) }) }), _jsxs(MenuContent, { children: [_jsx(MenuLabel, { children: user.name }), registry.userPages().map((p) => (_jsx(MenuItem, { onSelect: () => void navigate({ to: '/me/$page', params: { page: p.name } }), children: t(p.label) }, p.name))), _jsxs(MenuItem, { onSelect: async () => {
185
- await api.logout();
186
- queryClient.clear();
187
- await dropOfflineCache(persister);
188
- await navigate({ to: '/login' });
189
- }, children: [_jsx(LogOut, { className: "size-4", "aria-hidden": true }), t('auth.signOut')] })] })] })] }) }), user.administratorNeedsSecondFactor ? (_jsx("div", { className: "px-4 pt-3", children: _jsx(Alert, { tone: "info", children: t('auth.adminNeeds2fa') }) })) : null, _jsx("main", { id: "content", className: "mx-auto w-full max-w-[100rem] p-4", children: _jsx(Outlet, {}) }), _jsx(CommandPalette, { open: palette, onOpenChange: setPalette, api: api, onShortcuts: openHelp }), _jsx(ShortcutsHelp, { open: help, onOpenChange: setHelp })] })] }) }) }));
222
+ // A page of the user menu (`/me/<page>`): the page bar names it.
223
+ const userPage = params.page === undefined
224
+ ? undefined
225
+ : registry.userPages().find((p) => p.name === params.page);
226
+ /** The open section's label (the menu entry of the current action), for the page bar. */
227
+ const section = (() => {
228
+ const walk = (nodes) => {
229
+ for (const n of nodes) {
230
+ if (n.action !== undefined && n.action === params.action)
231
+ return n.label;
232
+ const found = walk(n.children);
233
+ if (found !== undefined)
234
+ return found;
235
+ }
236
+ return undefined;
237
+ };
238
+ return params.action === undefined ? undefined : walk(menus.data ?? []);
239
+ })();
240
+ const switchCompany = (id) => {
241
+ setPalette(false);
242
+ api.setCompany(id);
243
+ setCompany(id);
244
+ void queryClient.invalidateQueries();
245
+ };
246
+ const sidebar = (inDrawer) => (_jsx(Sidebar, { api: api, apps: apps.data ?? [], current: params.app, company: current, collapsed: inDrawer ? false : collapsed, onCollapse: inDrawer ? undefined : setCollapsed, onSearch: () => {
247
+ setDrawer(false);
248
+ setPalette(true);
249
+ }, top: _jsx(CompanyBlock, { me: user, company: current, collapsed: inDrawer ? false : collapsed, onChange: switchCompany }), bottom: _jsx(UserMenu, { user: user, collapsed: inDrawer ? false : collapsed }), onNavigate: inDrawer ? () => setDrawer(false) : undefined }));
250
+ return (_jsx(KoshProvider, { api: api, registry: registry, uid: user.id, companyId: current, lang: lang, tz: Intl.DateTimeFormat().resolvedOptions().timeZone, grouping: user.groupings?.[String(current)] ?? 'international', accent: user.accents?.[String(current)] ?? null, ...(realtime === undefined ? {} : { realtime }), ...(offline === undefined ? {} : { offline }), switchCompany: switchCompany, children: _jsx(ToastProvider, { children: _jsxs(DesignProvider, { children: [_jsx(OfflineAgent, { uid: user.id }), _jsxs(PaletteContext.Provider, { value: openPalette, children: [_jsx("a", { href: "#content", className: "sr-only focus:not-sr-only focus:absolute focus:start-2 focus:top-2 focus:z-50 focus:rounded focus:bg-surface focus:px-3 focus:py-2", children: t('shell.skip') }), _jsxs("div", { className: "flex min-h-screen", children: [_jsx("aside", { "aria-label": t('shell.sidebar'), className: cn('sticky top-0 hidden h-screen shrink-0 border-e bg-surface transition-[width] lg:block', collapsed ? 'w-14' : 'w-60'), children: sidebar(false) }), _jsx(SidebarDrawer, { open: drawer, onOpenChange: setDrawer, children: sidebar(true) }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [_jsx("header", { className: "sticky top-0 z-30 border-b bg-surface", children: _jsxs("div", { className: "flex h-12 items-center gap-2 px-3 lg:px-4", children: [_jsx(Button, { variant: "ghost", size: "icon", className: "lg:hidden", "aria-label": t('shell.openMenu'), onClick: () => setDrawer(true), children: _jsx(PanelLeft, { className: "rtl:rotate-180", "aria-hidden": true }) }), _jsx("nav", { "aria-label": t('shell.location'), className: "flex min-w-0 flex-1 items-center gap-1.5 text-sm", children: app === undefined ? (_jsx("span", { className: "font-semibold", children: userPage !== undefined
251
+ ? t(userPage.label)
252
+ : params.app === undefined
253
+ ? t('shell.home')
254
+ : t('app.name') })) : (_jsxs(_Fragment, { children: [_jsx(Link, { to: "/$app", params: { app: app.code }, className: "font-semibold hover:underline", children: app.label }), section === undefined ? null : (_jsxs(_Fragment, { children: [_jsx(ChevronRight, { className: "size-3.5 text-muted-foreground rtl:rotate-180", "aria-hidden": true }), _jsx(Link, { to: "/$app/a/$action", params: { app: app.code, action: params.action ?? '' }, className: "truncate text-muted-foreground hover:text-foreground hover:underline", children: section })] }))] })) }), _jsx(Button, { variant: "ghost", size: "icon", className: "lg:hidden", "aria-label": t('palette.button'), onClick: () => setPalette(true), children: _jsx(Search, { "aria-hidden": true }) }), _jsx(OfflineMenu, {}), registry.headerItems().map(({ name, component: Item }) => (_jsx(Item, { api: api }, name))), _jsx(LanguageMenu, {}), _jsx(ThemeMenu, {})] }) }), user.administratorNeedsSecondFactor ? (_jsx("div", { className: "px-4 pt-3", children: _jsx(Alert, { tone: "info", children: t('auth.adminNeeds2fa') }) })) : null, _jsx("main", { id: "content", className: "mx-auto w-full max-w-[100rem] flex-1 p-4 lg:p-6", children: _jsx(Outlet, {}) })] })] }), _jsx(CommandPalette, { open: palette, onOpenChange: setPalette, api: api, onShortcuts: openHelp, companies: (companyNames.data ?? [])
255
+ .filter((c) => c.id !== current)
256
+ .map((c) => ({ id: c.id, name: c.displayName })), onSwitchCompany: switchCompany }), _jsx(ShortcutsHelp, { open: help, onOpenChange: setHelp })] })] }) }) }));
190
257
  }
191
258
  function AppHome() {
192
259
  const { api } = useServices();
@@ -410,6 +477,10 @@ function OpenRecordPage() {
410
477
  return _jsx(Alert, { children: t('app.notFound') });
411
478
  return (_jsx(Navigate, { to: "/$app/a/$action/$id", params: { app: route.app, action: route.action, id }, replace: true }));
412
479
  }
480
+ function ActivatePage() {
481
+ const navigate = useNavigate();
482
+ return _jsx(Activate, { api: useServices().api, onSignedIn: () => void navigate({ to: '/' }) });
483
+ }
413
484
  function LoginPage() {
414
485
  const navigate = useNavigate();
415
486
  return _jsx(Login, { api: useServices().api, onSignedIn: () => void navigate({ to: '/' }) });
@@ -421,6 +492,11 @@ const loginRoute = createRoute({
421
492
  path: '/login',
422
493
  component: LoginPage,
423
494
  });
495
+ const activateRoute = createRoute({
496
+ getParentRoute: () => rootRoute,
497
+ path: '/activate',
498
+ component: ActivatePage,
499
+ });
424
500
  const shellRoute = createRoute({ getParentRoute: () => rootRoute, id: 'shell', component: Shell });
425
501
  const indexRoute = createRoute({
426
502
  getParentRoute: () => shellRoute,
@@ -469,6 +545,7 @@ const userPageRoute = createRoute({
469
545
  });
470
546
  const routeTree = rootRoute.addChildren([
471
547
  loginRoute,
548
+ activateRoute,
472
549
  shellRoute.addChildren([
473
550
  indexRoute,
474
551
  openRoute,
@@ -1,4 +1,4 @@
1
- import { Checkbox as RCheckbox, DropdownMenu as RMenu, Tabs as RTabs } from 'radix-ui';
1
+ import { Checkbox as RCheckbox, DropdownMenu as RMenu, Popover as RPopover, Tabs as RTabs } from 'radix-ui';
2
2
  import type { ComponentProps, ReactElement, ReactNode } from 'react';
3
3
  export type ButtonProps = ComponentProps<'button'> & {
4
4
  readonly variant?: 'primary' | 'secondary' | 'ghost' | 'danger' | 'link';
@@ -10,6 +10,10 @@ export declare function Button({ className, variant, size, asChild, busy, childr
10
10
  export declare function Input({ className, ...props }: ComponentProps<'input'>): ReactElement;
11
11
  export declare function Textarea({ className, ...props }: ComponentProps<'textarea'>): ReactElement;
12
12
  export declare function Label({ className, ...props }: ComponentProps<'label'>): ReactElement;
13
+ /** The badge's classes, for elements that look like one (the status chip). */
14
+ export declare const badgeVariants: (props?: {
15
+ readonly tone?: 'neutral' | 'info' | 'success' | 'warning' | 'danger' | null;
16
+ }) => string;
13
17
  export declare function Badge({ className, tone, ...props }: ComponentProps<'span'> & {
14
18
  readonly tone?: 'neutral' | 'info' | 'success' | 'warning' | 'danger' | null;
15
19
  }): ReactElement;
@@ -36,6 +40,10 @@ export declare function MenuCheckboxItem({ className, children, ...props }: Comp
36
40
  export declare function MenuLabel({ className, ...props }: ComponentProps<typeof RMenu.Label>): ReactElement;
37
41
  export declare function MenuSeparator(): ReactElement;
38
42
  /** A modal dialog with a title (required for screen readers) and a close button. */
43
+ /** A popover (Radix): focus moves in and back, Escape closes, positioned beside its trigger. */
44
+ export declare const Popover: typeof RPopover.Root;
45
+ export declare const PopoverTrigger: typeof RPopover.Trigger;
46
+ export declare function PopoverContent({ className, align, sideOffset, ...props }: ComponentProps<typeof RPopover.Content>): ReactElement;
39
47
  export declare function Dialog({ open, onOpenChange, title, description, children, footer, wide, }: {
40
48
  readonly open: boolean;
41
49
  readonly onOpenChange: (open: boolean) => void;
@@ -6,7 +6,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
6
6
  */
7
7
  import { cva } from 'class-variance-authority';
8
8
  import { Check, ChevronDown, Loader2, X } from 'lucide-react';
9
- import { Checkbox as RCheckbox, Dialog as RDialog, DropdownMenu as RMenu, Select as RSelect, Tabs as RTabs, Slot, } from 'radix-ui';
9
+ import { Checkbox as RCheckbox, Dialog as RDialog, DropdownMenu as RMenu, Popover as RPopover, Select as RSelect, Tabs as RTabs, Slot, } from 'radix-ui';
10
10
  import { cn } from '../lib/cn.js';
11
11
  const buttonVariants = cva('inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors disabled:pointer-events-none disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0', {
12
12
  variants: {
@@ -17,7 +17,7 @@ const buttonVariants = cva('inline-flex items-center justify-center gap-2 whites
17
17
  danger: 'bg-danger text-danger-foreground hover:bg-danger/90',
18
18
  link: 'text-primary underline-offset-4 hover:underline',
19
19
  },
20
- size: { sm: 'h-8 px-3', md: 'h-9 px-4', icon: 'size-9' },
20
+ size: { sm: 'h-8 px-3', md: 'h-[var(--control-h)] px-4', icon: 'size-[var(--control-h)]' },
21
21
  },
22
22
  defaultVariants: { variant: 'secondary', size: 'md' },
23
23
  });
@@ -27,7 +27,7 @@ export function Button({ className, variant, size, asChild, busy, children, disa
27
27
  }
28
28
  const field = 'w-full rounded-md border border-input bg-surface px-3 text-sm text-surface-foreground placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-60 read-only:bg-muted aria-invalid:border-danger';
29
29
  export function Input({ className, ...props }) {
30
- return _jsx("input", { className: cn(field, 'h-9', className), ...props });
30
+ return _jsx("input", { className: cn(field, 'h-[var(--control-h)]', className), ...props });
31
31
  }
32
32
  export function Textarea({ className, ...props }) {
33
33
  return _jsx("textarea", { className: cn(field, 'min-h-20 py-2', className), ...props });
@@ -47,6 +47,8 @@ const badge = cva('inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-
47
47
  },
48
48
  defaultVariants: { tone: 'neutral' },
49
49
  });
50
+ /** The badge's classes, for elements that look like one (the status chip). */
51
+ export const badgeVariants = badge;
50
52
  export function Badge({ className, tone, ...props }) {
51
53
  return _jsx("span", { className: cn(badge({ tone }), className), ...props });
52
54
  }
@@ -55,7 +57,7 @@ export function Checkbox({ className, ...props }) {
55
57
  }
56
58
  /** A select over `options` (`null` value: empty). */
57
59
  export function Select({ value, options, onChange, disabled, placeholder, id, label, className, }) {
58
- return (_jsxs(RSelect.Root, { ...(value === null ? {} : { value }), onValueChange: onChange, ...(disabled === true ? { disabled } : {}), children: [_jsxs(RSelect.Trigger, { ...(id === undefined ? {} : { id }), "aria-label": label, className: cn(field, 'flex h-9 items-center justify-between gap-2 text-start', className), children: [_jsx(RSelect.Value, { placeholder: placeholder }), _jsx(RSelect.Icon, { children: _jsx(ChevronDown, { className: "size-4 opacity-60", "aria-hidden": true }) })] }), _jsx(RSelect.Portal, { children: _jsx(RSelect.Content, { position: "popper", sideOffset: 4, className: "z-50 min-w-[var(--radix-select-trigger-width)] overflow-hidden rounded-md border bg-surface text-surface-foreground shadow-lg", children: _jsx(RSelect.Viewport, { className: "p-1", children: options.map((o) => (_jsxs(RSelect.Item, { value: o.value, className: "relative flex cursor-default select-none items-center rounded-sm py-1.5 pe-2 ps-7 text-sm outline-none data-[highlighted]:bg-muted", children: [_jsx(RSelect.ItemIndicator, { className: "absolute start-2", children: _jsx(Check, { className: "size-3.5", "aria-hidden": true }) }), _jsx(RSelect.ItemText, { children: o.label })] }, o.value))) }) }) })] }));
60
+ return (_jsxs(RSelect.Root, { ...(value === null ? {} : { value }), onValueChange: onChange, ...(disabled === true ? { disabled } : {}), children: [_jsxs(RSelect.Trigger, { ...(id === undefined ? {} : { id }), "aria-label": label, className: cn(field, 'flex h-[var(--control-h)] items-center justify-between gap-2 text-start', className), children: [_jsx(RSelect.Value, { placeholder: placeholder }), _jsx(RSelect.Icon, { children: _jsx(ChevronDown, { className: "size-4 opacity-60", "aria-hidden": true }) })] }), _jsx(RSelect.Portal, { children: _jsx(RSelect.Content, { position: "popper", sideOffset: 4, className: "z-50 min-w-[var(--radix-select-trigger-width)] overflow-hidden rounded-md border bg-surface text-surface-foreground shadow-lg", children: _jsx(RSelect.Viewport, { className: "p-1", children: options.map((o) => (_jsxs(RSelect.Item, { value: o.value, className: "relative flex cursor-default select-none items-center rounded-sm py-1.5 pe-2 ps-7 text-sm outline-none data-[highlighted]:bg-muted", children: [_jsx(RSelect.ItemIndicator, { className: "absolute start-2", children: _jsx(Check, { className: "size-3.5", "aria-hidden": true }) }), _jsx(RSelect.ItemText, { children: o.label })] }, o.value))) }) }) })] }));
59
61
  }
60
62
  export const Menu = RMenu.Root;
61
63
  export const MenuTrigger = RMenu.Trigger;
@@ -75,6 +77,12 @@ export function MenuSeparator() {
75
77
  return _jsx(RMenu.Separator, { className: "my-1 h-px bg-border" });
76
78
  }
77
79
  /** A modal dialog with a title (required for screen readers) and a close button. */
80
+ /** A popover (Radix): focus moves in and back, Escape closes, positioned beside its trigger. */
81
+ export const Popover = RPopover.Root;
82
+ export const PopoverTrigger = RPopover.Trigger;
83
+ export function PopoverContent({ className, align = 'start', sideOffset = 6, ...props }) {
84
+ return (_jsx(RPopover.Portal, { children: _jsx(RPopover.Content, { align: align, sideOffset: sideOffset, className: cn('z-50 rounded-lg border bg-surface p-3 text-surface-foreground shadow-lg outline-none', className), ...props }) }));
85
+ }
78
86
  export function Dialog({ open, onOpenChange, title, description, children, footer, wide, }) {
79
87
  return (_jsx(RDialog.Root, { open: open, onOpenChange: onOpenChange, children: _jsxs(RDialog.Portal, { children: [_jsx(RDialog.Overlay, { className: "fixed inset-0 z-40 bg-black/40" }), _jsxs(RDialog.Content, { className: cn('fixed start-1/2 top-1/2 z-50 -translate-y-1/2 rtl:translate-x-1/2 ltr:-translate-x-1/2 rounded-lg border bg-surface p-5 text-surface-foreground shadow-xl', wide === true
80
88
  ? 'max-h-[calc(100vh-2rem)] w-[min(56rem,calc(100vw-2rem))] overflow-y-auto'
@@ -17,16 +17,22 @@ export interface KoshContextValue {
17
17
  readonly format: Formatter;
18
18
  readonly theme: ThemeChoice;
19
19
  setTheme(theme: ThemeChoice): void;
20
+ /** Comfortable or compact (RFC 0018): lists, forms and controls. */
21
+ readonly density: Density;
22
+ setDensity(density: Density): void;
20
23
  /** The realtime gateway's client, when the app has one: live lists, records and presence. */
21
24
  readonly realtime?: RealtimeClient;
22
25
  /** Changes saved offline, when the app keeps them: a form that cannot reach the server saves here. */
23
26
  readonly offline?: OfflineQueue;
27
+ /** Switches to another of the user's companies, when the shell offers it (Settings › Companies). */
28
+ readonly switchCompany?: (company: number) => void;
24
29
  }
25
30
  /** The renderer context. @throws outside {@link KoshProvider} */
26
31
  export declare function useKosh(): KoshContextValue;
32
+ export type Density = 'comfortable' | 'compact';
27
33
  /** Applies the theme to `<html>` (the `dark` class) and follows the system when asked to. */
28
34
  declare function useTheme(): [ThemeChoice, (t: ThemeChoice) => void];
29
- export declare function KoshProvider({ api, registry, uid, companyId, lang, tz, grouping, realtime, offline, children, }: {
35
+ export declare function KoshProvider({ api, registry, uid, companyId, lang, tz, grouping, accent, realtime, offline, switchCompany, children, }: {
30
36
  readonly api: KoshApi;
31
37
  readonly registry: Registry;
32
38
  readonly uid: number;
@@ -35,8 +41,11 @@ export declare function KoshProvider({ api, registry, uid, companyId, lang, tz,
35
41
  readonly tz: string;
36
42
  /** The company's number grouping (RFC 0016). */
37
43
  readonly grouping?: 'international' | 'lakh';
44
+ /** The company's brand colour (`#rrggbb`, RFC 0018). */
45
+ readonly accent?: string | null;
38
46
  readonly realtime?: RealtimeClient;
39
47
  readonly offline?: OfflineQueue;
48
+ readonly switchCompany?: (company: number) => void;
40
49
  readonly children: ReactNode;
41
50
  }): ReactElement;
42
51
  /** Theme switching without a signed-in user (the sign-in page). */
@@ -4,6 +4,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
4
4
  * formatter for the user's language and time zone, and the theme.
5
5
  */
6
6
  import { createContext, useContext, useEffect, useMemo, useState, } from 'react';
7
+ import { accentVariables } from './lib/accent.js';
7
8
  import { createFormatter } from './lib/format.js';
8
9
  const Context = createContext(null);
9
10
  /** The renderer context. @throws outside {@link KoshProvider} */
@@ -14,6 +15,45 @@ export function useKosh() {
14
15
  return value;
15
16
  }
16
17
  const THEME_KEY = 'kosh.theme';
18
+ const DENSITY_KEY = 'kosh.density';
19
+ function readDensity() {
20
+ try {
21
+ return JSON.parse(localStorage.getItem(DENSITY_KEY) ?? 'null') === 'compact'
22
+ ? 'compact'
23
+ : 'comfortable';
24
+ }
25
+ catch {
26
+ return 'comfortable';
27
+ }
28
+ }
29
+ /** Applies the density to `<html>` (`data-density`), remembered per browser. */
30
+ function useDensity() {
31
+ const [density, setDensity] = useState(readDensity);
32
+ useEffect(() => {
33
+ document.documentElement.dataset['density'] = density;
34
+ try {
35
+ localStorage.setItem(DENSITY_KEY, JSON.stringify(density));
36
+ }
37
+ catch {
38
+ // storage unavailable: the choice lasts for this page only
39
+ }
40
+ }, [density]);
41
+ return [density, setDensity];
42
+ }
43
+ /** Sets the company's brand colour as the accent (RFC 0018), or the default when it has none. */
44
+ function useAccent(accent) {
45
+ useEffect(() => {
46
+ const root = document.documentElement.style;
47
+ const variables = accentVariables(accent);
48
+ for (const key of ['--accent-h', '--accent-c']) {
49
+ const value = variables[key];
50
+ if (value === undefined)
51
+ root.removeProperty(key);
52
+ else
53
+ root.setProperty(key, value);
54
+ }
55
+ }, [accent]);
56
+ }
17
57
  function readTheme() {
18
58
  try {
19
59
  const stored = localStorage.getItem(THEME_KEY);
@@ -47,8 +87,10 @@ function useTheme() {
47
87
  }, [theme]);
48
88
  return [theme, setTheme];
49
89
  }
50
- export function KoshProvider({ api, registry, uid, companyId, lang, tz, grouping, realtime, offline, children, }) {
90
+ export function KoshProvider({ api, registry, uid, companyId, lang, tz, grouping, accent, realtime, offline, switchCompany, children, }) {
51
91
  const [theme, setTheme] = useTheme();
92
+ const [density, setDensity] = useDensity();
93
+ useAccent(accent);
52
94
  const format = useMemo(() => createFormatter(lang, tz, grouping === undefined ? {} : { grouping }), [lang, tz, grouping]);
53
95
  const value = useMemo(() => ({
54
96
  api,
@@ -58,9 +100,25 @@ export function KoshProvider({ api, registry, uid, companyId, lang, tz, grouping
58
100
  format,
59
101
  theme,
60
102
  setTheme,
103
+ density,
104
+ setDensity,
61
105
  ...(realtime === undefined ? {} : { realtime }),
62
106
  ...(offline === undefined ? {} : { offline }),
63
- }), [api, registry, uid, companyId, format, theme, setTheme, realtime, offline]);
107
+ ...(switchCompany === undefined ? {} : { switchCompany }),
108
+ }), [
109
+ api,
110
+ registry,
111
+ uid,
112
+ companyId,
113
+ format,
114
+ theme,
115
+ setTheme,
116
+ density,
117
+ setDensity,
118
+ realtime,
119
+ offline,
120
+ switchCompany,
121
+ ]);
64
122
  return _jsx(Context.Provider, { value: value, children: children });
65
123
  }
66
124
  /** Theme switching without a signed-in user (the sign-in page). */
@@ -12,6 +12,14 @@ export declare const en: {
12
12
  unknownPage: string;
13
13
  };
14
14
  auth: {
15
+ activateTitle: string;
16
+ activateIntro: string;
17
+ activateNoToken: string;
18
+ newPassword: string;
19
+ repeatPassword: string;
20
+ passwordRule: string;
21
+ mismatch: string;
22
+ activate: string;
15
23
  signIn: string;
16
24
  login: string;
17
25
  password: string;
@@ -21,11 +29,32 @@ export declare const en: {
21
29
  signOut: string;
22
30
  invalid: string;
23
31
  adminNeeds2fa: string;
32
+ setupTitle: string;
33
+ setupIntro: string;
34
+ setupStart: string;
35
+ setupStep1: string;
36
+ setupOpenApp: string;
37
+ setupStep2: string;
38
+ setupStep3: string;
39
+ setupTurnOn: string;
40
+ setupCodes: string;
41
+ setupContinue: string;
24
42
  };
25
43
  shell: {
26
44
  apps: string;
27
45
  menu: string;
28
46
  theme: string;
47
+ density: string;
48
+ home: string;
49
+ appNamed: string;
50
+ switchCompany: string;
51
+ sidebar: string;
52
+ collapseSidebar: string;
53
+ expandSidebar: string;
54
+ openMenu: string;
55
+ closeMenu: string;
56
+ menuHint: string;
57
+ location: string;
29
58
  light: string;
30
59
  dark: string;
31
60
  system: string;
@@ -62,6 +91,20 @@ export declare const en: {
62
91
  exportFailed: string;
63
92
  columns: string;
64
93
  density: string;
94
+ statusOf: string;
95
+ nextState: string;
96
+ recordActions: string;
97
+ summary: string;
98
+ details: string;
99
+ recordPanel: string;
100
+ showPanel: string;
101
+ hidePanel: string;
102
+ previousMonth: string;
103
+ nextMonth: string;
104
+ pickDate: string;
105
+ datePlaceholder: string;
106
+ today: string;
107
+ clear: string;
65
108
  proposed: string;
66
109
  design: {
67
110
  addField: string;
@@ -197,6 +240,8 @@ export declare const en: {
197
240
  close: string;
198
241
  };
199
242
  palette: {
243
+ switchCompany: string;
244
+ profile: string;
200
245
  button: string;
201
246
  title: string;
202
247
  description: string;
@@ -220,6 +265,7 @@ export declare const en: {
220
265
  shortcuts: {
221
266
  title: string;
222
267
  palette: string;
268
+ sidebar: string;
223
269
  search: string;
224
270
  new: string;
225
271
  views: string;