@deenruv/admin-dashboard 1.0.16 → 1.0.17-dev.14

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 (74) hide show
  1. package/dist/DeenruvAdminPanel.js +35 -46
  2. package/dist/access/access-context.js +10 -0
  3. package/dist/access/built-in-routes.js +300 -0
  4. package/dist/access/index.js +6 -0
  5. package/dist/access/navigation.js +33 -0
  6. package/dist/access/permission-access.js +9 -0
  7. package/dist/access/permission-routes.js +3 -0
  8. package/dist/access/types.js +1 -0
  9. package/dist/components/GlobalSearch.js +37 -62
  10. package/dist/components/Menu/ActiveAdmins.js +1 -1
  11. package/dist/components/Menu/ChannelSwitcher.js +1 -1
  12. package/dist/components/Menu/LanguagesDropdown.js +1 -1
  13. package/dist/components/Menu/Navigation.js +68 -229
  14. package/dist/components/Menu/NavigationFooter.js +7 -4
  15. package/dist/components/Menu/Notifications.js +1 -1
  16. package/dist/components/Menu/index.js +50 -16
  17. package/dist/index.css +54 -44
  18. package/dist/locales/en/admins.json +62 -0
  19. package/dist/locales/en/collections.json +7 -0
  20. package/dist/locales/en/common.json +2 -0
  21. package/dist/locales/en/orders.json +8 -0
  22. package/dist/locales/en/products.json +14 -2
  23. package/dist/locales/en/promotions.json +4 -0
  24. package/dist/locales/pl/admins.json +62 -0
  25. package/dist/locales/pl/collections.json +7 -0
  26. package/dist/locales/pl/common.json +2 -0
  27. package/dist/locales/pl/orders.json +8 -0
  28. package/dist/locales/pl/products.json +14 -2
  29. package/dist/locales/pl/promotions.json +4 -0
  30. package/dist/pages/Root.js +31 -15
  31. package/dist/pages/admins/Detail.js +1 -1
  32. package/dist/pages/admins/List.js +8 -3
  33. package/dist/pages/admins/Provision.js +173 -0
  34. package/dist/pages/admins/Provision.logic.js +53 -0
  35. package/dist/pages/admins/index.js +1 -0
  36. package/dist/pages/assets/List.js +3 -3
  37. package/dist/pages/channels/Detail.js +1 -1
  38. package/dist/pages/collections/Detail.js +21 -6
  39. package/dist/pages/collections/List.js +87 -20
  40. package/dist/pages/collections/_components/CollectionDetailView.js +15 -2
  41. package/dist/pages/collections/_components/FiltersCard.js +51 -11
  42. package/dist/pages/countries/Detail.js +1 -1
  43. package/dist/pages/customers/_components/PersonalDataCard.js +2 -1
  44. package/dist/pages/extensions/Extensions.js +3 -7
  45. package/dist/pages/facets/Detail.js +1 -1
  46. package/dist/pages/orders/Detail.js +38 -5
  47. package/dist/pages/orders/List.js +1 -16
  48. package/dist/pages/orders/_components/CancelAndRefundDialog.js +1 -2
  49. package/dist/pages/orders/_components/OrderLineCustomFields.js +1 -1
  50. package/dist/pages/orders/_components/ProductsTable.js +1 -2
  51. package/dist/pages/orders/_components/RefundPaymentCard.js +1 -2
  52. package/dist/pages/payment-methods/Detail.js +1 -1
  53. package/dist/pages/product-variants/List.js +3 -3
  54. package/dist/pages/products/Detail.js +58 -4
  55. package/dist/pages/products/_components/AssetsCard.js +15 -2
  56. package/dist/pages/products/_components/OptionsCard.js +7 -5
  57. package/dist/pages/products/_components/PriceCard.js +21 -8
  58. package/dist/pages/products/_components/ProductDetailView.js +7 -3
  59. package/dist/pages/products/_components/Variant.js +53 -26
  60. package/dist/pages/products/_components/VariantsTab.js +1 -1
  61. package/dist/pages/promotions/Detail.js +34 -3
  62. package/dist/pages/promotions/_components/EnabledCard.js +2 -2
  63. package/dist/pages/promotions/_components/PromotionDetailSidebar.js +2 -1
  64. package/dist/pages/roles/Detail.js +1 -1
  65. package/dist/pages/roles/_components/PermissionsTable.js +0 -1
  66. package/dist/pages/sellers/Detail.js +1 -1
  67. package/dist/pages/shipping-methods/Detail.js +10 -6
  68. package/dist/pages/shipping-methods/_components/CalculatorCard.js +27 -11
  69. package/dist/pages/shipping-methods/_components/CheckerCard.js +28 -12
  70. package/dist/pages/stock-locations/Detail.js +1 -1
  71. package/dist/pages/tax-rates/Detail.js +1 -1
  72. package/dist/pages/zones/Detail.js +1 -1
  73. package/dist/version.js +1 -1
  74. package/package.json +68 -68
@@ -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,15 +12,17 @@ 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";
14
- const ResizablePanelGroup = ({ className, ...props }) => (_jsx(ResizablePrimitive.PanelGroup, { className: cn('flex h-full w-full data-[panel-group-direction=vertical]:flex-col', className), ...props }));
15
+ import { canAccessAdminItem, useAdminAccess } from "../../access/index.js";
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
- const ResizableHandle = ({ withHandle, className, ...props }) => (_jsx(ResizablePrimitive.PanelResizeHandle, { className: cn('relative flex w-px items-center justify-center bg-border 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-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:translate-x-0 data-[panel-group-direction=vertical]:after:-translate-y-1/2 [&[data-panel-group-direction=vertical]>div]:rotate-90', className), ...props, children: withHandle && (_jsx("div", { className: "z-10 flex h-4 w-3 items-center justify-center rounded-sm border bg-border", children: _jsx(GripVertical, { className: "size-2.5" }) })) }));
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" }) })) }));
17
19
  const removableCrumbs = ['draft', 'admin-ui'];
18
20
  export const Menu = ({ children }) => {
19
21
  const openGlobalSearch = useGlobalSearch((state) => state.open);
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');
@@ -42,18 +74,18 @@ export const Menu = ({ children }) => {
42
74
  .flatMap((p) => p.split('/'))
43
75
  .filter(Boolean)
44
76
  .filter((crumb) => !removableCrumbs.includes(crumb)), [matches]);
45
- return (_jsx("div", { className: "w-full border-r bg-muted/40", children: _jsx("div", { className: "flex h-full max-h-screen flex-col gap-2", children: _jsx("div", { className: "flex-1", children: _jsx(TooltipProvider, { delayDuration: 100, children: _jsxs(ResizablePanelGroup, { onLayout: (sizes) => {
46
- document.cookie = `react-resizable-panels:layout=${JSON.stringify(sizes)}`;
47
- }, direction: "horizontal", className: "size-full", children: [_jsxs(ResizablePanel, { defaultSize: 18, collapsedSize: 4, collapsible: true, minSize: 10, maxSize: 20, onExpand: () => {
48
- setIsCollapsed(false);
49
- document.cookie = `react-resizable-panels:collapsed=${JSON.stringify(false)}`;
50
- }, onCollapse: () => {
51
- setIsCollapsed(true);
52
- document.cookie = `react-resizable-panels:collapsed=${JSON.stringify(true)}`;
53
- }, className: cn(isCollapsed && 'min-w-[50px] transition-all duration-300 ease-in-out'), children: [_jsx("div", { className: cn('flex h-[80px] flex-col items-center justify-center gap-4 border-b'), children: _jsx("div", { className: `flex h-full items-center justify-center ${!isCollapsed && 'w-full'} cursor-pointer p-4`, onClick: () => navigate(Routes.dashboard, { viewTransition: true }), children: _jsx(BrandLogo, { isCollapsed: isCollapsed }) }) }), _jsxs("div", { className: "flex h-[calc(100vh-80px)] flex-col justify-between", children: [_jsx(Navigation, { isCollapsed: isCollapsed }), _jsx(NavigationFooter, { isCollapsed: isCollapsed })] })] }), _jsx(ResizableHandle, { withHandle: true }), _jsxs(ResizablePanel, { children: [_jsxs("div", { className: "flex h-[70px] items-center border-b p-4 lg:h-[80px] lg:px-6", children: [_jsxs("div", { className: "flex flex-col items-start justify-center", children: [_jsx(Breadcrumb, { children: _jsx(BreadcrumbList, { children: crumbs.length ? (crumbs.map((c, i) => {
77
+ return (_jsx("div", { className: "w-full bg-background", children: _jsx("div", { className: "flex h-full max-h-screen flex-col", children: _jsx("div", { className: "flex-1", children: _jsx(TooltipProvider, { delayDuration: 100, children: _jsxs(ResizablePanelGroup, { onLayoutChanged: (layout) => {
78
+ document.cookie = `react-resizable-panels:layout=${JSON.stringify(Object.values(layout))}`;
79
+ }, orientation: "horizontal", className: "size-full", children: [_jsxs(ResizablePanel, { id: "navigation", defaultSize: "16%", collapsedSize: "4%", collapsible: true, minSize: "11%", maxSize: "18%", onResize: (size) => {
80
+ const collapsed = size.asPercentage <= 4;
81
+ setIsCollapsed(collapsed);
82
+ document.cookie = `react-resizable-panels:collapsed=${JSON.stringify(collapsed)}`;
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) => {
54
84
  linkPath.push(c);
55
- return (_jsxs(React.Fragment, { children: [_jsx(BreadcrumbItem, { children: _jsx(NavLink, { to: buildURL(linkPath), viewTransition: true, children: _jsx("p", { className: cn('text-md font-bold 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));
56
- })) : (_jsx(BreadcrumbItem, { children: _jsx(NavLink, { to: Routes.dashboard, viewTransition: true, children: _jsx("p", { className: "text-2xl font-bold text-foreground", children: t('dashboard') }) }) })) }) }), _jsx("div", { className: "flex items-center gap-2" })] }), _jsxs("div", { className: "flex flex-1 items-center justify-end gap-2", 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-10", children: _jsx(SearchIcon, { className: "size-4" }) }), _jsx(Notifications, {}), _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", size: "icon", 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", 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-5 items-center justify-center rounded-full bg-gray-100 text-xs text-gray-600", 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 () => {
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));
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 () => {
57
89
  const result = await createDialog({
58
90
  title: t('logOutConfirmation'),
59
91
  description: t('logOutConfirmationDescription'),
@@ -62,7 +94,9 @@ export const Menu = ({ children }) => {
62
94
  { label: t('logOut'), variant: 'destructive', returnValue: true },
63
95
  ],
64
96
  });
65
- if (result)
97
+ if (result) {
98
+ clearAdministratorAccess();
66
99
  logOut();
67
- }, children: [_jsx(LogOutIcon, { className: "size-4" }), t('logOut')] })] })] })] })] }), _jsx(ScrollArea, { className: "relative h-[calc(100vh-70px)] overflow-y-hidden lg:h-[calc(100vh-80px)]", children: children })] })] }) }) }) }) }));
100
+ }
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 })] })] }) }) }) }) }));
68
102
  };
package/dist/index.css CHANGED
@@ -24,47 +24,47 @@
24
24
  @plugin "@tailwindcss/typography";
25
25
 
26
26
  :root {
27
- --background: hsl(0 0% 100%);
28
- --foreground: hsl(240 10% 3.9%);
27
+ --background: hsl(225 33% 97%);
28
+ --foreground: hsl(230 20% 7%);
29
29
 
30
30
  --card: hsl(0 0% 100%);
31
- --card-foreground: hsl(240 10% 3.9%);
31
+ --card-foreground: hsl(230 20% 7%);
32
32
 
33
33
  --popover: hsl(0 0% 100%);
34
- --popover-foreground: hsl(240 10% 3.9%);
34
+ --popover-foreground: hsl(230 20% 7%);
35
35
 
36
- --primary: hsl(240 5.9% 10%);
37
- --primary-foreground: hsl(0 0% 98%);
36
+ --primary: hsl(238 70% 58%);
37
+ --primary-foreground: hsl(0 0% 100%);
38
38
 
39
- --secondary: hsl(240 4.8% 95.9%);
40
- --secondary-foreground: hsl(240 5.9% 10%);
39
+ --secondary: hsl(225 28% 94%);
40
+ --secondary-foreground: hsl(230 18% 12%);
41
41
 
42
- --muted: hsl(240 4.8% 95.9%);
43
- --muted-foreground: hsl(240 3.8% 46.1%);
42
+ --muted: hsl(225 24% 95%);
43
+ --muted-foreground: hsl(228 10% 41%);
44
44
 
45
- --accent: hsl(240 4.8% 95.9%);
46
- --accent-foreground: hsl(240 5.9% 10%);
45
+ --accent: hsl(238 80% 96%);
46
+ --accent-foreground: hsl(238 70% 42%);
47
47
 
48
48
  --destructive: hsl(0 84.2% 60.2%);
49
49
  --destructive-foreground: hsl(0 0% 98%);
50
50
 
51
- --border: hsl(240 5.9% 90%);
52
- --input: hsl(240 5.9% 90%);
53
- --ring: hsl(240 10% 3.9%);
51
+ --border: hsl(225 18% 88%);
52
+ --input: hsl(225 18% 86%);
53
+ --ring: hsl(238 70% 58%);
54
54
 
55
- --radius: 0.5rem;
55
+ --radius: 0rem;
56
56
 
57
57
  --warning: hsl(38 92% 50%);
58
58
  --warning-foreground: hsl(48 96% 89%);
59
59
 
60
- --navigation-link: hsl(240 10% 3.9%);
60
+ --navigation-link: hsl(228 14% 28%);
61
61
 
62
- --topbar-height: 70px;
63
- --topbar-height-lg: 80px;
62
+ --topbar-height: 64px;
63
+ --topbar-height-lg: 72px;
64
64
 
65
- --page-padding-total-y: 2.75rem;
66
- --page-padding-total-y-md: 4.75rem;
67
- --page-padding-total-y-lg: 2rem;
65
+ --page-padding-total-y: 2rem;
66
+ --page-padding-total-y-md: 3.25rem;
67
+ --page-padding-total-y-lg: 1.5rem;
68
68
 
69
69
  --page-content-h: calc(100vh - var(--topbar-height) - var(--page-padding-total-y));
70
70
  --page-content-h-md: calc(100vh - var(--topbar-height) - var(--page-padding-total-y-md));
@@ -72,38 +72,38 @@
72
72
  }
73
73
 
74
74
  .dark {
75
- --background: hsl(240 10% 3.9%);
76
- --foreground: hsl(0 0% 98%);
75
+ --background: hsl(230 22% 5%);
76
+ --foreground: hsl(220 22% 96%);
77
77
 
78
- --card: hsl(240 10% 3.9%);
79
- --card-foreground: hsl(0 0% 98%);
78
+ --card: hsl(228 18% 8%);
79
+ --card-foreground: hsl(220 22% 96%);
80
80
 
81
- --popover: hsl(240 10% 3.9%);
82
- --popover-foreground: hsl(0 0% 98%);
81
+ --popover: hsl(228 18% 9%);
82
+ --popover-foreground: hsl(220 22% 96%);
83
83
 
84
- --primary: hsl(0 0% 98%);
85
- --primary-foreground: hsl(240 5.9% 10%);
84
+ --primary: hsl(238 80% 66%);
85
+ --primary-foreground: hsl(0 0% 100%);
86
86
 
87
- --secondary: hsl(240 3.7% 15.9%);
88
- --secondary-foreground: hsl(0 0% 98%);
87
+ --secondary: hsl(228 16% 14%);
88
+ --secondary-foreground: hsl(220 22% 96%);
89
89
 
90
- --muted: hsl(240 3.7% 15.9%);
91
- --muted-foreground: hsl(240 5% 64.9%);
90
+ --muted: hsl(228 16% 14%);
91
+ --muted-foreground: hsl(225 10% 65%);
92
92
 
93
- --accent: hsl(240 3.7% 15.9%);
94
- --accent-foreground: hsl(0 0% 98%);
93
+ --accent: hsl(238 34% 18%);
94
+ --accent-foreground: hsl(238 100% 82%);
95
95
 
96
96
  --destructive: hsl(0 62.8% 30.6%);
97
97
  --destructive-foreground: hsl(0 0% 98%);
98
98
 
99
- --border: hsl(240 3.7% 15.9%);
100
- --input: hsl(240 3.7% 15.9%);
101
- --ring: hsl(240 4.9% 83.9%);
99
+ --border: hsl(228 14% 20%);
100
+ --input: hsl(228 14% 22%);
101
+ --ring: hsl(238 80% 66%);
102
102
 
103
103
  --warning: hsl(48 96% 89%);
104
104
  --warning-foreground: hsl(38 92% 50%);
105
105
 
106
- --navigation-link: hsl(0 0% 98%);
106
+ --navigation-link: hsl(220 18% 86%);
107
107
  }
108
108
 
109
109
  @theme inline {
@@ -140,9 +140,9 @@
140
140
 
141
141
  --color-navigation-link: var(--navigation-link);
142
142
 
143
- --radius-lg: var(--radius);
144
- --radius-md: calc(var(--radius) - 2px);
145
- --radius-sm: calc(var(--radius) - 4px);
143
+ --radius-lg: 0rem;
144
+ --radius-md: 0rem;
145
+ --radius-sm: 0rem;
146
146
 
147
147
  --animate-accordion-down: accordion-down 0.2s ease-out;
148
148
  --animate-accordion-up: accordion-up 0.2s ease-out;
@@ -176,6 +176,16 @@
176
176
  background-color: var(--background);
177
177
  color: var(--foreground);
178
178
  box-sizing: border-box;
179
+ font-family:
180
+ Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
181
+ font-feature-settings:
182
+ "cv02", "cv03", "cv04", "cv11";
183
+ text-rendering: geometricPrecision;
184
+ }
185
+
186
+ ::selection {
187
+ background-color: var(--accent);
188
+ color: var(--accent-foreground);
179
189
  }
180
190
  }
181
191
 
@@ -200,7 +210,7 @@
200
210
  }
201
211
 
202
212
  *::-webkit-scrollbar-thumb {
203
- background-color: var(--border);
213
+ background-color: color-mix(in srgb, var(--foreground) 18%, transparent);
204
214
  position: relative;
205
215
  border-radius: 9999px;
206
216
  }
@@ -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",
@@ -38,6 +38,7 @@
38
38
  "inherit": "Filter inheritance",
39
39
  "condition": "Condition",
40
40
  "addCondition": "Add condition",
41
+ "noAvailableConditions": "Filter conditions are still loading",
41
42
  "addFacets": "Adding aspect values",
42
43
  "labels": {
43
44
  "codes": {
@@ -55,6 +56,12 @@
55
56
  },
56
57
  "contents": {
57
58
  "title": "Content"
59
+ },
60
+ "channels": {
61
+ "title": "Additional channels",
62
+ "description": "Select additional channels to assign after creating the collection. The current channel is assigned automatically.",
63
+ "currentChannel": "Current channel",
64
+ "noChannels": "No channels available"
58
65
  }
59
66
  },
60
67
  "visibility": {
@@ -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",
@@ -962,6 +963,7 @@
962
963
  "tags": "Filter by tag",
963
964
  "perPageValue": "{{value}} per page",
964
965
  "confirmButton": "Add resource",
966
+ "uploadButton": "Upload from disk",
965
967
  "cancel": "Cancel",
966
968
  "noImages": "No resources for a specific search",
967
969
  "description": "Uploading and managing resources that can be used in content"
@@ -40,6 +40,14 @@
40
40
  "confirm": "Delete draft",
41
41
  "cancel": "Cancel"
42
42
  },
43
+ "createDraftOrder": {
44
+ "title": "Create a new draft order",
45
+ "description": "A draft order will only be created after you confirm. You can then add products, a customer, and shipping details.",
46
+ "startButton": "Start draft order",
47
+ "cancelButton": "Back to orders",
48
+ "creating": "Creating draft...",
49
+ "error": "Failed to create draft order"
50
+ },
43
51
  "searchProduct": {
44
52
  "id": "ID",
45
53
  "image": "Image",
@@ -42,6 +42,14 @@
42
42
  "removeVariant": "Usuń wariant",
43
43
  "removeVariantFromChannel": "Usuń wariant z kanału"
44
44
  },
45
+ "initialVariant": {
46
+ "title": "Initial variant",
47
+ "description": "Create one default variant together with this product. If the variant name is empty, the product name will be used.",
48
+ "sku": "SKU",
49
+ "price": "Price",
50
+ "name": "Variant name",
51
+ "namePlaceholder": "Use the product name"
52
+ },
45
53
  "optionsTab": {
46
54
  "placeholder": "Wpisz, aby dodać nową wartość opcji",
47
55
  "groupName": "Nazwa grupy",
@@ -53,6 +61,7 @@
53
61
  "noOptionValues": "Dodaj opcje powyżej, aby znaleźć je tutaj"
54
62
  },
55
63
  "variantsTab": {
64
+ "unnamedVariant": "Unnamed variant",
56
65
  "emptyState": {
57
66
  "title": "Nic tutaj nie ma",
58
67
  "description": "Nie ma jeszcze żadnych wariantów. Użyj przycisku powyżej, aby je dodać, pod warunkiem, że dodałeś już jakieś opcje. Jeśli nie, utwórz je najpierw w zakładce Opcje."
@@ -128,6 +137,7 @@
128
137
  "noAssets": "Nie ma jeszcze żadnych aktywów",
129
138
  "setAsFeatured": "Ustaw jako polecane",
130
139
  "editAsset": "Edytuj zdjęcie",
140
+ "uploadAsset": "Upload image",
131
141
  "removeAsset": "Usuń zasób",
132
142
  "usedForColorsDesc": "Użyj tego atrybutu dla facetów, którzy są kolorami",
133
143
  "colorsCollectionDesc": "Użyj tego dla ścieżek produktu w KOLORZE (BLAT/CORPUS)",
@@ -186,9 +196,11 @@
186
196
  "new": "Nowy wariant",
187
197
  "selectOption": "Wybierz opcję",
188
198
  "createBasic": "Dane podstawowe",
189
- "createOptions": "Opcje"
199
+ "createOptions": "Opcje",
200
+ "createOptionsHint": "Add an option group from this panel to start choosing variant options. You can refine option values later in the Options tab."
190
201
  },
191
202
  "validation": {
192
- "nameSlugRequired": "Pola nazwy i slug są wymagane"
203
+ "nameSlugRequired": "Pola nazwy i slug są wymagane",
204
+ "initialVariantSkuRequired": "Variant SKU is required"
193
205
  }
194
206
  }
@@ -13,6 +13,10 @@
13
13
  "usageLimit": "Use limit",
14
14
  "perCustomerUsageLimit": "Usage limit per customer"
15
15
  },
16
+ "enabled": {
17
+ "header": "Status",
18
+ "label": "Promotion enabled"
19
+ },
16
20
  "conditions": {
17
21
  "header": "Conditions",
18
22
  "add": "Add a new condition",
@@ -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",
@@ -38,6 +38,7 @@
38
38
  "inherit": "Dziedziczenie filtrów",
39
39
  "condition": "Warunek",
40
40
  "addCondition": "Dodaj warunek",
41
+ "noAvailableConditions": "Warunki filtrów nadal się ładują",
41
42
  "addFacets": "Dodawanie wartości aspektów",
42
43
  "labels": {
43
44
  "codes": {
@@ -55,6 +56,12 @@
55
56
  },
56
57
  "contents": {
57
58
  "title": "Zawartość"
59
+ },
60
+ "channels": {
61
+ "title": "Dodatkowe kanały",
62
+ "description": "Wybierz dodatkowe kanały, które mają zostać przypisane po utworzeniu kolekcji. Bieżący kanał zostanie przypisany automatycznie.",
63
+ "currentChannel": "Bieżący kanał",
64
+ "noChannels": "Brak dostępnych kanałów"
58
65
  }
59
66
  },
60
67
  "visibility": {
@@ -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",
@@ -961,6 +962,7 @@
961
962
  "tags": "Filtruj według tagu",
962
963
  "perPageValue": "{{value}} na stronę",
963
964
  "confirmButton": "Dodaj zasób",
965
+ "uploadButton": "Prześlij z dysku",
964
966
  "cancel": "Anuluj",
965
967
  "noImages": "Brak zasobów dla określonego wyszukiwania",
966
968
  "description": "Przesyłanie i zarządzanie zasobami, które mogą być używane w treści"
@@ -40,6 +40,14 @@
40
40
  "confirm": "Usuń wersję roboczą",
41
41
  "cancel": "Anuluj"
42
42
  },
43
+ "createDraftOrder": {
44
+ "title": "Utwórz nowy projekt zamówienia",
45
+ "description": "Projekt zamówienia zostanie utworzony dopiero po potwierdzeniu. Następnie możesz dodać produkty, klienta i dane dostawy.",
46
+ "startButton": "Rozpocznij projekt zamówienia",
47
+ "cancelButton": "Wróć do zamówień",
48
+ "creating": "Tworzenie projektu...",
49
+ "error": "Nie udało się utworzyć projektu zamówienia"
50
+ },
43
51
  "searchProduct": {
44
52
  "id": "ID",
45
53
  "image": "Obraz",