@deenruv/admin-dashboard 1.0.6 → 1.0.11

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 (76) hide show
  1. package/dist/DeenruvAdminPanel.js +5 -4
  2. package/dist/DeenruvDeveloperIndicator.js +1 -1
  3. package/dist/components/ContentAreaSkeleton.js +5 -0
  4. package/dist/components/Menu/LanguagesDropdown.js +4 -3
  5. package/dist/components/Menu/Navigation.js +10 -9
  6. package/dist/components/Menu/Notifications.js +2 -1
  7. package/dist/components/Menu/flag-icons.js +16 -0
  8. package/dist/components/Menu/index.js +3 -2
  9. package/dist/pages/Custom404.js +16 -2
  10. package/dist/pages/Root.js +2 -2
  11. package/dist/pages/admins/Detail.js +7 -7
  12. package/dist/pages/admins/_components/AdminDetailView.js +17 -9
  13. package/dist/pages/channels/Detail.js +12 -12
  14. package/dist/pages/channels/_components/ChannelDetailView.js +25 -17
  15. package/dist/pages/collections/Detail.js +8 -7
  16. package/dist/pages/collections/List.js +1 -2
  17. package/dist/pages/collections/_components/CollectionDetailView.js +65 -22
  18. package/dist/pages/collections/_components/CollectionProductVariantsDrawer.js +1 -1
  19. package/dist/pages/countries/Detail.js +5 -5
  20. package/dist/pages/countries/_components/CountryDetailView.js +15 -8
  21. package/dist/pages/customer-groups/Detail.js +3 -3
  22. package/dist/pages/customer-groups/_components/CustomerGroupsDetailView.js +4 -4
  23. package/dist/pages/customers/Detail.js +6 -8
  24. package/dist/pages/customers/_components/AddressForm.js +48 -60
  25. package/dist/pages/customers/_components/AddressesCard.js +1 -1
  26. package/dist/pages/customers/_components/CustomerDetailView.js +9 -9
  27. package/dist/pages/dashboard/Dashboard.js +3 -2
  28. package/dist/pages/extensions/Extensions.js +12 -13
  29. package/dist/pages/facets/Detail.js +6 -6
  30. package/dist/pages/facets/_components/AddFacetValueDialog.js +39 -39
  31. package/dist/pages/facets/_components/FacetDetailView.js +19 -12
  32. package/dist/pages/global-settings/GlobalSettingsComponent.js +9 -8
  33. package/dist/pages/global-settings/index.js +4 -4
  34. package/dist/pages/orders/List.js +2 -1
  35. package/dist/pages/orders/_components/AddressCard.js +123 -78
  36. package/dist/pages/orders/_components/CustomerSelectCard.js +38 -43
  37. package/dist/pages/orders/_components/FulfillmentModal.js +47 -28
  38. package/dist/pages/orders/_components/ModifyingCard.js +30 -13
  39. package/dist/pages/orders/_components/OrderLineCustomFields.js +2 -1
  40. package/dist/pages/orders/_components/PossibleOrderStates.js +2 -1
  41. package/dist/pages/orders/_components/ProductsCard.js +2 -1
  42. package/dist/pages/orders/_components/SurchargeCard.js +58 -60
  43. package/dist/pages/orders/_components/TopActions.js +2 -1
  44. package/dist/pages/payment-methods/Detail.js +8 -7
  45. package/dist/pages/payment-methods/_components/PaymentMethodDetailView.js +22 -14
  46. package/dist/pages/products/Detail.js +9 -7
  47. package/dist/pages/products/_components/AddOptionGroupDialog.js +36 -14
  48. package/dist/pages/products/_components/BasicFieldsCard.js +5 -2
  49. package/dist/pages/products/_components/OptionValueCard.js +25 -23
  50. package/dist/pages/products/_components/ProductDetailSidebar.js +15 -7
  51. package/dist/pages/products/_components/ProductDetailView.js +51 -17
  52. package/dist/pages/products/_components/Variant.js +105 -57
  53. package/dist/pages/promotions/Detail.js +13 -11
  54. package/dist/pages/promotions/_components/OptionsCard.js +1 -1
  55. package/dist/pages/promotions/_components/PromotionDetailSidebar.js +9 -6
  56. package/dist/pages/promotions/_components/PromotionDetailView.js +26 -20
  57. package/dist/pages/roles/Detail.js +5 -5
  58. package/dist/pages/roles/_components/RoleDetailView.js +13 -8
  59. package/dist/pages/sellers/Detail.js +4 -4
  60. package/dist/pages/sellers/_components/SellerDetailView.js +4 -4
  61. package/dist/pages/shipping-methods/Detail.js +14 -12
  62. package/dist/pages/shipping-methods/_components/Lines.js +1 -1
  63. package/dist/pages/shipping-methods/_components/ShippingMethodDetailView.js +26 -13
  64. package/dist/pages/shipping-methods/_components/TestCard.js +67 -21
  65. package/dist/pages/status/_components/Health.js +2 -1
  66. package/dist/pages/status/_components/Jobs.js +2 -6
  67. package/dist/pages/stock-locations/Detail.js +4 -4
  68. package/dist/pages/stock-locations/_components/StockLocationDetailView.js +5 -5
  69. package/dist/pages/tax-categories/Detail.js +4 -4
  70. package/dist/pages/tax-categories/_components/TaxCategoryDetailView.js +7 -7
  71. package/dist/pages/tax-rates/Detail.js +8 -8
  72. package/dist/pages/tax-rates/_components/TaxRateDetailView.js +13 -9
  73. package/dist/pages/zones/Detail.js +4 -4
  74. package/dist/pages/zones/_components/ZoneDetailView.js +8 -7
  75. package/dist/version.js +1 -1
  76. package/package.json +79 -78
@@ -7,10 +7,11 @@ import { AnimatePresence } from 'framer-motion';
7
7
  import { Toaster } from 'sonner';
8
8
  import i18n from './i18.js';
9
9
  import { Routes, PluginProvider, PluginStore, useSettings, GlobalStoreProvider, DEFAULT_CHANNEL_CODE, NotificationProvider, } from '@deenruv/react-ui-devkit';
10
+ import { useShallow } from 'zustand/react/shallow';
10
11
  import { LanguageCode } from '@deenruv/admin-types';
11
12
  import { Root } from "./pages/Root.js";
12
13
  import { LoginScreen } from "./pages/LoginScreen.js";
13
- import { Custom404 } from "./pages/Custom404.js";
14
+ import { ErrorPage } from "./pages/Custom404.js";
14
15
  import * as Pages from "./pages/index.js";
15
16
  import * as resources from "./locales/index.js";
16
17
  import { ADMIN_DASHBOARD_VERSION } from "./version.js";
@@ -78,17 +79,17 @@ export const DeenruvAdminPanel = ({ plugins, settings }) => {
78
79
  const router = createBrowserRouter([
79
80
  {
80
81
  element: _jsx(Root, { allPaths: [...DeenruvPaths].map((path) => path.path).filter(Boolean) }),
81
- errorElement: _jsx(Custom404, {}),
82
+ errorElement: _jsx(ErrorPage, {}),
82
83
  children: [...DeenruvPaths, ...pluginsStore.routes],
83
84
  },
84
85
  ]);
85
- const { theme, isLoggedIn, ...context } = useSettings((p) => ({
86
+ const { theme, isLoggedIn, ...context } = useSettings(useShallow((p) => ({
86
87
  theme: p.theme,
87
88
  isLoggedIn: p.isLoggedIn,
88
89
  channel: p.selectedChannel,
89
90
  language: p.language,
90
91
  translationsLanguage: p.translationsLanguage,
91
- }));
92
+ })));
92
93
  useEffect(() => {
93
94
  const root = window.document.documentElement;
94
95
  root.classList.remove('light', 'dark');
@@ -50,7 +50,7 @@ import { useState } from 'react';
50
50
  // );
51
51
  // };
52
52
  export const DeenruvDeveloperIndicator = () => {
53
- const { graphQLSchema } = useServer(({ graphQLSchema }) => ({ graphQLSchema }));
53
+ const graphQLSchema = useServer((s) => s.graphQLSchema);
54
54
  const { plugins, viewMarkers, setViewMarkers } = usePluginStore();
55
55
  const [showSchema, setShowSchema] = useState(false);
56
56
  return (_jsx("div", { className: "fixed right-4 bottom-4", children: _jsxs(Drawer, { children: [_jsx(DrawerTrigger, { asChild: true, children: _jsx(Button, { variant: "outline", className: "flex size-10 items-center justify-center rounded-full border-dashed border-primary text-xs", children: "DDP" }) }), _jsx(DrawerContent, { className: "w-full max-w-5xl place-self-end", children: _jsxs("div", { className: "w-full", children: [_jsx(DrawerHeader, { children: _jsx(DrawerTitle, { children: "DDP - Deenruv Developer Panel" }) }), _jsx("div", { className: "p-4 pb-0", children: _jsxs("div", { className: "flex flex-col space-y-4 md:flex-row md:space-y-0 md:space-x-4", children: [_jsxs("div", { className: "space-y-4 md:w-1/4", children: [_jsxs("div", { className: "rounded-md border p-3", children: [_jsx("h3", { className: "mb-2 text-sm font-medium", children: "Controls" }), _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "flex items-center space-x-2", children: [_jsx(Switch, { id: "markers", checked: viewMarkers, onCheckedChange: setViewMarkers }), _jsx("label", { htmlFor: "markers", className: "text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70", children: "Show markers (ctrl + x)" })] }), _jsxs("div", { className: "flex items-center space-x-2", children: [_jsx(Switch, { id: "schema", checked: showSchema, onCheckedChange: setShowSchema }), _jsx("label", { htmlFor: "schema", className: "text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70", children: "Show GraphQL Schema" })] })] })] }), _jsxs("div", { className: "rounded-md border p-3", children: [_jsxs("div", { className: "mb-1 flex items-center gap-2", children: [_jsx("div", { className: "size-3 rounded-full bg-green-500" }), _jsx("h3", { className: "text-sm font-medium", children: "Plugins" })] }), _jsx(ScrollArea, { className: "mt-2 h-[250px]", children: _jsxs("div", { className: "grid gap-2 pr-2", children: [plugins.map((plugin) => (_jsxs("div", { className: "dark:hover:bg-gray-750 flex items-center rounded-md border border-gray-200 bg-gray-50 p-2 transition-colors hover:bg-gray-100 dark:border-gray-700 dark:bg-gray-800", children: [_jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "text-sm font-medium", children: plugin.name }), _jsxs("div", { className: "text-xs text-gray-500 dark:text-gray-400", children: ["v", plugin.version] })] }), _jsx("div", { className: "mr-1 size-2 rounded-full bg-green-500", title: "Active" })] }, plugin.name))), plugins.length === 0 && (_jsx("div", { className: "py-4 text-center text-sm text-gray-500 italic dark:text-gray-400", children: "No plugins installed" }))] }) })] })] }), _jsx("div", { className: "md:w-3/4" })] }) }), _jsx(DrawerFooter, { children: _jsx(DrawerClose, { asChild: true, children: _jsx(Button, { variant: "outline", children: "Close" }) }) })] }) })] }) }));
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Skeleton } from '@deenruv/react-ui-devkit';
3
+ export function ContentAreaSkeleton() {
4
+ return (_jsxs("div", { className: "space-y-6 p-6", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx(Skeleton, { className: "h-8 w-48" }), _jsx(Skeleton, { className: "h-10 w-32 rounded-md" })] }), _jsxs("div", { className: "space-y-4 rounded-lg border bg-card p-6", children: [_jsxs("div", { className: "flex items-center gap-4", children: [_jsx(Skeleton, { className: "h-5 w-40" }), _jsx(Skeleton, { className: "h-5 w-24" }), _jsx(Skeleton, { className: "h-5 w-32" })] }), Array.from({ length: 6 }).map((_, i) => (_jsxs("div", { className: "flex items-center gap-4 py-2", children: [_jsx(Skeleton, { className: "h-4 w-8" }), _jsx(Skeleton, { className: "h-10 w-10 rounded-md" }), _jsx(Skeleton, { className: "h-4 flex-1" }), _jsx(Skeleton, { className: "h-4 w-24" }), _jsx(Skeleton, { className: "h-4 w-20" })] }, i)))] })] }));
5
+ }
@@ -1,8 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { LanguageCode } from '@deenruv/admin-types';
3
3
  import { Badge, Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, getLanguageName, useSettings, useTranslation, } from '@deenruv/react-ui-devkit';
4
+ import { useShallow } from 'zustand/react/shallow';
4
5
  import { Check } from 'lucide-react';
5
- import { US, PL, CZ, DE, EU } from 'country-flag-icons/react/3x2';
6
+ import { US, PL, CZ, DE, EU } from './flag-icons';
6
7
  const uiLanguages = [LanguageCode.en, LanguageCode.pl];
7
8
  const langFlagDict = {
8
9
  [LanguageCode.en]: US,
@@ -13,13 +14,13 @@ const langFlagDict = {
13
14
  };
14
15
  export const LanguagesDropdown = () => {
15
16
  const { t } = useTranslation('common');
16
- const { contentLng, setContentLng, setUiLng, uiLng, selectedChannel } = useSettings((p) => ({
17
+ const { contentLng, setContentLng, setUiLng, uiLng, selectedChannel } = useSettings(useShallow((p) => ({
17
18
  uiLng: p.language,
18
19
  setUiLng: p.setLanguage,
19
20
  contentLng: p.translationsLanguage,
20
21
  setContentLng: p.setTranslationsLanguage,
21
22
  selectedChannel: p.selectedChannel,
22
- }));
23
+ })));
23
24
  const Flag = langFlagDict[contentLng] || EU;
24
25
  const contentLanguages = selectedChannel?.availableLanguageCodes || [];
25
26
  return (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", className: "h-9", children: [t('language'), _jsx(Badge, { variant: "outline", className: "ml-2", children: _jsx(Flag, { className: "size-4" }) })] }) }), _jsxs(DropdownMenuContent, { align: "end", className: "z-[150]", children: [_jsxs(DropdownMenuSub, { children: [_jsx(DropdownMenuSubTrigger, { children: _jsx("span", { children: t('content') }) }), _jsx(DropdownMenuPortal, { children: _jsx(DropdownMenuSubContent, { className: "z-[150]", children: contentLanguages.map((lng) => (_jsxs(DropdownMenuItem, { onClick: () => lng !== contentLng && setContentLng(lng), children: [_jsx("span", { children: getLanguageName(lng, uiLng) }), lng === contentLng && _jsx(Check, { className: "ml-auto size-4" })] }, lng))) }) })] }), _jsxs(DropdownMenuSub, { children: [_jsx(DropdownMenuSubTrigger, { children: _jsx("span", { children: "UI" }) }), _jsx(DropdownMenuPortal, { children: _jsx(DropdownMenuSubContent, { className: "z-[150]", children: uiLanguages.map((lng) => (_jsxs(DropdownMenuItem, { onClick: () => lng !== uiLng && setUiLng(lng), children: [_jsx("span", { children: getLanguageName(lng) }), lng === uiLng && _jsx(Check, { className: "ml-auto size-4" })] }, lng))) }) })] })] })] }));
@@ -1,7 +1,7 @@
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, } from '@deenruv/react-ui-devkit';
4
+ import { cn, buttonVariants, Routes, usePluginStore, Tooltip, TooltipContent, TooltipTrigger, useServer, Accordion, AccordionItem, AccordionTrigger, AccordionContent, useNotifications, useTranslation, capitalizeFirstLetter, Skeleton, } from '@deenruv/react-ui-devkit';
5
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
6
  import { Permission } from '@deenruv/admin-types';
7
7
  export function Navigation({ isCollapsed }) {
@@ -259,12 +259,13 @@ export function Navigation({ isCollapsed }) {
259
259
  // [permittedNavigationGroups, navMenuData],
260
260
  // );
261
261
  const defaultAccordionOpenValue = ['shop-group', 'assortment-group'];
262
- if (!loaded)
263
- return null;
264
- return (_jsx("div", { className: "relative overflow-y-auto", children: _jsx("div", { "data-collapsed": isCollapsed, className: "group flex h-[calc(100%-70px)] flex-col gap-4 pb-2 data-[collapsed=true]:py-2 lg:h-[calc(100%-80px)]", children: _jsx(Accordion, { type: "multiple", className: "w-full", defaultValue: defaultAccordionOpenValue, value: isCollapsed ? permittedNavigationGroups.map((g) => g.id) : undefined, children: permittedNavigationGroups.map((group) => (_jsx(AccordionItem, { value: group.id, children: _jsxs(React.Fragment, { children: [!isCollapsed && (_jsx(AccordionTrigger, { className: cn('flex items-center justify-between pr-3 hover:no-underline'), children: _jsxs("div", { className: "flex items-center gap-2 px-6", children: [_jsx("h4", { className: "text-xs font-bold uppercase hover:underline", children: group.label }), getNavigationNotification(group.id), viewMarkers ? (_jsx("p", { className: "text-xs font-semibold text-muted-foreground lowercase dark:text-muted-foreground", children: group.id })) : null] }) })), _jsx(AccordionContent, { className: cn(isCollapsed ? 'py-2' : 'pb-4'), children: _jsx("nav", { id: group.id, className: "grid gap-1 px-2 text-muted-foreground group-[[data-collapsed=true]]:justify-center group-[[data-collapsed=true]]:px-2", children: group.links.map((link, index) => {
265
- const notifications = getNavigationNotification(link.id);
266
- return (_jsx(React.Fragment, { children: isCollapsed ? (_jsxs(Tooltip, { delayDuration: 0, children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("div", { children: _jsx(NavLink, { to: link.href, viewTransition: true, children: _jsxs("div", { className: cn(buttonVariants({ variant: 'navigation-link', size: 'icon' }), 'h-9 w-9', location.pathname === link.href &&
267
- 'bg-muted opacity-100 hover:bg-muted hover:text-muted-foreground dark:bg-muted dark:hover:bg-muted'), children: [_jsx(link.icon, { className: "size-6" }), _jsx("span", { className: "sr-only", children: link.title })] }) }) }) }), _jsxs(TooltipContent, { side: "right", className: "relative flex items-center gap-4", children: [viewMarkers ? (_jsx("div", { className: "text-xs font-semibold text-muted-foreground lowercase dark:text-muted-foreground", children: link.id })) : null, capitalizeFirstLetter(link.title), notifications] })] }, index)) : (_jsx(NavLink, { to: link.href, viewTransition: true, children: _jsxs("div", { id: link.id, className: cn('relative flex items-center justify-start rounded-md px-4 py-2 capitalize', location.pathname === link.href &&
268
- 'bg-muted font-semibold opacity-100 hover:bg-muted hover:text-muted-foreground dark:bg-muted dark:hover:bg-muted'), children: [viewMarkers ? (_jsx("div", { className: "absolute top-1/2 right-2 -translate-y-1/2 text-xs font-semibold text-muted-foreground lowercase dark:text-muted-foreground", children: link.id })) : null, _jsx(link.icon, { className: "mr-2 size-4" }), capitalizeFirstLetter(link.title), notifications] }) })) }, link.id));
269
- }) }) })] }, group.id) }))) }) }) }));
262
+ if (!loaded) {
263
+ return (_jsx("div", { className: "relative overflow-y-auto", children: _jsx("div", { className: "flex h-[calc(100%-70px)] flex-col gap-4 pb-2 lg:h-[calc(100%-80px)]", children: Array.from({ length: 3 }).map((_, groupIdx) => (_jsxs("div", { className: "flex flex-col gap-1 px-2", children: [!isCollapsed && (_jsx("div", { className: "px-4 py-2", children: _jsx(Skeleton, { className: "h-3 w-20" }) })), Array.from({ length: groupIdx === 0 ? 4 : groupIdx === 1 ? 5 : 3 }).map((_, linkIdx) => (_jsxs("div", { className: cn('flex items-center rounded-md px-4 py-2', isCollapsed && 'justify-center px-0'), children: [_jsx(Skeleton, { className: cn('size-4 shrink-0', isCollapsed && 'size-6') }), !isCollapsed && _jsx(Skeleton, { className: "ml-2 h-4 w-24" })] }, linkIdx)))] }, groupIdx))) }) }));
264
+ }
265
+ return (_jsx("div", { className: "relative overflow-y-auto", children: _jsx("div", { "data-collapsed": isCollapsed, className: "group flex h-[calc(100%-70px)] flex-col gap-4 pb-2 data-[collapsed=true]:py-2 lg:h-[calc(100%-80px)]", children: _jsx(Accordion, { type: "multiple", className: "w-full", defaultValue: defaultAccordionOpenValue, value: isCollapsed ? permittedNavigationGroups.map((g) => g.id) : undefined, children: permittedNavigationGroups.map((group) => (_jsxs(AccordionItem, { value: group.id, children: [!isCollapsed && (_jsx(AccordionTrigger, { className: cn('flex items-center justify-between pr-3 hover:no-underline'), children: _jsxs("div", { className: "flex items-center gap-2 px-6", children: [_jsx("h4", { className: "text-xs font-bold uppercase hover:underline", children: group.label }), getNavigationNotification(group.id), viewMarkers ? (_jsx("p", { className: "text-xs font-semibold text-muted-foreground lowercase dark:text-muted-foreground", children: group.id })) : null] }) })), _jsx(AccordionContent, { className: cn(isCollapsed ? 'py-2' : 'pb-4'), children: _jsx("nav", { id: group.id, className: "grid gap-1 px-2 text-muted-foreground group-[[data-collapsed=true]]:justify-center group-[[data-collapsed=true]]:px-2", children: group.links.map((link, index) => {
266
+ const notifications = getNavigationNotification(link.id);
267
+ return (_jsx(React.Fragment, { children: isCollapsed ? (_jsxs(Tooltip, { delayDuration: 0, children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("div", { children: _jsx(NavLink, { to: link.href, viewTransition: true, children: _jsxs("div", { className: cn(buttonVariants({ variant: 'navigation-link', size: 'icon' }), 'h-9 w-9', location.pathname === link.href &&
268
+ 'bg-muted opacity-100 hover:bg-muted hover:text-muted-foreground dark:bg-muted dark:hover:bg-muted'), children: [_jsx(link.icon, { className: "size-6" }), _jsx("span", { className: "sr-only", children: link.title })] }) }) }) }), _jsxs(TooltipContent, { side: "right", className: "relative flex items-center gap-4", children: [viewMarkers ? (_jsx("div", { className: "text-xs font-semibold text-muted-foreground lowercase dark:text-muted-foreground", children: link.id })) : null, capitalizeFirstLetter(link.title), notifications] })] }, index)) : (_jsx(NavLink, { to: link.href, viewTransition: true, children: _jsxs("div", { id: link.id, className: cn('relative flex items-center justify-start rounded-md px-4 py-2 capitalize', location.pathname === link.href &&
269
+ 'bg-muted font-semibold opacity-100 hover:bg-muted hover:text-muted-foreground dark:bg-muted dark:hover:bg-muted'), children: [viewMarkers ? (_jsx("div", { className: "absolute top-1/2 right-2 -translate-y-1/2 text-xs font-semibold text-muted-foreground lowercase dark:text-muted-foreground", children: link.id })) : null, _jsx(link.icon, { className: "mr-2 size-4" }), capitalizeFirstLetter(link.title), notifications] }) })) }, link.id));
270
+ }) }) })] }, group.id))) }) }) }));
270
271
  }
@@ -3,12 +3,13 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { Bell, Check, Clock, Trash2 } from 'lucide-react';
4
4
  import { useEffect, useState, useRef, useMemo } from 'react';
5
5
  import { Popover, PopoverTrigger, PopoverContent, Card, CardHeader, CardTitle, CardDescription, CardContent, useServer, Button, CardFooter, useNotifications, Tabs, TabsContent, TabsList, TabsTrigger, cn, formatDate, useTranslation, } from '@deenruv/react-ui-devkit';
6
+ import { useShallow } from 'zustand/react/shallow';
6
7
  const NOTIFICATIONS_LOCAL_STORAGE_KEY = 'DEENRUV_NOTIFICATIONS';
7
8
  export const Notifications = () => {
8
9
  const cyclingNotifications = useNotifications(({ notifications }) => notifications);
9
10
  const getMainNotification = useNotifications(({ getMainNotification }) => getMainNotification);
10
11
  const jobQueues = useServer(({ jobQueues }) => jobQueues);
11
- const status = useServer(({ status }) => status);
12
+ const status = useServer(useShallow(({ status }) => status));
12
13
  const { t } = useTranslation('common');
13
14
  const [notifications, setNotifications] = useState([]);
14
15
  const prevJobQueuesRef = useRef(jobQueues);
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export function US(props) {
3
+ return (_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 513 342", ...props, children: [_jsx("path", { fill: "#FFF", d: "M0 0h513v342H0z" }), _jsx("g", { fill: "#D80027", children: _jsx("path", { d: "M0 0h513v26.3H0zM0 52.6h513v26.3H0zM0 105.2h513v26.3H0zM0 157.8h513v26.3H0zM0 210.5h513v26.3H0zM0 263.1h513v26.3H0zM0 315.7h513V342H0z" }) }), _jsx("path", { fill: "#2E52B2", d: "M0 0h256.5v184.1H0z" }), _jsx("g", { fill: "#FFF", children: _jsx("path", { d: "m47.8 138.9-4-12.8-4.4 12.8H26.2l10.7 7.7-4 12.8 10.9-7.9 10.6 7.9-4.1-12.8 10.9-7.7zM104.1 138.9l-4.1-12.8-4.2 12.8H82.6l10.7 7.7-4 12.8 10.7-7.9 10.8 7.9-4-12.8 10.7-7.7zM160.6 138.9l-4.3-12.8-4 12.8h-13.5l11 7.7-4.2 12.8 10.7-7.9 11 7.9-4.2-12.8 10.7-7.7zM216.8 138.9l-4-12.8-4.2 12.8h-13.3l10.8 7.7-4 12.8 10.7-7.9 10.8 7.9-4.3-12.8 11-7.7zM100 75.3l-4.2 12.8H82.6L93.3 96l-4 12.6 10.7-7.8 10.8 7.8-4-12.6 10.7-7.9h-13.4zM43.8 75.3l-4.4 12.8H26.2L36.9 96l-4 12.6 10.9-7.8 10.6 7.8L50.3 96l10.9-7.9H47.8zM156.3 75.3l-4 12.8h-13.5l11 7.9-4.2 12.6 10.7-7.8 11 7.8-4.2-12.6 10.7-7.9h-13.2zM212.8 75.3l-4.2 12.8h-13.3l10.8 7.9-4 12.6 10.7-7.8 10.8 7.8-4.3-12.6 11-7.9h-13.5zM43.8 24.7l-4.4 12.6H26.2l10.7 7.9-4 12.7L43.8 50l10.6 7.9-4.1-12.7 10.9-7.9H47.8zM100 24.7l-4.2 12.6H82.6l10.7 7.9-4 12.7L100 50l10.8 7.9-4-12.7 10.7-7.9h-13.4zM156.3 24.7l-4 12.6h-13.5l11 7.9-4.2 12.7 10.7-7.9 11 7.9-4.2-12.7 10.7-7.9h-13.2zM212.8 24.7l-4.2 12.6h-13.3l10.8 7.9-4 12.7 10.7-7.9 10.8 7.9-4.3-12.7 11-7.9h-13.5z" }) })] }));
4
+ }
5
+ export function PL(props) {
6
+ return (_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 85.333 512 341.333", ...props, children: [_jsxs("g", { fill: "#FFF", children: [_jsx("path", { d: "M0 85.337h512v341.326H0z" }), _jsx("path", { d: "M0 85.337h512V256H0z" })] }), _jsx("path", { fill: "#D80027", d: "M0 256h512v170.663H0z" })] }));
7
+ }
8
+ export function CZ(props) {
9
+ return (_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 513 342", ...props, children: [_jsx("path", { fill: "#11457e", d: "M0 0h513v342H0z" }), _jsx("path", { fill: "#d7141a", d: "M513 171v171H0l256.5-171z" }), _jsx("path", { fill: "#FFF", d: "M513 0v171H256.5L0 0z" })] }));
10
+ }
11
+ export function DE(props) {
12
+ return (_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 513 342", ...props, children: [_jsx("path", { fill: "#D80027", d: "M0 0h513v342H0z" }), _jsx("path", { d: "M0 0h513v114H0z" }), _jsx("path", { fill: "#FFDA44", d: "M0 228h513v114H0z" })] }));
13
+ }
14
+ export function EU(props) {
15
+ return (_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 810 540", ...props, children: [_jsx("path", { fill: "#039", d: "M0 0h810v540H0z" }), _jsx("path", { fill: "#FC0", d: "m404.951 59.865 6.735 20.718h21.894l-17.633 12.812 6.638 20.74-17.634-12.812-17.634 12.812 6.736-20.729-17.633-12.812h21.796zm-90 24 6.735 20.718h21.894l-17.633 12.812 6.638 20.74-17.634-12.812-17.634 12.812 6.736-20.729-17.633-12.812h21.796zm-66 66 6.735 20.718h21.894l-17.633 12.812 6.638 20.74-17.634-12.812-17.634 12.812 6.736-20.729-17.633-12.812h21.796zm-24 89.999 6.735 20.719h21.894l-17.633 12.813 6.638 20.74-17.634-12.813-17.634 12.813 6.736-20.731-17.633-12.811h21.796zm24 90.001 6.735 20.719h21.894l-17.633 12.811 6.638 20.74-17.634-12.811-17.634 12.811 6.736-20.729-17.633-12.812h21.796zm66 66 6.735 20.719h21.894l-17.633 12.811 6.638 20.74-17.634-12.811-17.634 12.811 6.736-20.729-17.633-12.812h21.796zm180-312 6.735 20.718h21.894l-17.633 12.812 6.637 20.74-17.633-12.812-17.635 12.812 6.737-20.729-17.633-12.812h21.795zm66 66 6.735 20.718h21.894l-17.633 12.812 6.637 20.74-17.633-12.812-17.635 12.812 6.737-20.729-17.633-12.812h21.795zm24 90 6.735 20.718h21.894l-17.633 12.813 6.638 20.74-17.634-12.813-17.634 12.813 6.736-20.731-17.633-12.811h21.796zm-24 90 6.735 20.719h21.894l-17.633 12.811 6.638 20.74-17.634-12.811-17.634 12.811 6.736-20.729-17.633-12.812h21.796zm-156 90 6.735 20.719h21.894l-17.633 12.811 6.638 20.74-17.634-12.811-17.634 12.811 6.736-20.729-17.633-12.812h21.796zm90-24.001 6.735 20.719h21.894l-17.633 12.812 6.639 20.74-17.635-12.812-17.633 12.812 6.735-20.73-17.633-12.811h21.797z" })] }));
16
+ }
@@ -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 { useShallow } from 'zustand/react/shallow';
4
5
  import { GripVertical, LogOutIcon, MenuIcon, Moon, Slash, Sun, SunMoon, RotateCwSquare, SearchIcon, } from 'lucide-react';
5
6
  import * as ResizablePrimitive from 'react-resizable-panels';
6
7
  import { Navigation } from './Navigation.js';
@@ -19,13 +20,13 @@ export const Menu = ({ children }) => {
19
20
  const linkPath = [];
20
21
  const { t } = useTranslation('common');
21
22
  const { topNavigationActionsMenu, topNavigationComponents } = usePluginStore();
22
- const { logOut, theme, setTheme } = useSettings((p) => ({
23
+ const { logOut, theme, setTheme } = useSettings(useShallow((p) => ({
23
24
  logOut: p.logOut,
24
25
  theme: p.theme,
25
26
  setTheme: p.setTheme,
26
27
  language: p.language,
27
28
  setLanguage: p.setLanguage,
28
- }));
29
+ })));
29
30
  const navigate = useNavigate();
30
31
  const [isCollapsed, setIsCollapsed] = useState(false);
31
32
  const { activeAdministrator, setJobQueue } = useServer();
@@ -1,5 +1,19 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useRouteError, isRouteErrorResponse, useNavigate } from 'react-router';
2
3
  import { Menu } from "../components";
3
- export const Custom404 = () => {
4
- return (_jsx("div", { className: "flex max-h-screen w-full max-w-full overflow-hidden bg-background text-foreground", children: _jsx(Menu, { children: _jsxs("div", { className: "flex size-full flex-1 flex-col overflow-y-auto p-4 pt-6 md:p-8", children: [_jsx("h1", { children: "404" }), _jsx("p", { children: "Page not found" })] }) }) }));
4
+ import { Button } from '@deenruv/react-ui-devkit';
5
+ import { ArrowLeft, AlertTriangle, FileQuestion } from 'lucide-react';
6
+ export const ErrorPage = () => {
7
+ const error = useRouteError();
8
+ const navigate = useNavigate();
9
+ const is404 = isRouteErrorResponse(error) && error.status === 404;
10
+ if (is404) {
11
+ return (_jsx("div", { className: "flex max-h-screen w-full max-w-full overflow-hidden bg-background text-foreground", children: _jsx(Menu, { children: _jsxs("div", { className: "flex size-full flex-1 flex-col items-center justify-center overflow-y-auto p-4 pt-6 md:p-8", children: [_jsx(FileQuestion, { className: "mb-4 size-16 text-muted-foreground" }), _jsx("h1", { className: "mb-2 text-4xl font-bold", children: "404" }), _jsx("p", { className: "mb-6 text-lg text-muted-foreground", children: "Page not found" }), _jsxs("div", { className: "flex gap-3", children: [_jsxs(Button, { variant: "outline", onClick: () => navigate(-1), children: [_jsx(ArrowLeft, { className: "mr-2 size-4" }), "Go back"] }), _jsx(Button, { onClick: () => navigate('/admin-ui/'), children: "Go to Dashboard" })] })] }) }) }));
12
+ }
13
+ // Runtime error - show dev-friendly details
14
+ const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred';
15
+ const errorStack = error instanceof Error ? error.stack : undefined;
16
+ return (_jsx("div", { className: "flex max-h-screen w-full max-w-full overflow-hidden bg-background text-foreground", children: _jsx(Menu, { children: _jsx("div", { className: "flex size-full flex-1 flex-col overflow-y-auto p-4 pt-6 md:p-8", children: _jsxs("div", { className: "mx-auto w-full max-w-3xl", children: [_jsxs("div", { className: "mb-6 flex items-center gap-3", children: [_jsx(AlertTriangle, { className: "size-8 text-destructive" }), _jsx("h1", { className: "text-2xl font-bold", children: "Something went wrong" })] }), _jsx("div", { className: "mb-6 rounded-lg border border-destructive/30 bg-destructive/5 p-4", children: _jsx("p", { className: "font-mono text-sm text-destructive", children: errorMessage }) }), errorStack && (_jsxs("details", { className: "mb-6", children: [_jsx("summary", { className: "cursor-pointer text-sm font-medium text-muted-foreground hover:text-foreground", children: "Stack trace" }), _jsx("pre", { className: "mt-2 overflow-x-auto rounded-lg bg-muted p-4 font-mono text-xs whitespace-pre-wrap", children: errorStack })] })), _jsxs("div", { className: "flex gap-3", children: [_jsxs(Button, { variant: "outline", onClick: () => navigate(-1), children: [_jsx(ArrowLeft, { className: "mr-2 size-4" }), "Go back"] }), _jsx(Button, { variant: "outline", onClick: () => window.location.reload(), children: "Reload page" }), _jsx(Button, { onClick: () => navigate('/admin-ui/'), children: "Go to Dashboard" })] })] }) }) }) }));
5
17
  };
18
+ // Keep backward compat export name
19
+ export const Custom404 = ErrorPage;
@@ -7,6 +7,7 @@ import { toast } from 'sonner';
7
7
  import { activeAdministratorSelector, paymentMethodsSelector, useServer, useSettings, } from '@deenruv/react-ui-devkit';
8
8
  import { Menu } from "../components";
9
9
  import { GlobalSearch } from "../components/GlobalSearch.js";
10
+ import { ContentAreaSkeleton } from "../components/ContentAreaSkeleton.js";
10
11
  import { DeenruvDeveloperIndicator } from "../DeenruvDeveloperIndicator.js";
11
12
  import { Permission } from '@deenruv/admin-types';
12
13
  const TAKE = 100;
@@ -244,9 +245,8 @@ export const Root = ({ allPaths }) => {
244
245
  };
245
246
  fetchGraphQLSchema().then(async (schema) => {
246
247
  window.__DEENRUV_SCHEMA__ = schema;
247
- await new Promise((resolve) => setTimeout(resolve, 1000));
248
248
  await init();
249
249
  });
250
250
  }, []);
251
- return (_jsx(_Fragment, { children: _jsx("div", { className: "flex max-h-screen w-full max-w-full overflow-hidden bg-background text-foreground", children: loaded ? (_jsxs(_Fragment, { children: [_jsx(Menu, { children: _jsx(Outlet, {}) }), _jsx(GlobalSearch, {}), _jsx(CanLeaveRouteDialog, {}), isLocalhost ? _jsx(DeenruvDeveloperIndicator, {}) : null] })) : (_jsx("div", { className: "flex h-screen w-full items-center justify-center", children: _jsx("span", { className: "animate-spin", children: "\u23F3" }) })) }) }));
251
+ return (_jsx(_Fragment, { children: _jsxs("div", { className: "flex max-h-screen w-full max-w-full overflow-hidden bg-background text-foreground", children: [_jsx(Menu, { children: loaded ? _jsx(Outlet, {}) : _jsx(ContentAreaSkeleton, {}) }), loaded && (_jsxs(_Fragment, { children: [_jsx(GlobalSearch, {}), _jsx(CanLeaveRouteDialog, {}), isLocalhost ? _jsx(DeenruvDeveloperIndicator, {}) : null] }))] }) }));
252
252
  };
@@ -16,16 +16,16 @@ export const AdminsDetailPage = () => {
16
16
  const { t } = useTranslation('admins');
17
17
  const { emailValidator, stringValidator, arrayValidator } = useValidators();
18
18
  const onSubmitHandler = useCallback((data) => {
19
- if (!data.emailAddress?.validatedValue) {
19
+ if (!data.emailAddress) {
20
20
  throw new Error('Name is required.');
21
21
  }
22
22
  const inputData = {
23
- emailAddress: data.emailAddress?.validatedValue,
24
- firstName: data.firstName?.validatedValue,
25
- lastName: data.lastName?.validatedValue,
26
- password: data.password?.validatedValue ? data.password?.validatedValue : undefined,
27
- roleIds: data.roleIds?.validatedValue,
28
- ...(data.customFields?.validatedValue ? { customFields: data.customFields?.validatedValue } : {}),
23
+ emailAddress: data.emailAddress,
24
+ firstName: data.firstName,
25
+ lastName: data.lastName,
26
+ password: data.password ? data.password : undefined,
27
+ roleIds: data.roleIds,
28
+ ...(data.customFields ? { customFields: data.customFields } : {}),
29
29
  };
30
30
  if (id) {
31
31
  return update({
@@ -13,7 +13,7 @@ const ADMIN_FORM_KEYS = [
13
13
  ];
14
14
  export const AdminDetailView = () => {
15
15
  const { form, entity, fetchEntity, id } = useDetailView('admins-detail-view', ...ADMIN_FORM_KEYS);
16
- const { base: { setField, state }, } = form;
16
+ const { base } = form;
17
17
  const editMode = useMemo(() => !!id, [id]);
18
18
  const { t } = useTranslation('admins');
19
19
  useEffect(() => {
@@ -21,14 +21,22 @@ export const AdminDetailView = () => {
21
21
  const res = await fetchEntity();
22
22
  if (!res)
23
23
  return;
24
- setField('firstName', res.firstName);
25
- setField('lastName', res.lastName);
26
- setField('emailAddress', res.emailAddress);
27
- setField('password', '');
28
- setField('roleIds', res.user.roles.map((r) => r.id));
24
+ base.setField('firstName', res.firstName);
25
+ base.setField('lastName', res.lastName);
26
+ base.setField('emailAddress', res.emailAddress);
27
+ base.setField('password', '');
28
+ base.setField('roleIds', res.user.roles.map((r) => r.id));
29
29
  })();
30
30
  }, []);
31
- return (_jsx("main", { className: "my-4", children: _jsxs("div", { className: "flex flex-col gap-3", children: [_jsx(CustomCard, { title: t('details.basic.title'), icon: _jsx(CardIcons.basic, {}), children: _jsxs("div", { className: "flex items-start gap-4", children: [_jsx(Input, { wrapperClassName: "basis-full md:basis-1/2 xl:basis-1/4", label: t('details.basic.firstName'), value: state.firstName?.value ?? undefined, onChange: (e) => setField('firstName', e.target.value), errors: state.firstName?.errors, required: true }), _jsx(Input, { wrapperClassName: "basis-full md:basis-1/2 xl:basis-1/4", label: t('details.basic.lastName'), value: state.lastName?.value ?? undefined, onChange: (e) => setField('lastName', e.target.value), errors: state.lastName?.errors, required: true }), _jsx(Input, { wrapperClassName: "basis-full md:basis-1/2 xl:basis-1/4", label: t('details.basic.emailAddress'), value: state.emailAddress?.value ?? undefined, onChange: (e) => setField('emailAddress', e.target.value), errors: state.emailAddress?.errors, required: true }), _jsx(Input, { wrapperClassName: "basis-full md:basis-1/2 xl:basis-1/4", label: t('details.basic.password'), value: state.password?.value ?? undefined, onChange: (e) => setField('password', e.target.value), errors: state.password?.errors, required: !editMode })] }) }), _jsx(DetailViewMarker, { position: 'admins-detail-view' }), _jsx(EntityCustomFields, { entityName: "administrator", id: id, hideButton: true, onChange: (customFields) => {
32
- setField('customFields', customFields);
33
- }, initialValues: entity && 'customFields' in entity ? { customFields: entity.customFields } : { customFields: {} } }), _jsx(RolesCard, { adminRoleIds: state.roleIds?.value ?? undefined, onRolesChange: (e) => setField('roleIds', e), errors: state.roleIds?.errors })] }) }));
31
+ return (_jsx("main", { className: "my-4", children: _jsxs("div", { className: "flex flex-col gap-3", children: [_jsx(CustomCard, { title: t('details.basic.title'), icon: _jsx(CardIcons.basic, {}), children: _jsxs("div", { className: "flex items-start gap-4", children: [_jsx(Input, { wrapperClassName: "basis-full md:basis-1/2 xl:basis-1/4", label: t('details.basic.firstName'), value: base.watch('firstName') ?? undefined, onChange: (e) => base.setField('firstName', e.target.value), errors: base.formState.errors?.firstName?.message
32
+ ? [base.formState.errors.firstName.message]
33
+ : undefined, required: true }), _jsx(Input, { wrapperClassName: "basis-full md:basis-1/2 xl:basis-1/4", label: t('details.basic.lastName'), value: base.watch('lastName') ?? undefined, onChange: (e) => base.setField('lastName', e.target.value), errors: base.formState.errors?.lastName?.message
34
+ ? [base.formState.errors.lastName.message]
35
+ : undefined, required: true }), _jsx(Input, { wrapperClassName: "basis-full md:basis-1/2 xl:basis-1/4", label: t('details.basic.emailAddress'), value: base.watch('emailAddress') ?? undefined, onChange: (e) => base.setField('emailAddress', e.target.value), errors: base.formState.errors?.emailAddress?.message
36
+ ? [base.formState.errors.emailAddress.message]
37
+ : undefined, required: true }), _jsx(Input, { wrapperClassName: "basis-full md:basis-1/2 xl:basis-1/4", label: t('details.basic.password'), value: base.watch('password') ?? undefined, onChange: (e) => base.setField('password', e.target.value), errors: base.formState.errors?.password?.message
38
+ ? [base.formState.errors.password.message]
39
+ : undefined, required: !editMode })] }) }), _jsx(DetailViewMarker, { position: 'admins-detail-view' }), _jsx(EntityCustomFields, { entityName: "administrator", id: id, hideButton: true, onChange: (customFields) => {
40
+ base.setField('customFields', customFields);
41
+ }, initialValues: entity && 'customFields' in entity ? { customFields: entity.customFields } : { customFields: {} } }), _jsx(RolesCard, { adminRoleIds: base.watch('roleIds') ?? undefined, onRolesChange: (e) => base.setField('roleIds', e), errors: base.formState.errors?.roleIds?.message ? [base.formState.errors.roleIds.message] : undefined })] }) }));
34
42
  };
@@ -31,21 +31,21 @@ export const ChannelsDetailPage = () => {
31
31
  const setSelectedChannel = useSettings((p) => p.setSelectedChannel);
32
32
  const setChannels = useServer((p) => p.setChannels);
33
33
  const onSubmitHandler = useCallback(async (data) => {
34
- if (!data.code?.validatedValue) {
34
+ if (!data.code) {
35
35
  throw new Error('Code is required.');
36
36
  }
37
37
  const inputData = {
38
- code: data.code.validatedValue,
39
- availableCurrencyCodes: data.availableCurrencyCodes?.validatedValue,
40
- availableLanguageCodes: data.availableLanguageCodes?.validatedValue,
41
- defaultCurrencyCode: data.defaultCurrencyCode?.validatedValue,
42
- defaultLanguageCode: data.defaultLanguageCode.validatedValue,
43
- defaultShippingZoneId: data.defaultShippingZoneId.validatedValue,
44
- defaultTaxZoneId: data.defaultTaxZoneId.validatedValue,
45
- pricesIncludeTax: data.pricesIncludeTax.value,
46
- sellerId: data.sellerId?.validatedValue,
47
- token: data.token.validatedValue,
48
- ...(data.customFields?.validatedValue ? { customFields: data.customFields?.validatedValue } : {}),
38
+ code: data.code,
39
+ availableCurrencyCodes: data.availableCurrencyCodes,
40
+ availableLanguageCodes: data.availableLanguageCodes,
41
+ defaultCurrencyCode: data.defaultCurrencyCode,
42
+ defaultLanguageCode: data.defaultLanguageCode,
43
+ defaultShippingZoneId: data.defaultShippingZoneId,
44
+ defaultTaxZoneId: data.defaultTaxZoneId,
45
+ pricesIncludeTax: data.pricesIncludeTax,
46
+ sellerId: data.sellerId,
47
+ token: data.token,
48
+ ...(data.customFields ? { customFields: data.customFields } : {}),
49
49
  };
50
50
  if (id) {
51
51
  const response = await update({ input: { id, ...inputData } });
@@ -9,22 +9,22 @@ export const ChannelDetailView = () => {
9
9
  const [sellersOptions, setSellersOptions] = useState();
10
10
  const availableLanguages = useSettings((p) => p.availableLanguages);
11
11
  const { form, fetchEntity, entity, id } = useDetailView('channels-detail-view', 'CreateChannelInput', 'code', 'availableCurrencyCodes', 'availableLanguageCodes', 'token', 'defaultCurrencyCode', 'defaultLanguageCode', 'defaultShippingZoneId', 'defaultTaxZoneId', 'sellerId', 'pricesIncludeTax', 'customFields');
12
- const { base: { setField, state }, } = form;
12
+ const { base } = form;
13
13
  useEffect(() => {
14
14
  (async () => {
15
15
  const resp = await fetchEntity();
16
16
  if (!resp)
17
17
  return;
18
- setField('code', resp.code);
19
- setField('availableCurrencyCodes', resp.availableCurrencyCodes);
20
- setField('availableLanguageCodes', resp.availableLanguageCodes);
21
- setField('token', resp.token);
22
- setField('defaultCurrencyCode', resp.defaultCurrencyCode);
23
- setField('defaultLanguageCode', resp.defaultLanguageCode);
24
- setField('defaultShippingZoneId', resp.defaultShippingZone?.id || '');
25
- setField('defaultTaxZoneId', resp.defaultTaxZone?.id || '');
26
- setField('sellerId', resp.seller?.id);
27
- setField('pricesIncludeTax', resp.pricesIncludeTax);
18
+ base.setField('code', resp.code);
19
+ base.setField('availableCurrencyCodes', resp.availableCurrencyCodes);
20
+ base.setField('availableLanguageCodes', resp.availableLanguageCodes);
21
+ base.setField('token', resp.token);
22
+ base.setField('defaultCurrencyCode', resp.defaultCurrencyCode);
23
+ base.setField('defaultLanguageCode', resp.defaultLanguageCode);
24
+ base.setField('defaultShippingZoneId', resp.defaultShippingZone?.id || '');
25
+ base.setField('defaultTaxZoneId', resp.defaultTaxZone?.id || '');
26
+ base.setField('sellerId', resp.seller?.id);
27
+ base.setField('pricesIncludeTax', resp.pricesIncludeTax);
28
28
  })();
29
29
  }, []);
30
30
  const fetchSellers = useCallback(async () => {
@@ -41,11 +41,19 @@ export const ChannelDetailView = () => {
41
41
  const currencyArray = Object.values(CurrencyCode);
42
42
  return currencyArray.map((l) => ({ label: l, value: l }));
43
43
  }, []);
44
- return (_jsx("main", { children: _jsxs("div", { className: "flex flex-col gap-3", children: [_jsx(CustomCard, { title: t('details.basic.title'), icon: _jsx(CardIcons.basic, {}), children: _jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "flex basis-full md:basis-1/2", children: _jsx(Input, { label: t('details.basic.code'), value: state.code?.value ?? undefined, onChange: (e) => setField('code', e.target.value), errors: state.code?.errors, required: true }) }), _jsx("div", { className: "flex basis-full md:basis-1/2", children: _jsx(Input, { label: t('details.basic.token'), value: state.token?.value ?? undefined, onChange: (e) => setField('token', e.target.value), errors: state.token?.errors, required: true }) })] }), _jsxs("div", { className: "flex flex-col gap-3", children: [_jsx("div", { className: "flex basis-full md:basis-1/3", children: _jsx(SimpleSelect, { label: t('details.basic.seller'), value: state?.sellerId?.value ?? undefined, onValueChange: (e) => setField('sellerId', e), options: sellersOptions }) }), _jsxs("div", { className: "flex basis-full flex-col md:basis-1/3", children: [_jsx(Label, { className: "mb-2", children: t('details.basic.languages') }), _jsx(MultipleSelector, { options: languageOptions, value: state?.availableLanguageCodes?.value?.map((l) => ({ label: l, value: l })), placeholder: t('details.basic.languagePlaceholder'), onChange: (options) => {
45
- setField('availableLanguageCodes', options.map((o) => o.value));
46
- }, hideClearAllButton: true })] }), _jsxs("div", { className: "flex basis-full flex-col md:basis-1/3", children: [_jsx(Label, { className: "mb-2", children: t('details.basic.currencies') }), _jsx(MultipleSelector, { options: currencyOptions, value: state?.availableCurrencyCodes?.value?.map((c) => ({ label: c, value: c })), placeholder: t('details.basic.currencyPlaceholder'), onChange: (options) => {
47
- setField('availableCurrencyCodes', options.map((o) => o.value));
44
+ const availableLanguageCodes = base.watch('availableLanguageCodes');
45
+ const availableCurrencyCodes = base.watch('availableCurrencyCodes');
46
+ return (_jsx("main", { children: _jsxs("div", { className: "flex flex-col gap-3", children: [_jsx(CustomCard, { title: t('details.basic.title'), icon: _jsx(CardIcons.basic, {}), children: _jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "flex basis-full md:basis-1/2", children: _jsx(Input, { label: t('details.basic.code'), value: base.watch('code') ?? undefined, onChange: (e) => base.setField('code', e.target.value), errors: base.formState.errors?.code?.message ? [base.formState.errors.code.message] : undefined, required: true }) }), _jsx("div", { className: "flex basis-full md:basis-1/2", children: _jsx(Input, { label: t('details.basic.token'), value: base.watch('token') ?? undefined, onChange: (e) => base.setField('token', e.target.value), errors: base.formState.errors?.token?.message ? [base.formState.errors.token.message] : undefined, required: true }) })] }), _jsxs("div", { className: "flex flex-col gap-3", children: [_jsx("div", { className: "flex basis-full md:basis-1/3", children: _jsx(SimpleSelect, { label: t('details.basic.seller'), value: base.watch('sellerId') ?? undefined, onValueChange: (e) => base.setField('sellerId', e), options: sellersOptions }) }), _jsxs("div", { className: "flex basis-full flex-col md:basis-1/3", children: [_jsx(Label, { className: "mb-2", children: t('details.basic.languages') }), _jsx(MultipleSelector, { options: languageOptions, value: availableLanguageCodes?.map((l) => ({ label: l, value: l })), placeholder: t('details.basic.languagePlaceholder'), onChange: (options) => {
47
+ base.setField('availableLanguageCodes', options.map((o) => o.value));
48
+ }, hideClearAllButton: true })] }), _jsxs("div", { className: "flex basis-full flex-col md:basis-1/3", children: [_jsx(Label, { className: "mb-2", children: t('details.basic.currencies') }), _jsx(MultipleSelector, { options: currencyOptions, value: availableCurrencyCodes?.map((c) => ({ label: c, value: c })), placeholder: t('details.basic.currencyPlaceholder'), onChange: (options) => {
49
+ base.setField('availableCurrencyCodes', options.map((o) => o.value));
48
50
  }, hideClearAllButton: true })] })] })] }) }), _jsx(DetailViewMarker, { position: 'channels-detail-view' }), _jsx(EntityCustomFields, { entityName: "channel", id: id, onChange: (customFields) => {
49
- setField('customFields', customFields);
50
- }, initialValues: entity && 'customFields' in entity ? { customFields: entity.customFields } : { customFields: {} }, hideButton: true }), _jsx(DefaultsCard, { availableLanguages: state.availableLanguageCodes?.value ?? undefined, availableCurrencies: state.availableCurrencyCodes?.value ?? undefined, onFieldChange: setField, defaultLanguage: state.defaultLanguageCode?.value ?? undefined, defaultCurrency: state.defaultCurrencyCode?.value ?? undefined, defaultTaxZone: state.defaultTaxZoneId?.value ?? undefined, defaultShippingZone: state.defaultShippingZoneId?.value ?? undefined, includeTax: state.pricesIncludeTax?.value ?? undefined, onIncludeTaxChange: (e) => setField('pricesIncludeTax', e), defaultLanguageErrors: state.defaultLanguageCode?.errors, defaultShippingZoneErrors: state.defaultShippingZoneId?.errors, defaultTaxZoneErrors: state.defaultTaxZoneId?.errors })] }) }));
51
+ base.setField('customFields', customFields);
52
+ }, initialValues: entity && 'customFields' in entity ? { customFields: entity.customFields } : { customFields: {} }, hideButton: true }), _jsx(DefaultsCard, { availableLanguages: availableLanguageCodes ?? undefined, availableCurrencies: availableCurrencyCodes ?? undefined, onFieldChange: base.setField, defaultLanguage: base.watch('defaultLanguageCode') ?? undefined, defaultCurrency: base.watch('defaultCurrencyCode') ?? undefined, defaultTaxZone: base.watch('defaultTaxZoneId') ?? undefined, defaultShippingZone: base.watch('defaultShippingZoneId') ?? undefined, includeTax: base.watch('pricesIncludeTax') ?? undefined, onIncludeTaxChange: (e) => base.setField('pricesIncludeTax', e), defaultLanguageErrors: base.formState.errors?.defaultLanguageCode?.message
53
+ ? [base.formState.errors.defaultLanguageCode.message]
54
+ : undefined, defaultShippingZoneErrors: base.formState.errors?.defaultShippingZoneId?.message
55
+ ? [base.formState.errors.defaultShippingZoneId.message]
56
+ : undefined, defaultTaxZoneErrors: base.formState.errors?.defaultTaxZoneId?.message
57
+ ? [base.formState.errors.defaultTaxZoneId.message]
58
+ : undefined })] }) }));
51
59
  };
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useCallback } from 'react';
3
3
  import { useParams } from 'react-router';
4
- import { useValidators, DetailView, createDeenruvForm, getMutation, useMutation, } from '@deenruv/react-ui-devkit';
4
+ import { useValidators, DetailView, createDeenruvForm, getMutation, useMutation } from '@deenruv/react-ui-devkit';
5
5
  import { CollectionsDetailView } from "./_components/CollectionDetailView.js";
6
6
  const CreateCollectionMutation = getMutation('createCollection');
7
7
  const EditCollectionMutation = getMutation('updateCollection');
@@ -13,13 +13,14 @@ export const CollectionsDetailPage = () => {
13
13
  const [remove] = useMutation(DeleteCollectionMutation);
14
14
  const { configurableOperationArrayValidator, translationsValidator } = useValidators();
15
15
  const onSubmitHandler = useCallback((data) => {
16
+ const translations = data.translations;
16
17
  const inputData = {
17
- assetIds: data.assetIds?.validatedValue,
18
- featuredAssetId: data.featuredAssetId?.validatedValue,
19
- isPrivate: data.isPrivate?.validatedValue,
20
- inheritFilters: data.inheritFilters?.validatedValue,
21
- filters: data.filters.validatedValue,
22
- translations: data.translations.validatedValue.map((t) => ({
18
+ assetIds: data.assetIds,
19
+ featuredAssetId: data.featuredAssetId,
20
+ isPrivate: data.isPrivate,
21
+ inheritFilters: data.inheritFilters,
22
+ filters: data.filters,
23
+ translations: translations.map((t) => ({
23
24
  description: t.description || '',
24
25
  name: t.name || '',
25
26
  languageCode: t.languageCode,
@@ -32,8 +32,7 @@ export const CollectionsListPage = () => {
32
32
  cell: ({ row, getValue }) => {
33
33
  const value = getValue();
34
34
  const isExpanded = row.getIsExpanded();
35
- return (_jsx("div", { style: { paddingLeft: `${row.depth * 2}rem` }, children: _jsx(ListBadge, { paddingLeft: `${row.depth * 2}rem`,
36
- onClick: row.getToggleExpandedHandler(),
35
+ return (_jsx("div", { style: { paddingLeft: `${row.depth * 2}rem` }, children: _jsx(ListBadge, { onClick: row.getToggleExpandedHandler(),
37
36
  style: { cursor: 'pointer' }, children: isExpanded ? (_jsx(FolderOpen, { size: 16 })) : value?.length > 0 ? (_jsx(Folder, { size: 16 })) : (_jsx(XSquareIcon, { size: 16 })) }) }));
38
37
  },
39
38
  },
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useCallback, useEffect } from 'react';
3
- import { EntityCustomFields, Input, Label, Switch, CustomCard, CardIcons, useDetailView, useSettings, RichTextEditor, useTranslation, setInArrayBy, EntityChannelManager, Routes, } from '@deenruv/react-ui-devkit';
2
+ import { useCallback, useEffect, useRef } from 'react';
3
+ import { EntityCustomFields, Input, Label, Switch, CustomCard, CardIcons, useDetailView, useSettings, RichTextEditor, useTranslation, setInArrayBy, EntityChannelManager, Routes, normalizeString, } from '@deenruv/react-ui-devkit';
4
4
  import { FiltersCard } from "./FiltersCard";
5
5
  import { ContentsCard } from "./ContentsCard";
6
6
  import { AssetsCard } from "../../products/_components/AssetsCard.js";
@@ -11,41 +11,84 @@ export const CollectionsDetailView = () => {
11
11
  const selectedChannel = useSettings((p) => p.selectedChannel);
12
12
  const navigate = useNavigate();
13
13
  const { form, fetchEntity, entity, id } = useDetailView('collections-detail-view', 'CreateCollectionInput', 'translations', 'assetIds', 'featuredAssetId', 'isPrivate', 'inheritFilters', 'filters', 'customFields');
14
- const { base: { setField, state }, } = form;
14
+ const { base } = form;
15
+ // --- Slug auto-generation ---
16
+ // Track whether the user has manually edited the slug for the current language.
17
+ // Using a ref avoids extra re-renders; keyed per-language so switching tabs resets.
18
+ const slugManuallyEditedRef = useRef({});
19
+ // Reset manual-edit flag when switching to a language whose slug is still empty
20
+ // (so auto-gen kicks in for new translations).
21
+ useEffect(() => {
22
+ if (!contentLng)
23
+ return;
24
+ const current = slugManuallyEditedRef.current[contentLng];
25
+ if (current === undefined) {
26
+ slugManuallyEditedRef.current[contentLng] = false;
27
+ }
28
+ }, [contentLng]);
15
29
  useEffect(() => {
16
30
  (async () => {
17
31
  const resp = await fetchEntity();
18
32
  if (!resp)
19
33
  return;
20
- setField('translations', resp.translations);
21
- setField('assetIds', resp.assets.map((a) => a.id));
22
- setField('featuredAssetId', resp.featuredAsset?.id);
23
- setField('isPrivate', resp.isPrivate);
24
- setField('inheritFilters', resp.inheritFilters);
25
- setField('filters', resp.filters.map((f) => ({ code: f.code, arguments: f.args })));
34
+ base.setField('translations', resp.translations);
35
+ base.setField('assetIds', resp.assets.map((a) => a.id));
36
+ base.setField('featuredAssetId', resp.featuredAsset?.id);
37
+ base.setField('isPrivate', resp.isPrivate);
38
+ base.setField('inheritFilters', resp.inheritFilters);
39
+ base.setField('filters', resp.filters.map((f) => ({ code: f.code, arguments: f.args })));
26
40
  })();
27
41
  }, [contentLng, selectedChannel?.id]);
28
- const translations = state?.translations?.value || [];
42
+ const translations = base.watch('translations') || [];
29
43
  const currentTranslationValue = translations.find((v) => v.languageCode === contentLng);
30
44
  const handleAddAsset = useCallback((newId) => {
31
45
  if (!newId)
32
46
  return;
33
- const currentIds = state.assetIds?.value || [];
34
- setField('assetIds', [...currentIds, newId]);
35
- }, [state.assetIds?.value, setField]);
47
+ const currentIds = base.watch('assetIds') || [];
48
+ base.setField('assetIds', [...currentIds, newId]);
49
+ }, [base, base.setField]);
36
50
  const setTranslationField = useCallback((field, e) => {
37
- if (!currentTranslationValue)
38
- return;
39
- setField('translations', setInArrayBy(translations, (t) => t.languageCode !== contentLng, {
40
- ...currentTranslationValue,
51
+ // On create the translations array is initially empty, so build a
52
+ // fallback translation object so the field update is never lost.
53
+ const baseTranslation = currentTranslationValue ?? {
54
+ languageCode: contentLng,
55
+ name: '',
56
+ slug: '',
57
+ description: '',
58
+ };
59
+ const updatedTranslation = {
60
+ ...baseTranslation,
41
61
  [field]: e,
42
62
  languageCode: contentLng,
43
- }));
44
- }, [contentLng, translations]);
45
- return (_jsx("main", { children: _jsxs("div", { className: "flex flex-col gap-3", children: [_jsx(CustomCard, { title: t('details.basic.title'), icon: _jsx(CardIcons.basic, {}), color: "blue", children: _jsxs("div", { className: "flex flex-wrap items-start gap-4 p-0 pt-4", children: [_jsxs("div", { className: "flex w-full flex-wrap items-start gap-4 p-0 pt-4 xl:flex-nowrap", children: [_jsx("div", { className: "flex basis-full md:basis-1/3", children: _jsx(Input, { label: t('details.basic.name'), value: currentTranslationValue?.name ?? undefined, onChange: (e) => setTranslationField('name', e.target.value), errors: state.translations?.errors, required: true }) }), _jsx("div", { className: "flex basis-full md:basis-1/3", children: _jsx(Input, { label: t('details.basic.slug'), value: currentTranslationValue?.slug ?? undefined, onChange: (e) => setTranslationField('slug', e.target.value), required: true }) }), _jsxs("div", { className: "mt-7 flex basis-full items-center gap-3 md:basis-1/3", children: [_jsx(Switch, { checked: state.isPrivate?.value ?? undefined, onCheckedChange: (e) => setField('isPrivate', e) }), _jsx(Label, { children: t('details.basic.isPrivate') })] })] }), _jsxs("div", { className: "flex basis-full flex-col", children: [_jsx(Label, { className: "mb-2", children: t('details.basic.description') }), _jsx(RichTextEditor, { content: currentTranslationValue?.description ?? undefined, onContentChanged: (e) => setTranslationField('description', e) })] })] }) }), _jsx(AssetsCard, { onAddAsset: handleAddAsset, featuredAssetId: state.featuredAssetId?.value ?? undefined, assetsIds: state.assetIds?.value ?? undefined, onFeaturedAssetChange: (id) => setField('featuredAssetId', id), onAssetsChange: (ids) => setField('assetIds', ids) }), _jsx(EntityChannelManager, { entity: "collection", entityId: id, entityChannels: [{ id: selectedChannel?.id, code: selectedChannel?.code }], onRemoveSuccess: () => navigate(Routes.collections.list) }), _jsx(FiltersCard, { currentFiltersValue: state.filters?.value ?? undefined, onFiltersValueChange: (filters) => setField('filters', filters ?? []), inheritValue: state.inheritFilters?.value ?? undefined, onInheritChange: (e) => setField('inheritFilters', e), errors: state.filters?.errors }), _jsx(EntityCustomFields, { entityName: "collection", id: id, hideButton: true, onChange: (customFields, translations) => {
46
- setField('customFields', customFields);
63
+ };
64
+ // Auto-generate slug when name changes
65
+ if (field === 'name') {
66
+ const isCreate = id === undefined;
67
+ const existingSlug = baseTranslation.slug;
68
+ const slugWasManuallyEdited = slugManuallyEditedRef.current[contentLng ?? ''];
69
+ // Auto-fill slug when:
70
+ // 1. Create mode: always, unless user manually edited slug
71
+ // 2. Edit mode: only if the existing slug is empty/missing
72
+ if (!slugWasManuallyEdited && (isCreate || !existingSlug)) {
73
+ updatedTranslation.slug = normalizeString(e, '-');
74
+ }
75
+ }
76
+ base.setField('translations', setInArrayBy(translations, (t) => t.languageCode === contentLng, updatedTranslation));
77
+ }, [contentLng, translations, currentTranslationValue, id]);
78
+ const handleSlugManualEdit = useCallback(() => {
79
+ if (contentLng) {
80
+ slugManuallyEditedRef.current[contentLng] = true;
81
+ }
82
+ }, [contentLng]);
83
+ return (_jsx("main", { children: _jsxs("div", { className: "flex flex-col gap-3", children: [_jsx(CustomCard, { title: t('details.basic.title'), icon: _jsx(CardIcons.basic, {}), color: "blue", children: _jsxs("div", { className: "flex flex-wrap items-start gap-4 p-0 pt-4", children: [_jsxs("div", { className: "flex w-full flex-wrap items-start gap-4 p-0 pt-4 xl:flex-nowrap", children: [_jsx("div", { className: "flex basis-full md:basis-1/3", children: _jsx(Input, { label: t('details.basic.name'), value: currentTranslationValue?.name ?? '', onChange: (e) => setTranslationField('name', e.target.value), errors: base.formState.errors?.translations?.message
84
+ ? [base.formState.errors.translations.message]
85
+ : undefined, required: true }) }), _jsx("div", { className: "flex basis-full md:basis-1/3", children: _jsx(Input, { label: t('details.basic.slug'), value: currentTranslationValue?.slug ?? '', onChange: (e) => {
86
+ handleSlugManualEdit();
87
+ setTranslationField('slug', e.target.value);
88
+ }, required: true }) }), _jsxs("div", { className: "mt-7 flex basis-full items-center gap-3 md:basis-1/3", children: [_jsx(Switch, { checked: base.watch('isPrivate') ?? false, onCheckedChange: (e) => base.setField('isPrivate', e) }), _jsx(Label, { children: t('details.basic.isPrivate') })] })] }), _jsxs("div", { className: "flex basis-full flex-col", children: [_jsx(Label, { className: "mb-2", children: t('details.basic.description') }), _jsx(RichTextEditor, { content: currentTranslationValue?.description ?? '', onContentChanged: (e) => setTranslationField('description', e) })] })] }) }), _jsx(AssetsCard, { onAddAsset: handleAddAsset, featuredAssetId: base.watch('featuredAssetId') ?? undefined, assetsIds: base.watch('assetIds') ?? undefined, onFeaturedAssetChange: (id) => base.setField('featuredAssetId', id), onAssetsChange: (ids) => base.setField('assetIds', ids) }), _jsx(EntityChannelManager, { entity: "collection", entityId: id, entityChannels: [{ id: selectedChannel?.id, code: selectedChannel?.code }], onRemoveSuccess: () => navigate(Routes.collections.list) }), _jsx(FiltersCard, { currentFiltersValue: base.watch('filters') ?? undefined, onFiltersValueChange: (filters) => base.setField('filters', filters ?? []), inheritValue: base.watch('inheritFilters') ?? false, onInheritChange: (e) => base.setField('inheritFilters', e), errors: base.formState.errors?.filters?.message ? [base.formState.errors.filters.message] : undefined }), _jsx(EntityCustomFields, { entityName: "collection", id: id, hideButton: true, onChange: (customFields, translations) => {
89
+ base.setField('customFields', customFields);
47
90
  if (translations)
48
- setField('translations', translations);
91
+ base.setField('translations', translations);
49
92
  }, initialValues: entity && 'customFields' in entity
50
93
  ? { customFields: entity.customFields, translations: entity.translations }
51
94
  : { customFields: {} } }), id && _jsx(ContentsCard, { collectionId: id })] }) }));