@deenruv/admin-dashboard 1.0.17-dev.7 → 1.0.17-dev.8

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.
@@ -1,14 +1,14 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { NavLink, useLocation } from 'react-router';
3
3
  import React, { useMemo } from 'react';
4
- import { cn, buttonVariants, Routes, usePluginStore, Tooltip, TooltipContent, TooltipTrigger, useServer, Accordion, AccordionItem, AccordionTrigger, AccordionContent, useNotifications, useTranslation, capitalizeFirstLetter, Skeleton, } from '@deenruv/react-ui-devkit';
5
- import { BarChart, Barcode, Store, ShoppingCart, Images, Folder, Globe2, Tag, UserCog, Users, UserRoundSearch, MapPin, Flag, Coins, Globe, Percent, CreditCard, Truck, Cog, UsersRound, Server, ScanBarcode, } from 'lucide-react';
6
- import { Permission } from '@deenruv/admin-types';
4
+ import { cn, buttonVariants, usePluginStore, Tooltip, TooltipContent, TooltipTrigger, useServer, Accordion, AccordionItem, AccordionTrigger, AccordionContent, useNotifications, useTranslation, capitalizeFirstLetter, Skeleton, } from '@deenruv/react-ui-devkit';
5
+ import { adminNavigationGroups, canAccessAdminItem, useAdminAccess } from "../../access/index.js";
7
6
  export function Navigation({ isCollapsed }) {
8
7
  const { t } = useTranslation('common');
9
8
  const { t: _pluginT } = useTranslation();
10
9
  const location = useLocation();
11
10
  const { navMenuData, viewMarkers } = usePluginStore();
11
+ const { routes } = useAdminAccess();
12
12
  const userPermissions = useServer((p) => p.userPermissions);
13
13
  const loaded = useServer((p) => p.loaded);
14
14
  const getNavigationNotification = useNotifications(({ getNavigationNotification }) => getNavigationNotification);
@@ -19,201 +19,32 @@ export function Navigation({ isCollapsed }) {
19
19
  return _pluginT(key, { ns });
20
20
  };
21
21
  const navigationGroups = useMemo(() => {
22
- const navData = [
23
- {
24
- label: t('menuGroups.shop'),
25
- id: 'shop-group',
26
- links: [
27
- { title: t('menu.dashboard'), href: Routes.dashboard, id: 'link-dashboard', icon: BarChart },
28
- {
29
- title: t('menu.orders'),
30
- href: Routes.orders.list,
31
- id: 'link-orders',
32
- icon: ShoppingCart,
33
- requiredPermissions: [Permission.ReadOrder],
34
- },
35
- {
36
- title: t('menu.customers'),
37
- href: Routes.customers.list,
38
- id: 'link-customers',
39
- icon: UserRoundSearch,
40
- requiredPermissions: [Permission.ReadCustomer],
41
- },
42
- {
43
- title: t('menu.customerGroups'),
44
- href: Routes.customerGroups.list,
45
- id: 'link-customerGroups',
46
- icon: UsersRound,
47
- requiredPermissions: [Permission.ReadCustomerGroup],
48
- },
49
- ],
50
- },
51
- {
52
- label: t('menuGroups.assortment'),
53
- id: 'assortment-group',
54
- links: [
55
- {
56
- title: t('menu.products'),
57
- href: Routes.products.list,
58
- id: 'link-products',
59
- icon: Barcode,
60
- requiredPermissions: [Permission.ReadProduct, Permission.ReadCatalog],
61
- },
62
- {
63
- title: t('menu.productVariants'),
64
- href: Routes.productVariants.list,
65
- id: 'link-product-variants',
66
- icon: ScanBarcode,
67
- requiredPermissions: [Permission.ReadProduct, Permission.ReadCatalog],
68
- },
69
- {
70
- title: t('menu.collections'),
71
- href: Routes.collections.list,
72
- id: 'link-collections',
73
- icon: Folder,
74
- requiredPermissions: [Permission.ReadCollection, Permission.ReadCatalog],
75
- },
76
- {
77
- title: t('menu.facets'),
78
- href: Routes.facets.list,
79
- id: 'link-facets',
80
- icon: Tag,
81
- requiredPermissions: [Permission.ReadFacet, Permission.ReadCatalog],
82
- },
83
- {
84
- title: t('menu.assets'),
85
- href: Routes.assets.list,
86
- id: 'link-assets',
87
- icon: Images,
88
- requiredPermissions: [Permission.ReadAsset, Permission.ReadCatalog],
89
- },
90
- ],
91
- },
92
- {
93
- label: t('menuGroups.users'),
94
- id: 'users-group',
95
- links: [
96
- {
97
- title: t('menu.admins'),
98
- href: Routes.admins.list,
99
- id: 'link-admins',
100
- icon: UserCog,
101
- requiredPermissions: [Permission.ReadAdministrator],
102
- },
103
- {
104
- title: t('menu.roles'),
105
- href: Routes.roles.list,
106
- id: 'link-roles',
107
- icon: Users,
108
- requiredPermissions: [Permission.ReadAdministrator],
109
- },
110
- {
111
- title: t('menu.sellers'),
112
- href: Routes.sellers.list,
113
- id: 'link-sellers',
114
- icon: Store,
115
- requiredPermissions: [Permission.ReadSeller],
116
- },
117
- ],
118
- },
119
- {
120
- label: t('menuGroups.promotions'),
121
- id: 'promotions-group',
122
- links: [
123
- {
124
- title: t('menu.promotions'),
125
- href: Routes.promotions.list,
126
- id: 'link-promotions',
127
- icon: ShoppingCart,
128
- requiredPermissions: [Permission.ReadPromotion],
129
- },
130
- ],
131
- },
132
- {
133
- label: t('menuGroups.shipping'),
134
- id: 'shipping-group',
135
- links: [
136
- {
137
- title: t('menu.paymentMethods'),
138
- href: Routes.paymentMethods.list,
139
- id: 'link-payment-methods',
140
- icon: CreditCard,
141
- requiredPermissions: [Permission.ReadPaymentMethod],
142
- },
143
- {
144
- title: t('menu.shippingMethods'),
145
- href: Routes.shippingMethods.list,
146
- id: 'link-shipping-methods',
147
- icon: Truck,
148
- requiredPermissions: [Permission.ReadShippingMethod],
149
- },
150
- {
151
- title: t('menu.stock'),
152
- href: Routes.stockLocations.list,
153
- id: 'link-stock',
154
- icon: MapPin,
155
- requiredPermissions: [Permission.ReadStockLocation],
156
- },
157
- ],
158
- },
159
- {
160
- label: t('menuGroups.settings'),
161
- id: 'settings-group',
162
- links: [
163
- {
164
- title: t('menu.channels'),
165
- href: Routes.channels.list,
166
- id: 'link-channels',
167
- icon: Globe2,
168
- requiredPermissions: [Permission.ReadChannel],
169
- },
170
- {
171
- title: t('menu.zones'),
172
- href: Routes.zones.list,
173
- id: 'link-zones',
174
- icon: Globe,
175
- requiredPermissions: [Permission.ReadZone],
176
- },
177
- {
178
- title: t('menu.countries'),
179
- href: Routes.countries.list,
180
- id: 'link-countries',
181
- icon: Flag,
182
- requiredPermissions: [Permission.ReadCountry],
183
- },
184
- {
185
- title: t('menu.taxCategories'),
186
- href: Routes.taxCategories.list,
187
- id: 'link-tax-categories',
188
- icon: Coins,
189
- requiredPermissions: [Permission.ReadTaxCategory],
190
- },
191
- {
192
- title: t('menu.taxRates'),
193
- href: Routes.taxRates.list,
194
- id: 'link-tax-rates',
195
- icon: Percent,
196
- requiredPermissions: [Permission.ReadTaxRate],
197
- },
198
- {
199
- title: t('menu.globalSettings'),
200
- href: Routes.globalSettings,
201
- id: 'link-global-settings',
202
- icon: Cog,
203
- requiredPermissions: [Permission.ReadSettings],
204
- },
205
- {
206
- title: t('menu.systemStatus'),
207
- href: Routes.status,
208
- id: 'link-system-status',
209
- icon: Server,
210
- requiredPermissions: [Permission.ReadSystem],
211
- },
212
- ],
213
- },
214
- ];
22
+ const navData = adminNavigationGroups.map((group) => ({
23
+ label: t(`menuGroups.${group.labelKey}`),
24
+ id: group.id,
25
+ links: [],
26
+ }));
27
+ routes.forEach((route) => {
28
+ if (!route.nav)
29
+ return;
30
+ if (!canAccessAdminItem({ item: route, userPermissions }))
31
+ return;
32
+ const foundGroupIdx = navData.findIndex((group) => group.id === route.nav?.groupId);
33
+ if (foundGroupIdx === -1)
34
+ return;
35
+ navData[foundGroupIdx].links.push({
36
+ title: t(`menu.${route.nav.menuKey}`),
37
+ href: route.path,
38
+ id: route.nav.linkId,
39
+ icon: route.nav.icon,
40
+ access: route,
41
+ routeId: route.id,
42
+ });
43
+ });
215
44
  const { groups, links } = navMenuData;
216
- groups.forEach(({ id, labelId, placement }) => {
45
+ groups.forEach(({ id, labelId, placement, access }) => {
46
+ if (!canAccessAdminItem({ item: access, userPermissions }))
47
+ return;
217
48
  let foundGroupIdx = -1;
218
49
  const newGroup = { id, label: pluginT(labelId), links: [] };
219
50
  if (placement?.groupId) {
@@ -226,11 +57,13 @@ export function Navigation({ isCollapsed }) {
226
57
  navData.splice(foundGroupIdx + 1, 0, newGroup);
227
58
  }
228
59
  });
229
- links.forEach(({ groupId, href, labelId, id, icon, placement }) => {
60
+ links.forEach(({ groupId, href, labelId, id, icon, placement, access }) => {
61
+ if (!canAccessAdminItem({ item: access, userPermissions }))
62
+ return;
230
63
  const foundGroupIdx = navData.findIndex((group) => group.id === groupId);
231
64
  if (foundGroupIdx == -1)
232
- throw new Error(`Navbar menu group with id ${groupId} was not found.\nPlugin navigation href: ${href}`);
233
- const newElement = { title: pluginT(labelId), label: pluginT(labelId), href: `/${href}`, id, icon };
65
+ return;
66
+ const newElement = { title: pluginT(labelId), label: pluginT(labelId), href: `/${href}`, id, icon, access };
234
67
  if (!placement) {
235
68
  navData[foundGroupIdx].links.push(newElement);
236
69
  return;
@@ -239,18 +72,9 @@ export function Navigation({ isCollapsed }) {
239
72
  const offset = placement.where === 'above' ? 0 : 1;
240
73
  navData[foundGroupIdx].links.splice(foundIndex + offset, 0, newElement);
241
74
  });
242
- return navData;
243
- }, [navMenuData.groups, navMenuData.links, t]);
244
- const permittedNavigationGroups = useMemo(() => {
245
- return navigationGroups
246
- .map((group) => ({
247
- ...group,
248
- links: group.links.filter((link) => 'requiredPermissions' in link
249
- ? link.requiredPermissions?.some((permission) => userPermissions.includes(permission))
250
- : true),
251
- }))
252
- .filter((group) => group.links.length > 0);
253
- }, [userPermissions, navigationGroups]);
75
+ return navData.filter((group) => group.links.length > 0);
76
+ }, [navMenuData.groups, navMenuData.links, pluginT, routes, t, userPermissions]);
77
+ const permittedNavigationGroups = navigationGroups;
254
78
  // const defaultAccordionOpenValue = useMemo(
255
79
  // () =>
256
80
  // permittedNavigationGroups
@@ -1,12 +1,15 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { Permission } from '@deenruv/admin-types';
3
2
  import { capitalizeFirstLetter, cn, Routes, Separator, useServer, useTranslation } from '@deenruv/react-ui-devkit';
4
3
  import { Puzzle } from 'lucide-react';
5
4
  import { NavLink } from 'react-router';
5
+ import { canAccessAdminItem, useAdminAccess } from "../../access/index.js";
6
6
  export const NavigationFooter = ({ isCollapsed }) => {
7
7
  const { t } = useTranslation('common');
8
8
  const userPermissions = useServer((p) => p.userPermissions);
9
- const isPermittedToExtensions = userPermissions.includes(Permission.ReadSettings);
10
- return (_jsxs("div", { className: "flex w-full flex-col gap-2 border-t bg-card/80 py-2 text-xs text-muted-foreground select-none", children: [isPermittedToExtensions && !isCollapsed && (_jsxs(_Fragment, { children: [_jsx("div", { children: _jsx(NavLink, { to: Routes.extensions, viewTransition: true, children: _jsxs("div", { className: cn('relative mx-2 flex h-8 items-center justify-center px-3 text-sm font-medium capitalize transition-colors hover:bg-muted/70 hover:text-foreground', location.pathname === Routes.extensions &&
9
+ const { routes } = useAdminAccess();
10
+ const extensionsRoute = routes.find((route) => route.id === 'extensions');
11
+ const isPermittedToExtensions = !!extensionsRoute && canAccessAdminItem({ item: extensionsRoute, userPermissions });
12
+ const extensionsPath = extensionsRoute?.path || Routes.extensions;
13
+ return (_jsxs("div", { className: "flex w-full flex-col gap-2 border-t bg-card/80 py-2 text-xs text-muted-foreground select-none", children: [isPermittedToExtensions && !isCollapsed && (_jsxs(_Fragment, { children: [_jsx("div", { children: _jsx(NavLink, { to: extensionsPath, viewTransition: true, children: _jsxs("div", { className: cn('relative mx-2 flex h-8 items-center justify-center px-3 text-sm font-medium capitalize transition-colors hover:bg-muted/70 hover:text-foreground', location.pathname === extensionsPath &&
11
14
  'bg-primary/10 text-primary opacity-100 hover:bg-primary/10 hover:text-primary'), children: [_jsx(Puzzle, { className: "mr-2 size-4" }), capitalizeFirstLetter(t('menu.extensions'))] }) }) }), _jsx(Separator, {})] })), _jsxs("div", { className: "flex items-center justify-center gap-1 px-2", children: [!isCollapsed && _jsx("p", { className: "uppercase", children: "Deenruv" }), _jsxs("span", { children: [!isCollapsed ? 'ver. ' : 'v. ', window.__DEENRUV_SETTINGS__.appVersion] })] })] }));
12
15
  };
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import React, { useMemo, useState } from 'react';
3
3
  import { Breadcrumb, BreadcrumbItem, BreadcrumbList, BreadcrumbSeparator, Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, ScrollArea, TooltipProvider, Routes, useSettings, useServer, usePluginStore, cn, dashToCamelCase, apiClient, useGlobalSearch, useTranslation, DropdownMenuGroup, DropdownMenuSub, DropdownMenuSubTrigger, DropdownMenuPortal, DropdownMenuSubContent, createDialog, buildURL, } from '@deenruv/react-ui-devkit';
4
+ import { Permission } from '@deenruv/admin-types';
4
5
  import { useShallow } from 'zustand/react/shallow';
5
6
  import { GripVertical, LogOutIcon, MenuIcon, Moon, Slash, Sun, SunMoon, RotateCwSquare, SearchIcon, } from 'lucide-react';
6
7
  import * as ResizablePrimitive from 'react-resizable-panels';
@@ -11,6 +12,7 @@ import { BrandLogo } from "../BrandLogo.js";
11
12
  import { LanguagesDropdown } from './LanguagesDropdown.js';
12
13
  import { Notifications } from './Notifications.js';
13
14
  import { NavigationFooter } from "./NavigationFooter.js";
15
+ import { canAccessAdminItem, useAdminAccess } from "../../access/index.js";
14
16
  const ResizablePanelGroup = ({ className, ...props }) => (_jsx(ResizablePrimitive.Group, { className: cn('flex h-full w-full data-[orientation=vertical]:flex-col', className), ...props }));
15
17
  const ResizablePanel = ResizablePrimitive.Panel;
16
18
  const ResizableHandle = ({ withHandle, className, ...props }) => (_jsx(ResizablePrimitive.Separator, { className: cn('relative flex w-px items-center justify-center bg-border/70 after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:outline-none data-[orientation=vertical]:h-px data-[orientation=vertical]:w-full data-[orientation=vertical]:after:left-0 data-[orientation=vertical]:after:h-1 data-[orientation=vertical]:after:w-full data-[orientation=vertical]:after:translate-x-0 data-[orientation=vertical]:after:-translate-y-1/2 [&[data-orientation=vertical]>div]:rotate-90', className), ...props, children: withHandle && (_jsx("div", { className: "z-10 flex h-4 w-3 items-center justify-center border bg-background text-muted-foreground", children: _jsx(GripVertical, { className: "size-2.5" }) })) }));
@@ -20,6 +22,7 @@ export const Menu = ({ children }) => {
20
22
  const linkPath = [];
21
23
  const { t } = useTranslation('common');
22
24
  const { topNavigationActionsMenu, topNavigationComponents } = usePluginStore();
25
+ const { routes, defaultRoute } = useAdminAccess();
23
26
  const { logOut, theme, setTheme } = useSettings(useShallow((p) => ({
24
27
  logOut: p.logOut,
25
28
  theme: p.theme,
@@ -29,7 +32,36 @@ export const Menu = ({ children }) => {
29
32
  })));
30
33
  const navigate = useNavigate();
31
34
  const [isCollapsed, setIsCollapsed] = useState(false);
32
- const { activeAdministrator, setJobQueue } = useServer();
35
+ const { activeAdministrator, clearAdministratorAccess, setJobQueue, userPermissions } = useServer();
36
+ const defaultRoutePath = defaultRoute?.path || Routes.dashboard;
37
+ const defaultRouteMenuKey = defaultRoute?.nav?.menuKey || defaultRoute?.search?.menuKey || 'dashboard';
38
+ const globalSearchEnabled = true;
39
+ const languageSwitcherEnabled = true;
40
+ const channelSwitcherEnabled = true;
41
+ const notificationsEnabled = true;
42
+ const reindexEnabled = canAccessAdminItem({
43
+ item: { requiredPermissions: [Permission.UpdateCatalog, Permission.UpdateProduct] },
44
+ userPermissions,
45
+ });
46
+ const canAccessPluginSurface = (entry) => canAccessAdminItem({ item: entry.access, userPermissions });
47
+ const allowedTopNavigationComponents = topNavigationComponents?.filter(canAccessPluginSurface);
48
+ const allowedTopNavigationActions = topNavigationActionsMenu?.filter(canAccessPluginSurface);
49
+ const systemStatusRoute = routes.find((route) => route.id === 'system.status');
50
+ const globalSettingsRoute = routes.find((route) => route.id === 'settings.global');
51
+ const fastLinks = [
52
+ systemStatusRoute &&
53
+ canAccessAdminItem({ item: systemStatusRoute, userPermissions }) && {
54
+ key: 'systemStatus',
55
+ label: t('systemStatus'),
56
+ path: systemStatusRoute.path,
57
+ },
58
+ globalSettingsRoute &&
59
+ canAccessAdminItem({ item: globalSettingsRoute, userPermissions }) && {
60
+ key: 'globalSettings',
61
+ label: t('globalSettings'),
62
+ path: globalSettingsRoute.path,
63
+ },
64
+ ].filter(Boolean);
33
65
  const rebuildSearchIndex = async () => {
34
66
  await apiClient('mutation')({ reindex: { id: true, queueName: true, state: true } }).then(({ reindex: { queueName, state } }) => {
35
67
  setJobQueue(queueName, state === 'RUNNING');
@@ -48,10 +80,12 @@ export const Menu = ({ children }) => {
48
80
  const collapsed = size.asPercentage <= 4;
49
81
  setIsCollapsed(collapsed);
50
82
  document.cookie = `react-resizable-panels:collapsed=${JSON.stringify(collapsed)}`;
51
- }, className: cn(isCollapsed && 'min-w-[50px] transition-all duration-300 ease-in-out'), children: [_jsx("div", { className: cn('flex h-[72px] flex-col items-center justify-center gap-4 border-b bg-card/80'), children: _jsx("div", { className: `flex h-full items-center justify-center ${!isCollapsed && 'w-full'} cursor-pointer px-4 py-3`, onClick: () => navigate(Routes.dashboard, { viewTransition: true }), children: _jsx(BrandLogo, { isCollapsed: isCollapsed }) }) }), _jsxs("div", { className: "flex h-[calc(100vh-72px)] flex-col justify-between bg-card/70", children: [_jsx(Navigation, { isCollapsed: isCollapsed }), _jsx(NavigationFooter, { isCollapsed: isCollapsed })] })] }), _jsx(ResizableHandle, { withHandle: true }), _jsxs(ResizablePanel, { id: "content", children: [_jsxs("div", { className: "flex h-[64px] items-center border-b bg-background/95 px-3 backdrop-blur lg:h-[72px] lg:px-5", children: [_jsxs("div", { className: "flex flex-col items-start justify-center", children: [_jsx(Breadcrumb, { children: _jsx(BreadcrumbList, { children: crumbs.length ? (crumbs.map((c, i) => {
83
+ }, className: cn(isCollapsed && 'min-w-[50px] transition-all duration-300 ease-in-out'), children: [_jsx("div", { className: cn('flex h-[72px] flex-col items-center justify-center gap-4 border-b bg-card/80'), children: _jsx("div", { className: `flex h-full items-center justify-center ${!isCollapsed && 'w-full'} cursor-pointer px-4 py-3`, onClick: () => navigate(defaultRoutePath, { viewTransition: true }), children: _jsx(BrandLogo, { isCollapsed: isCollapsed }) }) }), _jsxs("div", { className: "flex h-[calc(100vh-72px)] flex-col justify-between bg-card/70", children: [_jsx(Navigation, { isCollapsed: isCollapsed }), _jsx(NavigationFooter, { isCollapsed: isCollapsed })] })] }), _jsx(ResizableHandle, { withHandle: true }), _jsxs(ResizablePanel, { id: "content", children: [_jsxs("div", { className: "flex h-[64px] items-center border-b bg-background/95 px-3 backdrop-blur lg:h-[72px] lg:px-5", children: [_jsxs("div", { className: "flex flex-col items-start justify-center", children: [_jsx(Breadcrumb, { children: _jsx(BreadcrumbList, { children: crumbs.length ? (crumbs.map((c, i) => {
52
84
  linkPath.push(c);
53
85
  return (_jsxs(React.Fragment, { children: [_jsx(BreadcrumbItem, { children: _jsx(NavLink, { to: buildURL(linkPath), viewTransition: true, children: _jsx("p", { className: cn('text-sm font-semibold tracking-tight text-foreground capitalize'), children: i === 0 ? t('menu.' + dashToCamelCase(c)) : c }) }) }), i !== crumbs.length - 1 && (_jsx(BreadcrumbSeparator, { children: _jsx(Slash, { className: "text-muted-foreground" }) }))] }, c));
54
- })) : (_jsx(BreadcrumbItem, { children: _jsx(NavLink, { to: Routes.dashboard, viewTransition: true, children: _jsx("p", { className: "text-xl font-semibold tracking-tight text-foreground", children: t('dashboard') }) }) })) }) }), _jsx("div", { className: "flex items-center gap-2" })] }), _jsxs("div", { className: "flex flex-1 items-center justify-end gap-1.5", children: [topNavigationComponents && topNavigationComponents.length > 0 ? (_jsx("div", { className: "flex items-center gap-2", children: topNavigationComponents.map(({ component: Component }, index) => (_jsx(Component, {}, index))) })) : null, _jsx(LanguagesDropdown, {}), _jsx(ChannelSwitcher, { className: "min-w-44" }), _jsx(Button, { onClick: openGlobalSearch, variant: "outline", size: "icon", className: "relative size-9", children: _jsx(SearchIcon, { className: "size-4" }) }), _jsx(Notifications, {}), _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", size: "icon", className: "size-9", children: [theme === 'light' ? (_jsx(Sun, { className: "size-[1.2rem] scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" })) : theme === 'dark' ? (_jsx(Moon, { className: "absolute size-[1.2rem] scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" })) : (_jsx(SunMoon, { className: "absolute size-[1.2rem] rotate-90 transition-all" })), _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') })] })] }), _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "outline", size: "icon", className: "size-9", children: _jsx(MenuIcon, { className: "size-4" }) }) }), _jsxs(DropdownMenuContent, { className: "z-[150] mr-6 min-w-40", children: [activeAdministrator?.emailAddress && (_jsxs(_Fragment, { children: [_jsxs(DropdownMenuLabel, { className: "flex items-center gap-2 px-3 py-2 font-medium", children: [_jsx("div", { className: "flex size-6 items-center justify-center bg-primary/10 text-xs font-semibold text-primary", children: activeAdministrator.firstName.charAt(0).toUpperCase() }), _jsxs("div", { className: "truncate text-sm", children: [activeAdministrator.firstName, " ", activeAdministrator.lastName] })] }), _jsx(DropdownMenuSeparator, { className: "my-1" })] })), _jsxs(DropdownMenuItem, { className: "flex cursor-pointer items-center gap-2 text-nowrap", onSelect: rebuildSearchIndex, children: [_jsx(RotateCwSquare, { className: "size-4" }), t('rebuildSerachIndex')] }), _jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuGroup, { children: _jsxs(DropdownMenuSub, { children: [_jsx(DropdownMenuSubTrigger, { children: t('fastLinks') }), _jsx(DropdownMenuPortal, { children: _jsxs(DropdownMenuSubContent, { children: [_jsxs(DropdownMenuItem, { className: "flex cursor-pointer items-center gap-2 text-nowrap", onSelect: () => navigate(Routes.status, { viewTransition: true }), children: [_jsx(RotateCwSquare, { className: "size-4" }), t('systemStatus')] }), _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { className: "flex cursor-pointer items-center gap-2 text-nowrap", onSelect: () => navigate(Routes.globalSettings, { viewTransition: true }), children: [_jsx(RotateCwSquare, { className: "size-4" }), t('globalSettings')] })] }) })] }) }), topNavigationActionsMenu?.length && topNavigationActionsMenu.length > 0 ? (_jsxs(_Fragment, { children: [_jsx(DropdownMenuSeparator, {}), topNavigationActionsMenu.map((action) => (_jsxs(DropdownMenuItem, { className: "flex cursor-pointer items-center gap-2", onSelect: action.onClick, children: [action.icon && _jsx(action.icon, { className: "size-4" }), action.label] }, action.label)))] })) : null, _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { className: "flex cursor-pointer items-center gap-2 text-red-500", onSelect: async () => {
86
+ })) : (_jsx(BreadcrumbItem, { children: _jsx(NavLink, { to: defaultRoutePath, viewTransition: true, children: _jsx("p", { className: "text-xl font-semibold tracking-tight text-foreground", children: t(defaultRouteMenuKey === 'dashboard'
87
+ ? 'dashboard'
88
+ : `menu.${dashToCamelCase(defaultRouteMenuKey)}`) }) }) })) }) }), _jsx("div", { className: "flex items-center gap-2" })] }), _jsxs("div", { className: "flex flex-1 items-center justify-end gap-1.5", children: [allowedTopNavigationComponents && allowedTopNavigationComponents.length > 0 ? (_jsx("div", { className: "flex items-center gap-2", children: allowedTopNavigationComponents.map(({ component: Component }, index) => (_jsx(Component, {}, index))) })) : null, languageSwitcherEnabled && _jsx(LanguagesDropdown, {}), channelSwitcherEnabled && _jsx(ChannelSwitcher, { className: "min-w-44" }), globalSearchEnabled && (_jsx(Button, { onClick: openGlobalSearch, variant: "outline", size: "icon", className: "relative size-9", children: _jsx(SearchIcon, { className: "size-4" }) })), notificationsEnabled && _jsx(Notifications, {}), _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", size: "icon", className: "size-9", children: [theme === 'light' ? (_jsx(Sun, { className: "size-[1.2rem] scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" })) : theme === 'dark' ? (_jsx(Moon, { className: "absolute size-[1.2rem] scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" })) : (_jsx(SunMoon, { className: "absolute size-[1.2rem] rotate-90 transition-all" })), _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') })] })] }), _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "outline", size: "icon", className: "size-9", children: _jsx(MenuIcon, { className: "size-4" }) }) }), _jsxs(DropdownMenuContent, { className: "z-[150] mr-6 min-w-40", children: [activeAdministrator?.emailAddress && (_jsxs(_Fragment, { children: [_jsxs(DropdownMenuLabel, { className: "flex items-center gap-2 px-3 py-2 font-medium", children: [_jsx("div", { className: "flex size-6 items-center justify-center bg-primary/10 text-xs font-semibold text-primary", children: activeAdministrator.firstName.charAt(0).toUpperCase() }), _jsxs("div", { className: "truncate text-sm", children: [activeAdministrator.firstName, " ", activeAdministrator.lastName] })] }), _jsx(DropdownMenuSeparator, { className: "my-1" })] })), reindexEnabled && (_jsxs(DropdownMenuItem, { className: "flex cursor-pointer items-center gap-2 text-nowrap", onSelect: rebuildSearchIndex, children: [_jsx(RotateCwSquare, { className: "size-4" }), t('rebuildSerachIndex')] })), reindexEnabled && fastLinks.length > 0 && _jsx(DropdownMenuSeparator, {}), fastLinks.length > 0 && (_jsx(DropdownMenuGroup, { children: _jsxs(DropdownMenuSub, { children: [_jsx(DropdownMenuSubTrigger, { children: t('fastLinks') }), _jsx(DropdownMenuPortal, { children: _jsx(DropdownMenuSubContent, { children: fastLinks.map((link, index) => (_jsxs(React.Fragment, { children: [index > 0 && _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { className: "flex cursor-pointer items-center gap-2 text-nowrap", onSelect: () => navigate(link.path, { viewTransition: true }), children: [_jsx(RotateCwSquare, { className: "size-4" }), link.label] })] }, link.key))) }) })] }) })), allowedTopNavigationActions?.length && allowedTopNavigationActions.length > 0 ? (_jsxs(_Fragment, { children: [_jsx(DropdownMenuSeparator, {}), allowedTopNavigationActions.map((action) => (_jsxs(DropdownMenuItem, { className: "flex cursor-pointer items-center gap-2", onSelect: action.onClick, children: [action.icon && _jsx(action.icon, { className: "size-4" }), action.label] }, action.label)))] })) : null, _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { className: "flex cursor-pointer items-center gap-2 text-red-500", onSelect: async () => {
55
89
  const result = await createDialog({
56
90
  title: t('logOutConfirmation'),
57
91
  description: t('logOutConfirmationDescription'),
@@ -60,7 +94,9 @@ export const Menu = ({ children }) => {
60
94
  { label: t('logOut'), variant: 'destructive', returnValue: true },
61
95
  ],
62
96
  });
63
- if (result)
97
+ if (result) {
98
+ clearAdministratorAccess();
64
99
  logOut();
100
+ }
65
101
  }, children: [_jsx(LogOutIcon, { className: "size-4" }), t('logOut')] })] })] })] })] }), _jsx(ScrollArea, { className: "relative h-[calc(100vh-64px)] overflow-y-hidden lg:h-[calc(100vh-72px)]", children: children })] })] }) }) }) }) }));
66
102
  };
@@ -32,6 +32,68 @@
32
32
  "placeholder": "Adding roles to a user"
33
33
  }
34
34
  },
35
+ "provision": {
36
+ "action": "Create with role",
37
+ "title": "Create administrator with role",
38
+ "description": "Use a business preset to create a role and assign it to a new administrator in one flow.",
39
+ "submit": "Create administrator",
40
+ "submitting": "Creating...",
41
+ "admin": {
42
+ "title": "Administrator account",
43
+ "description": "Credentials and contact details for the new administrator."
44
+ },
45
+ "role": {
46
+ "title": "Role setup",
47
+ "description": "Create a new role from the selected preset or reuse an existing role by code.",
48
+ "code": "Role code",
49
+ "descriptionLabel": "Role name",
50
+ "reuseExisting": "Use existing role with this code",
51
+ "reuseExistingHint": "When enabled, permissions and channels below are ignored and the administrator gets the existing role.",
52
+ "channels": "Channels",
53
+ "channelsPlaceholder": "Add channel"
54
+ },
55
+ "permissions": {
56
+ "title": "Permissions",
57
+ "description": "Review the preset and tweak permissions before creating the role.",
58
+ "reuseExisting": "Permissions are taken from the existing role when reuse mode is enabled."
59
+ },
60
+ "presets": {
61
+ "title": "Business preset",
62
+ "description": "Start from a safe operational role and adjust it if needed.",
63
+ "defaultBadge": "Default",
64
+ "backendNote": "The backend still enforces every permission. This wizard only provisions roles and administrator accounts.",
65
+ "shopManager": {
66
+ "title": "Shop manager",
67
+ "description": "Order handling, customer support, catalog visibility, payment/shipping lookup and stock-location visibility.",
68
+ "roleDescription": "Shop manager"
69
+ }
70
+ },
71
+ "summary": {
72
+ "title": "Summary",
73
+ "description": "What will be submitted.",
74
+ "role": "Role",
75
+ "channels": "Channels",
76
+ "permissions": "Permissions",
77
+ "mode": "Mode",
78
+ "create": "Create role",
79
+ "reuse": "Reuse role"
80
+ },
81
+ "validation": {
82
+ "emailRequired": "Email is required",
83
+ "emailInvalid": "Enter a valid email address",
84
+ "roleCodeRequired": "Role code is required",
85
+ "channelsRequired": "Select at least one channel",
86
+ "permissionsRequired": "Select at least one permission",
87
+ "roleCodeExists": "A role with this code already exists. Enable reuse or choose another code.",
88
+ "roleNotFound": "No existing role with this code was found."
89
+ },
90
+ "toasts": {
91
+ "success": "Administrator and role were created successfully",
92
+ "error": "Could not create administrator with role",
93
+ "roleCodeExists": "A role with this code already exists",
94
+ "roleNotFound": "No existing role with this code was found"
95
+ }
96
+ },
35
97
  "toasts": {
36
98
  "adminLoadingError": "Failed to load administrator with ID {{value}}",
37
99
  "adminUpdateSuccess": "Administrator successfully updated",
@@ -582,6 +582,7 @@
582
582
  "globalSettings": "global settings",
583
583
  "roles": "roles",
584
584
  "admins": "administrators",
585
+ "adminProvision": "administrator provisioning",
585
586
  "stock": "storage locations",
586
587
  "sellers": "retailers",
587
588
  "assets": "assets",
@@ -32,6 +32,68 @@
32
32
  "placeholder": "Dodawanie ról do użytkownika"
33
33
  }
34
34
  },
35
+ "provision": {
36
+ "action": "Utwórz z rolą",
37
+ "title": "Utwórz administratora z rolą",
38
+ "description": "Użyj biznesowego presetu, żeby w jednym przepływie utworzyć rolę i przypisać ją do nowego administratora.",
39
+ "submit": "Utwórz administratora",
40
+ "submitting": "Tworzenie...",
41
+ "admin": {
42
+ "title": "Konto administratora",
43
+ "description": "Dane logowania i kontaktowe nowego administratora."
44
+ },
45
+ "role": {
46
+ "title": "Konfiguracja roli",
47
+ "description": "Utwórz nową rolę z wybranego presetu albo użyj istniejącej roli po kodzie.",
48
+ "code": "Kod roli",
49
+ "descriptionLabel": "Nazwa roli",
50
+ "reuseExisting": "Użyj istniejącej roli o tym kodzie",
51
+ "reuseExistingHint": "Po włączeniu tej opcji uprawnienia i kanały poniżej są ignorowane, a administrator otrzyma istniejącą rolę.",
52
+ "channels": "Kanały",
53
+ "channelsPlaceholder": "Dodaj kanał"
54
+ },
55
+ "permissions": {
56
+ "title": "Uprawnienia",
57
+ "description": "Przejrzyj preset i dostosuj uprawnienia przed utworzeniem roli.",
58
+ "reuseExisting": "Uprawnienia są pobierane z istniejącej roli, gdy włączone jest użycie istniejącej roli."
59
+ },
60
+ "presets": {
61
+ "title": "Preset biznesowy",
62
+ "description": "Zacznij od bezpiecznej roli operacyjnej i dostosuj ją, jeśli trzeba.",
63
+ "defaultBadge": "Domyślny",
64
+ "backendNote": "Backend nadal egzekwuje każde uprawnienie. Ten wizard tylko zakłada role i konta administratorów.",
65
+ "shopManager": {
66
+ "title": "Shop manager",
67
+ "description": "Obsługa zamówień, wsparcie klienta, widoczność katalogu, podgląd płatności/wysyłki i lokalizacji magazynowych.",
68
+ "roleDescription": "Shop manager"
69
+ }
70
+ },
71
+ "summary": {
72
+ "title": "Podsumowanie",
73
+ "description": "To zostanie wysłane.",
74
+ "role": "Rola",
75
+ "channels": "Kanały",
76
+ "permissions": "Uprawnienia",
77
+ "mode": "Tryb",
78
+ "create": "Utwórz rolę",
79
+ "reuse": "Użyj roli"
80
+ },
81
+ "validation": {
82
+ "emailRequired": "Adres e-mail jest wymagany",
83
+ "emailInvalid": "Podaj poprawny adres e-mail",
84
+ "roleCodeRequired": "Kod roli jest wymagany",
85
+ "channelsRequired": "Wybierz co najmniej jeden kanał",
86
+ "permissionsRequired": "Wybierz co najmniej jedno uprawnienie",
87
+ "roleCodeExists": "Rola o tym kodzie już istnieje. Włącz użycie istniejącej roli albo wybierz inny kod.",
88
+ "roleNotFound": "Nie znaleziono istniejącej roli o tym kodzie."
89
+ },
90
+ "toasts": {
91
+ "success": "Administrator i rola zostały utworzone pomyślnie",
92
+ "error": "Nie udało się utworzyć administratora z rolą",
93
+ "roleCodeExists": "Rola o tym kodzie już istnieje",
94
+ "roleNotFound": "Nie znaleziono istniejącej roli o tym kodzie"
95
+ }
96
+ },
35
97
  "toasts": {
36
98
  "adminLoadingError": "Nie udało się załadować administratora o identyfikatorze {{value}}",
37
99
  "adminUpdateSuccess": "Administrator został pomyślnie zaktualizowany",
@@ -581,6 +581,7 @@
581
581
  "globalSettings": "ustawienia globalne",
582
582
  "roles": "role",
583
583
  "admins": "administratorzy",
584
+ "adminProvision": "zakładanie administratora",
584
585
  "stock": "lokalizacje magazynowe",
585
586
  "sellers": "sprzedawcy",
586
587
  "assets": "aktywa",