@deenruv/admin-dashboard 1.0.17-dev.24 → 1.0.17-dev.26

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.
@@ -33,6 +33,8 @@ export const DeenruvAdminPanel = ({ plugins, settings }) => {
33
33
  ...settings,
34
34
  ui: {
35
35
  ...settings.ui,
36
+ showChannelPicker: settings?.ui?.showChannelPicker ?? true,
37
+ showLanguagePicker: settings?.ui?.showLanguagePicker ?? true,
36
38
  defaultChannelCode: settings?.ui?.defaultChannelCode || DEFAULT_CHANNEL_CODE,
37
39
  defaultLanguageCode: settings?.ui?.defaultLanguageCode || LanguageCode.en,
38
40
  defaultTranslationLanguageCode: settings?.ui?.defaultTranslationLanguageCode || LanguageCode.en,
@@ -1,15 +1,16 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useLayoutEffect, useRef, useState } from 'react';
2
+ import { Fragment, useLayoutEffect, useRef, useState } from 'react';
3
3
  import { Routes } from '@deenruv/react-ui-devkit';
4
4
  import { Navigate } from 'react-router';
5
5
  export const CommittedRouterOwner = ({ createRouter, children, }) => {
6
- const [router, setRouter] = useState();
6
+ const [ownedRouter, setOwnedRouter] = useState();
7
7
  const activeRouter = useRef(undefined);
8
+ const routerGeneration = useRef(0);
8
9
  const disposedRouters = useRef(new WeakSet());
9
10
  useLayoutEffect(() => {
10
11
  const nextRouter = createRouter();
11
12
  activeRouter.current = nextRouter;
12
- setRouter(nextRouter);
13
+ setOwnedRouter({ router: nextRouter, generation: ++routerGeneration.current });
13
14
  return () => {
14
15
  if (activeRouter.current === nextRouter) {
15
16
  activeRouter.current = undefined;
@@ -22,7 +23,7 @@ export const CommittedRouterOwner = ({ createRouter, children, }) => {
22
23
  });
23
24
  };
24
25
  }, [createRouter]);
25
- return router && activeRouter.current === router ? children(router) : null;
26
+ return ownedRouter && activeRouter.current === ownedRouter.router ? (_jsx(Fragment, { children: children(ownedRouter.router) }, ownedRouter.generation)) : null;
26
27
  };
27
28
  export const createAdminRouterRoutes = ({ permittedRoutes, defaultRoute, administratorAccessState, rootElement, errorElement, }) => {
28
29
  const children = permittedRoutes.map(({ id, path, element }) => ({ id, path, element }));
@@ -3,7 +3,9 @@ export const switchSelectedChannel = (channel, actions) => {
3
3
  actions.selectChannel(channel);
4
4
  actions.setPermissionsForChannel(channel.id);
5
5
  };
6
- export const selectPreferredChannel = (channels, retainedChannel, configuredDefaultCode, builtInDefaultCode) => channels.find((channel) => channel.id === retainedChannel?.id) ??
7
- channels.find((channel) => channel.code === configuredDefaultCode) ??
8
- channels.find((channel) => channel.code === builtInDefaultCode) ??
9
- channels[0];
6
+ export const selectPreferredChannel = (channels, retainedChannel, configuredDefaultCode, builtInDefaultCode, forceConfiguredDefault = false) => forceConfiguredDefault
7
+ ? channels.find((channel) => channel.code === configuredDefaultCode)
8
+ : (channels.find((channel) => channel.id === retainedChannel?.id) ??
9
+ channels.find((channel) => channel.code === configuredDefaultCode) ??
10
+ channels.find((channel) => channel.code === builtInDefaultCode) ??
11
+ channels[0]);
@@ -3,7 +3,9 @@ import { Button, Popover, PopoverContent, PopoverTrigger, useTranslation, } from
3
3
  import { SlidersHorizontal } from 'lucide-react';
4
4
  import { ChannelSwitcher } from './ChannelSwitcher.js';
5
5
  import { LanguagesDropdown } from './LanguagesDropdown.js';
6
- export const TopbarOverflow = ({ components }) => {
6
+ export const TopbarOverflow = ({ components, showChannelPicker, showLanguagePicker }) => {
7
7
  const { t } = useTranslation('common');
8
- return (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx(Button, { variant: "outline", size: "icon", className: "size-9 shrink-0 lg:hidden", "aria-label": t('openTopbarControls'), title: t('openTopbarControls'), children: _jsx(SlidersHorizontal, { className: "size-4" }) }) }), _jsx(PopoverContent, { align: "end", className: "z-[2138] w-[min(20rem,calc(100vw-2rem))] p-3 lg:hidden", children: _jsxs("div", { className: "flex flex-col gap-3", children: [components.length > 0 && (_jsx("div", { className: "flex flex-wrap items-center gap-2 border-b pb-3", children: components.map(({ component: Component }, index) => (_jsx(Component, {}, index))) })), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(LanguagesDropdown, {}), _jsx(ChannelSwitcher, { className: "min-w-0 flex-1" })] })] }) })] }));
8
+ if (components.length === 0 && !showChannelPicker && !showLanguagePicker)
9
+ return null;
10
+ return (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx(Button, { variant: "outline", size: "icon", className: "size-9 shrink-0 lg:hidden", "aria-label": t('openTopbarControls'), title: t('openTopbarControls'), children: _jsx(SlidersHorizontal, { className: "size-4" }) }) }), _jsx(PopoverContent, { align: "end", className: "z-[2138] w-[min(20rem,calc(100vw-2rem))] p-3 lg:hidden", children: _jsxs("div", { className: "flex flex-col gap-3", children: [components.length > 0 && (_jsx("div", { className: `flex flex-wrap items-center gap-2 ${showChannelPicker || showLanguagePicker ? 'border-b pb-3' : ''}`, children: components.map(({ component: Component }, index) => (_jsx(Component, {}, index))) })), showChannelPicker || showLanguagePicker ? (_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [showLanguagePicker ? _jsx(LanguagesDropdown, {}) : null, showChannelPicker ? _jsx(ChannelSwitcher, { className: "min-w-0 flex-1" }) : null] })) : null] }) })] }));
9
11
  };
@@ -20,6 +20,8 @@ export const Menu = ({ children }) => {
20
20
  const { defaultRoute } = useAdminAccess();
21
21
  const userPermissions = useServer((state) => state.userPermissions);
22
22
  const { theme, setTheme } = useSettings();
23
+ const showChannelPicker = window.__DEENRUV_SETTINGS__.ui?.showChannelPicker !== false;
24
+ const showLanguagePicker = window.__DEENRUV_SETTINGS__.ui?.showLanguagePicker !== false;
23
25
  const { isCollapsed, isMobileOpen, manuallyOpenGroupIds, setIsCollapsed, setIsMobileOpen, setManuallyOpenGroupIds, toggleDesktop, } = useSidebarState();
24
26
  const defaultRoutePath = defaultRoute?.path || Routes.dashboard;
25
27
  const defaultRouteMenuKey = defaultRoute?.nav?.menuKey || defaultRoute?.search?.menuKey || 'dashboard';
@@ -39,5 +41,5 @@ export const Menu = ({ children }) => {
39
41
  return (_jsxs(React.Fragment, { children: [_jsx(BreadcrumbItem, { className: "min-w-0", children: _jsx(NavLink, { to: buildURL(path), viewTransition: true, className: "truncate", children: _jsx("span", { className: "text-sm font-semibold tracking-tight text-foreground", children: index === 0 ? t(`menu.${dashToCamelCase(crumb)}`) : crumb }) }) }), index !== crumbs.length - 1 && (_jsx(BreadcrumbSeparator, { children: _jsx(Slash, { className: "text-muted-foreground" }) }))] }, `${crumb}-${index}`));
40
42
  })) : (_jsx(BreadcrumbItem, { children: _jsx(NavLink, { to: defaultRoutePath, viewTransition: true, children: _jsx("span", { className: "text-lg font-semibold tracking-tight text-foreground", children: t(defaultRouteMenuKey === 'dashboard'
41
43
  ? 'dashboard'
42
- : `menu.${dashToCamelCase(defaultRouteMenuKey)}`) }) }) })) }) }) }), _jsxs("div", { className: "ml-auto flex min-w-0 items-center justify-end gap-1.5", children: [_jsx(TopbarOverflow, { components: allowedTopNavigationComponents || [] }), allowedTopNavigationComponents?.length ? (_jsx("div", { className: "hidden items-center gap-2 lg:flex", children: allowedTopNavigationComponents.map(({ component: Component }, index) => (_jsx(Component, {}, index))) })) : null, _jsxs("div", { className: "hidden items-center gap-1.5 lg:flex", children: [_jsx(LanguagesDropdown, {}), _jsx(ChannelSwitcher, { className: "min-w-44" })] }), _jsx(Button, { onClick: openGlobalSearch, variant: "outline", size: "icon", className: "relative size-9 shrink-0", "aria-label": t('openGlobalSearch'), title: t('openGlobalSearch'), children: _jsx(SearchIcon, { className: "size-4" }) }), _jsx(Notifications, {}), _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", size: "icon", className: "relative size-9 shrink-0", children: [theme === 'light' ? (_jsx(Sun, { className: "size-[1.2rem]" })) : theme === 'dark' ? (_jsx(Moon, { className: "size-[1.2rem]" })) : (_jsx(SunMoon, { className: "size-[1.2rem]" })), _jsx("span", { className: "sr-only", children: t('toggleTheme') })] }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsx(DropdownMenuItem, { onClick: () => setTheme('light'), children: t('themeLight') }), _jsx(DropdownMenuItem, { onClick: () => setTheme('dark'), children: t('themeDark') }), _jsx(DropdownMenuItem, { onClick: () => setTheme('system'), children: t('themeSystem') })] })] })] })] }), _jsx(ScrollArea, { className: "relative min-h-0 flex-1 overflow-y-hidden bg-[var(--sidebar-canvas)]", children: children })] })] }) }));
44
+ : `menu.${dashToCamelCase(defaultRouteMenuKey)}`) }) }) })) }) }) }), _jsxs("div", { className: "ml-auto flex min-w-0 items-center justify-end gap-1.5", children: [_jsx(TopbarOverflow, { components: allowedTopNavigationComponents || [], showChannelPicker: showChannelPicker, showLanguagePicker: showLanguagePicker }), allowedTopNavigationComponents?.length ? (_jsx("div", { className: "hidden items-center gap-2 lg:flex", children: allowedTopNavigationComponents.map(({ component: Component }, index) => (_jsx(Component, {}, index))) })) : null, showChannelPicker || showLanguagePicker ? (_jsxs("div", { className: "hidden items-center gap-1.5 lg:flex", children: [showLanguagePicker ? _jsx(LanguagesDropdown, {}) : null, showChannelPicker ? _jsx(ChannelSwitcher, { className: "min-w-44" }) : null] })) : null, _jsx(Button, { onClick: openGlobalSearch, variant: "outline", size: "icon", className: "relative size-9 shrink-0", "aria-label": t('openGlobalSearch'), title: t('openGlobalSearch'), children: _jsx(SearchIcon, { className: "size-4" }) }), _jsx(Notifications, {}), _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", size: "icon", className: "relative size-9 shrink-0", children: [theme === 'light' ? (_jsx(Sun, { className: "size-[1.2rem]" })) : theme === 'dark' ? (_jsx(Moon, { className: "size-[1.2rem]" })) : (_jsx(SunMoon, { className: "size-[1.2rem]" })), _jsx("span", { className: "sr-only", children: t('toggleTheme') })] }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsx(DropdownMenuItem, { onClick: () => setTheme('light'), children: t('themeLight') }), _jsx(DropdownMenuItem, { onClick: () => setTheme('dark'), children: t('themeDark') }), _jsx(DropdownMenuItem, { onClick: () => setTheme('system'), children: t('themeSystem') })] })] })] })] }), _jsx(ScrollArea, { className: "relative min-h-0 flex-1 overflow-y-hidden bg-[var(--sidebar-canvas)]", children: children })] })] }) }));
43
45
  };
@@ -156,6 +156,11 @@ export const Root = ({ allPaths }) => {
156
156
  return;
157
157
  hasInitializedAdministratorAccess.current = true;
158
158
  beginAdministratorAccessInitialization();
159
+ const uiSettings = window.__DEENRUV_SETTINGS__.ui;
160
+ if (uiSettings?.showLanguagePicker === false) {
161
+ setLanguage(uiSettings.defaultLanguageCode);
162
+ setTranslationLanguage(uiSettings.defaultTranslationLanguageCode);
163
+ }
159
164
  const init = async () => {
160
165
  const activeAdministratorResponse = await apiClient('query')({
161
166
  activeAdministrator: activeAdministratorSelector,
@@ -168,6 +173,15 @@ export const Root = ({ allPaths }) => {
168
173
  return;
169
174
  }
170
175
  else {
176
+ const channelPickerIsHidden = uiSettings?.showChannelPicker === false;
177
+ const configuredChannelCode = uiSettings?.defaultChannelCode;
178
+ const failFixedChannelInitialization = () => {
179
+ failAdministratorAccess();
180
+ setLoaded(true);
181
+ toast.error(t('setup.failedAdmin'), {
182
+ description: `Configured channel "${configuredChannelCode}" is not accessible to this administrator.`,
183
+ });
184
+ };
171
185
  let administratorChannelId = selectedChannel?.id;
172
186
  if ([Permission.ReadChannel].some((p) => roles.some((r) => r.permissions.includes(p)))) {
173
187
  const { channels: { items: allChannels = [] }, } = await apiClient('query')({
@@ -186,7 +200,11 @@ export const Root = ({ allPaths }) => {
186
200
  ],
187
201
  });
188
202
  setChannels(allChannels);
189
- const preferredChannel = selectPreferredChannel(allChannels, selectedChannel, window?.__DEENRUV_SETTINGS__?.ui?.defaultChannelCode, DEFAULT_CHANNEL_CODE);
203
+ const preferredChannel = selectPreferredChannel(allChannels, selectedChannel, configuredChannelCode, DEFAULT_CHANNEL_CODE, channelPickerIsHidden);
204
+ if (channelPickerIsHidden && !preferredChannel) {
205
+ failFixedChannelInitialization();
206
+ return;
207
+ }
190
208
  if (preferredChannel && preferredChannel.id !== channel?.id) {
191
209
  setSelectedChannel(preferredChannel);
192
210
  }
@@ -195,21 +213,23 @@ export const Root = ({ allPaths }) => {
195
213
  else {
196
214
  const possibleChannels = roles.flatMap((role) => role.channels);
197
215
  if (possibleChannels.length > 0) {
198
- setSelectedChannel(possibleChannels[0]);
199
- administratorChannelId = possibleChannels[0].id;
216
+ const preferredChannel = channelPickerIsHidden
217
+ ? selectPreferredChannel(possibleChannels, selectedChannel, configuredChannelCode, DEFAULT_CHANNEL_CODE, true)
218
+ : possibleChannels[0];
219
+ if (!preferredChannel) {
220
+ failFixedChannelInitialization();
221
+ return;
222
+ }
223
+ setSelectedChannel(preferredChannel);
224
+ administratorChannelId = preferredChannel.id;
225
+ }
226
+ else if (channelPickerIsHidden) {
227
+ failFixedChannelInitialization();
228
+ return;
200
229
  }
201
230
  }
202
231
  setAdministratorAccess(activeAdministratorResponse.activeAdministrator, administratorChannelId);
203
232
  }
204
- // WE NEED TO CHECK IF LOCALSTORAGE HAS LANGUAGE SET
205
- if (window?.__DEENRUV_SETTINGS__?.ui?.defaultLanguageCode) {
206
- // window?.__DEENRUV_SETTINGS__.i18n.changeLanguage(window?.__DEENRUV_SETTINGS__?.ui?.defaultLanguageCode);
207
- // setLanguage(window?.__DEENRUV_SETTINGS__?.ui?.defaultLanguageCode);
208
- }
209
- // WE NEED TO CHECK IF LOCALSTORAGE HAS LANGUAGE SET
210
- if (window?.__DEENRUV_SETTINGS__?.ui?.defaultTranslationLanguageCode) {
211
- // setTranslationLanguage(window?.__DEENRUV_SETTINGS__?.ui?.defaultTranslationLanguageCode);
212
- }
213
233
  try {
214
234
  const { globalSettings } = await apiClient('query')({
215
235
  globalSettings: { serverConfig: serverConfigSelector, availableLanguages: true },
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const ADMIN_DASHBOARD_VERSION = '1.0.17-dev.24';
1
+ export const ADMIN_DASHBOARD_VERSION = '1.0.17-dev.26';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deenruv/admin-dashboard",
3
- "version": "1.0.17-dev.24",
3
+ "version": "1.0.17-dev.26",
4
4
  "main": "dist/index.js",
5
5
  "style": "dist/index.css",
6
6
  "types": "dist/index.d.ts",
@@ -80,10 +80,10 @@
80
80
  "yup": "^1.7.1",
81
81
  "zod": "^4.4.3",
82
82
  "zustand": "^5.0.14",
83
- "@deenruv/admin-dashboard": "1.0.17-dev.24",
84
- "@deenruv/admin-types": "1.0.17-dev.24",
85
- "@deenruv/react-ui-devkit": "1.0.17-dev.24",
86
- "@deenruv/deenruv-examples-plugin": "1.0.17-dev.24"
83
+ "@deenruv/admin-dashboard": "1.0.17-dev.26",
84
+ "@deenruv/admin-types": "1.0.17-dev.26",
85
+ "@deenruv/deenruv-examples-plugin": "1.0.17-dev.26",
86
+ "@deenruv/react-ui-devkit": "1.0.17-dev.26"
87
87
  },
88
88
  "devDependencies": {
89
89
  "@tailwindcss/typography": "^0.5.20",