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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { CanLeaveRouteDialog } from "../components";
3
3
  import { serverConfigSelector, configurableOperationDefinitionSelector, countrySelector, apiClient, useOrder, useNotifications, DEFAULT_CHANNEL_CODE, useTranslation, capitalizeFirstLetter, } from '@deenruv/react-ui-devkit';
4
- import { useCallback, useEffect } from 'react';
4
+ import { useCallback, useEffect, useRef } from 'react';
5
5
  import { Outlet, useLocation, useSearchParams } from 'react-router';
6
6
  import { toast } from 'sonner';
7
7
  import { activeAdministratorSelector, paymentMethodsSelector, useServer, useSettings, } from '@deenruv/react-ui-devkit';
@@ -45,8 +45,8 @@ const getAllPaymentMethods = async () => {
45
45
  export const Root = ({ allPaths }) => {
46
46
  const isLocalhost = window.location.hostname === 'localhost';
47
47
  const { t } = useTranslation('common');
48
- const setActiveAdministrator = useServer((p) => p.setActiveAdministrator);
49
- const setUserPermissions = useServer((p) => p.setUserPermissions);
48
+ const clearAdministratorAccess = useServer((p) => p.clearAdministratorAccess);
49
+ const setAdministratorAccess = useServer((p) => p.setAdministratorAccess);
50
50
  const setServerConfig = useServer((p) => p.setServerConfig);
51
51
  const setCountries = useServer((p) => p.setCountries);
52
52
  const setFulfillmentHandlers = useServer((p) => p.setFulfillmentHandlers);
@@ -68,6 +68,7 @@ export const Root = ({ allPaths }) => {
68
68
  const channel = useSettings((p) => p.selectedChannel);
69
69
  const location = useLocation();
70
70
  const [searchParams] = useSearchParams();
71
+ const hasInitializedAdministratorAccess = useRef(false);
71
72
  const managePageTitle = useCallback(() => {
72
73
  const pathname = location.pathname;
73
74
  const isDeenruvPath = allPaths.some((path) => pathname.startsWith(path));
@@ -143,17 +144,22 @@ export const Root = ({ allPaths }) => {
143
144
  };
144
145
  }, [notifications.map((n) => n.id).join(','), loaded]);
145
146
  useEffect(() => {
147
+ if (hasInitializedAdministratorAccess.current)
148
+ return;
149
+ hasInitializedAdministratorAccess.current = true;
150
+ clearAdministratorAccess();
146
151
  const init = async () => {
147
152
  const activeAdministratorResponse = await apiClient('query')({
148
153
  activeAdministrator: activeAdministratorSelector,
149
154
  });
150
155
  const roles = activeAdministratorResponse.activeAdministrator?.user.roles || [];
151
156
  if (!activeAdministratorResponse.activeAdministrator) {
157
+ clearAdministratorAccess();
152
158
  toast.error(t('setup.failedAdmin'));
159
+ return;
153
160
  }
154
161
  else {
155
- setActiveAdministrator(activeAdministratorResponse.activeAdministrator);
156
- setUserPermissions(Array.from(new Set(roles.flatMap((role) => role.permissions))));
162
+ setAdministratorAccess(activeAdministratorResponse.activeAdministrator, Array.from(new Set(roles.flatMap((role) => role.permissions))));
157
163
  if ([Permission.ReadChannel].some((p) => roles.some((r) => r.permissions.includes(p)))) {
158
164
  const { channels: { items: allChannels = [] }, } = await apiClient('query')({
159
165
  channels: [
@@ -200,12 +206,20 @@ export const Root = ({ allPaths }) => {
200
206
  if (window?.__DEENRUV_SETTINGS__?.ui?.defaultTranslationLanguageCode) {
201
207
  // setTranslationLanguage(window?.__DEENRUV_SETTINGS__?.ui?.defaultTranslationLanguageCode);
202
208
  }
203
- const { globalSettings } = await apiClient('query')({
204
- globalSettings: { serverConfig: serverConfigSelector, availableLanguages: true },
205
- });
206
- fetchPendingJobs();
207
- initializeOrderCustomFields(globalSettings.serverConfig);
208
- setLoaded(true);
209
+ try {
210
+ const { globalSettings } = await apiClient('query')({
211
+ globalSettings: { serverConfig: serverConfigSelector, availableLanguages: true },
212
+ });
213
+ fetchPendingJobs();
214
+ initializeOrderCustomFields(globalSettings.serverConfig);
215
+ setLoaded(true);
216
+ setServerConfig(globalSettings.serverConfig);
217
+ setAvailableLanguages(globalSettings.availableLanguages);
218
+ }
219
+ catch {
220
+ toast.error(t('setup.failedServer'));
221
+ setLoaded(true);
222
+ }
209
223
  if ([Permission.ReadCountry, Permission.ReadPaymentMethod, Permission.ReadOrder].some((p) => roles.some((r) => r.permissions.includes(p)))) {
210
224
  const [countriesResponse, paymentsResponse, fulfillmentsResponse] = await Promise.allSettled([
211
225
  getAllPaginatedCountries(),
@@ -216,8 +230,6 @@ export const Root = ({ allPaths }) => {
216
230
  toast.error(t('setup.failedServer'));
217
231
  }
218
232
  else {
219
- setServerConfig(globalSettings.serverConfig);
220
- setAvailableLanguages(globalSettings.availableLanguages);
221
233
  // const socket = serverConfigResponse.value.globalSettings.serverConfig.plugins?.find(
222
234
  // (plugin) => plugin.name === 'AexolAdminsPlugin',
223
235
  // );
@@ -243,9 +255,13 @@ export const Root = ({ allPaths }) => {
243
255
  }
244
256
  }
245
257
  };
246
- fetchGraphQLSchema().then(async (schema) => {
258
+ fetchGraphQLSchema()
259
+ .then(async (schema) => {
247
260
  window.__DEENRUV_SCHEMA__ = schema;
248
261
  await init();
262
+ })
263
+ .catch(() => {
264
+ clearAdministratorAccess();
249
265
  });
250
266
  }, []);
251
267
  return (_jsx(_Fragment, { children: _jsxs("div", { className: "flex max-h-screen w-full max-w-full overflow-hidden bg-background text-foreground antialiased", children: [_jsx(Menu, { children: loaded ? _jsx(Outlet, {}) : _jsx(ContentAreaSkeleton, {}) }), loaded && (_jsxs(_Fragment, { children: [_jsx(GlobalSearch, {}), _jsx(CanLeaveRouteDialog, {}), isLocalhost ? _jsx(DeenruvDeveloperIndicator, {}) : null] }))] }) }));
@@ -1,6 +1,8 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { Routes, apiClient, DetailList, deepMerge, ListBadge, ListLocations, useTranslation, TableLabel, } from '@deenruv/react-ui-devkit';
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Button, Routes, apiClient, DetailList, deepMerge, ListBadge, ListLocations, useTranslation, TableLabel, useServer, } from '@deenruv/react-ui-devkit';
3
3
  import { Permission, SortOrder } from '@deenruv/admin-types';
4
+ import { UserPlus } from 'lucide-react';
5
+ import { useNavigate } from 'react-router';
4
6
  const tableId = 'admins-list-view';
5
7
  const { selector } = ListLocations[tableId];
6
8
  const fetch = async ({ page, perPage, filter, filterOperator, sort }, additionalSelector) => {
@@ -40,6 +42,9 @@ const onRemove = async (items) => {
40
42
  };
41
43
  export const AdminsListPage = () => {
42
44
  const { t } = useTranslation('admins');
45
+ const navigate = useNavigate();
46
+ const userPermissions = useServer(({ userPermissions }) => userPermissions);
47
+ const canCreateAdministrators = userPermissions.includes(Permission.CreateAdministrator);
43
48
  return (_jsx(DetailList, { filterFields: [
44
49
  { key: 'firstName', operator: 'StringOperators' },
45
50
  { key: 'emailAddress', operator: 'StringOperators' },
@@ -52,5 +57,5 @@ export const AdminsListPage = () => {
52
57
  header: () => _jsx(TableLabel, { children: t('table.role') }),
53
58
  cell: ({ row }) => (_jsx("div", { className: "flex gap-1", children: row.original.user.roles.map((r) => (_jsx(ListBadge, { children: r.description }, r.description))) })),
54
59
  },
55
- ], entityName: 'Administrator', route: Routes['admins'], tableId: tableId, fetch: fetch, onRemove: onRemove, createPermissions: [Permission.CreateAdministrator], deletePermissions: [Permission.DeleteAdministrator] }));
60
+ ], entityName: 'Administrator', route: Routes['admins'], tableId: tableId, fetch: fetch, onRemove: onRemove, createPermissions: [Permission.CreateAdministrator], deletePermissions: [Permission.DeleteAdministrator], additionalButtons: canCreateAdministrators ? (_jsxs(Button, { variant: "outline", className: "flex items-center gap-2", onClick: () => navigate(Routes.admins.provision), children: [_jsx(UserPlus, { size: 16 }), t('provision.action')] })) : null }));
56
61
  };
@@ -0,0 +1,173 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useState } from 'react';
3
+ import { useNavigate } from 'react-router';
4
+ import { ArrowLeft, BadgeCheck, ShieldCheck, UserPlus } from 'lucide-react';
5
+ import { toast } from 'sonner';
6
+ import { apiClient, Badge, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Checkbox, DEFAULT_CHANNEL_CODE, Input, Label, MultipleSelector, PageBlock, Routes, Separator, useServer, useTranslation, } from '@deenruv/react-ui-devkit';
7
+ import { PermissionsTable } from "../roles/_components/PermissionsTable";
8
+ import { getSelectedAssignablePermissions, normalizeRoleCode, SHOP_MANAGER_ROLE_PERMISSIONS, validateAdminProvisionForm, } from './Provision.logic';
9
+ const roleProvisionSelector = {
10
+ id: true,
11
+ code: true,
12
+ description: true,
13
+ permissions: true,
14
+ channels: {
15
+ id: true,
16
+ code: true,
17
+ },
18
+ };
19
+ const findRoleByCode = async (code) => {
20
+ const response = await apiClient('query')({
21
+ roles: [
22
+ {
23
+ options: {
24
+ take: 1,
25
+ filter: {
26
+ code: {
27
+ eq: code,
28
+ },
29
+ },
30
+ },
31
+ },
32
+ { items: roleProvisionSelector },
33
+ ],
34
+ });
35
+ return response.roles.items[0];
36
+ };
37
+ export const AdminsProvisionPage = () => {
38
+ const { t } = useTranslation('admins');
39
+ const { t: tRoles } = useTranslation('roles');
40
+ const navigate = useNavigate();
41
+ const { channels, serverConfig } = useServer();
42
+ const [firstName, setFirstName] = useState('');
43
+ const [lastName, setLastName] = useState('');
44
+ const [emailAddress, setEmailAddress] = useState('');
45
+ const [password, setPassword] = useState('');
46
+ const [roleCode, setRoleCode] = useState('shop-manager');
47
+ const [roleDescription, setRoleDescription] = useState(t('provision.presets.shopManager.roleDescription'));
48
+ const [channelIds, setChannelIds] = useState([]);
49
+ const [permissions, setPermissions] = useState(SHOP_MANAGER_ROLE_PERMISSIONS);
50
+ const [reuseExistingRole, setReuseExistingRole] = useState(false);
51
+ const [errors, setErrors] = useState({});
52
+ const [submitting, setSubmitting] = useState(false);
53
+ const channelOptions = useMemo(() => channels.map((channel) => ({
54
+ value: channel.id,
55
+ label: channel.code === DEFAULT_CHANNEL_CODE ? tRoles('defaultChannel') : channel.code,
56
+ })), [channels, tRoles]);
57
+ const currentChannelOptions = useMemo(() => channelIds.map((id) => channelOptions.find((option) => option.value === id) || { value: id, label: id }), [channelIds, channelOptions]);
58
+ const assignablePermissions = useMemo(() => {
59
+ if (!serverConfig?.permissions)
60
+ return undefined;
61
+ return new Set(serverConfig.permissions
62
+ .filter((permission) => permission.assignable)
63
+ .map((permission) => permission.name));
64
+ }, [serverConfig]);
65
+ const selectedAssignablePermissions = useMemo(() => getSelectedAssignablePermissions(permissions, assignablePermissions), [assignablePermissions, permissions]);
66
+ useEffect(() => {
67
+ if (!channelIds.length && channels.length) {
68
+ setChannelIds(channels.map((channel) => channel.id));
69
+ }
70
+ }, [channelIds.length, channels]);
71
+ useEffect(() => {
72
+ setRoleDescription(t('provision.presets.shopManager.roleDescription'));
73
+ }, [t]);
74
+ const applyShopManagerPreset = () => {
75
+ setRoleCode('shop-manager');
76
+ setRoleDescription(t('provision.presets.shopManager.roleDescription'));
77
+ setPermissions(SHOP_MANAGER_ROLE_PERMISSIONS);
78
+ };
79
+ const validateForm = () => {
80
+ const result = validateAdminProvisionForm({
81
+ firstName,
82
+ lastName,
83
+ emailAddress,
84
+ password,
85
+ roleCode,
86
+ channelIds,
87
+ permissions,
88
+ reuseExistingRole,
89
+ }, {
90
+ firstNameRequired: t('validation.firstNameRequired'),
91
+ lastNameRequired: t('validation.lastNameRequired'),
92
+ emailRequired: t('provision.validation.emailRequired'),
93
+ emailInvalid: t('provision.validation.emailInvalid'),
94
+ passwordRequired: t('validation.passwordRequired'),
95
+ roleCodeRequired: t('provision.validation.roleCodeRequired'),
96
+ channelsRequired: t('provision.validation.channelsRequired'),
97
+ permissionsRequired: t('provision.validation.permissionsRequired'),
98
+ }, assignablePermissions);
99
+ setErrors(result.errors);
100
+ return result.valid;
101
+ };
102
+ const createRole = async () => {
103
+ const response = await apiClient('mutation')({
104
+ createRole: [
105
+ {
106
+ input: {
107
+ code: normalizeRoleCode(roleCode),
108
+ description: roleDescription.trim() || normalizeRoleCode(roleCode),
109
+ channelIds,
110
+ permissions: selectedAssignablePermissions,
111
+ },
112
+ },
113
+ roleProvisionSelector,
114
+ ],
115
+ });
116
+ return response.createRole;
117
+ };
118
+ const createAdministrator = async (roleId) => {
119
+ const response = await apiClient('mutation')({
120
+ createAdministrator: [
121
+ {
122
+ input: {
123
+ firstName: firstName.trim(),
124
+ lastName: lastName.trim(),
125
+ emailAddress: emailAddress.trim(),
126
+ password,
127
+ roleIds: [roleId],
128
+ },
129
+ },
130
+ {
131
+ id: true,
132
+ emailAddress: true,
133
+ },
134
+ ],
135
+ });
136
+ return response.createAdministrator;
137
+ };
138
+ const handleSubmit = async () => {
139
+ if (!validateForm())
140
+ return;
141
+ setSubmitting(true);
142
+ const normalizedRoleCode = normalizeRoleCode(roleCode);
143
+ try {
144
+ const existingRole = await findRoleByCode(normalizedRoleCode);
145
+ let roleId = existingRole?.id;
146
+ if (reuseExistingRole) {
147
+ if (!roleId) {
148
+ setErrors({ roleCode: t('provision.validation.roleNotFound') });
149
+ toast.error(t('provision.toasts.roleNotFound'));
150
+ return;
151
+ }
152
+ }
153
+ else {
154
+ if (existingRole) {
155
+ setErrors({ roleCode: t('provision.validation.roleCodeExists') });
156
+ toast.error(t('provision.toasts.roleCodeExists'));
157
+ return;
158
+ }
159
+ roleId = (await createRole()).id;
160
+ }
161
+ const administrator = await createAdministrator(roleId);
162
+ toast.success(t('provision.toasts.success'));
163
+ navigate(Routes.admins.to(administrator.id));
164
+ }
165
+ catch (error) {
166
+ toast.error(error instanceof Error ? error.message : t('provision.toasts.error'));
167
+ }
168
+ finally {
169
+ setSubmitting(false);
170
+ }
171
+ };
172
+ return (_jsx(PageBlock, { children: _jsxs("div", { className: "flex flex-col gap-6", children: [_jsxs("div", { className: "flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between", children: [_jsxs("div", { className: "flex items-start gap-3", children: [_jsx(Button, { variant: "secondary", size: "icon", className: "mt-1", onClick: () => navigate(Routes.admins.list), children: _jsx(ArrowLeft, { className: "size-4" }) }), _jsxs("div", { children: [_jsx("h1", { className: "text-3xl font-bold tracking-tight", children: t('provision.title') }), _jsx("p", { className: "mt-1 max-w-3xl text-sm text-muted-foreground", children: t('provision.description') })] })] }), _jsxs(Button, { className: "gap-2", disabled: submitting, onClick: handleSubmit, children: [_jsx(UserPlus, { className: "size-4" }), submitting ? t('provision.submitting') : t('provision.submit')] })] }), _jsxs("div", { className: "grid gap-4 xl:grid-cols-[minmax(0,1fr)_380px]", children: [_jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: t('provision.admin.title') }), _jsx(CardDescription, { children: t('provision.admin.description') })] }), _jsx(CardContent, { children: _jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [_jsx(Input, { label: t('details.basic.firstName'), value: firstName, onChange: (event) => setFirstName(event.target.value), errors: errors.firstName ? [errors.firstName] : undefined, required: true }), _jsx(Input, { label: t('details.basic.lastName'), value: lastName, onChange: (event) => setLastName(event.target.value), errors: errors.lastName ? [errors.lastName] : undefined, required: true }), _jsx(Input, { label: t('details.basic.emailAddress'), type: "email", value: emailAddress, onChange: (event) => setEmailAddress(event.target.value), errors: errors.emailAddress ? [errors.emailAddress] : undefined, required: true }), _jsx(Input, { label: t('details.basic.password'), type: "password", value: password, onChange: (event) => setPassword(event.target.value), errors: errors.password ? [errors.password] : undefined, required: true })] }) })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: t('provision.role.title') }), _jsx(CardDescription, { children: t('provision.role.description') })] }), _jsxs(CardContent, { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [_jsx(Input, { label: t('provision.role.code'), value: roleCode, onChange: (event) => setRoleCode(event.target.value), onBlur: () => setRoleCode(normalizeRoleCode(roleCode)), errors: errors.roleCode ? [errors.roleCode] : undefined, required: true }), _jsx(Input, { label: t('provision.role.descriptionLabel'), value: roleDescription, onChange: (event) => setRoleDescription(event.target.value), required: !reuseExistingRole, disabled: reuseExistingRole })] }), _jsxs("label", { className: "flex items-start gap-3 border border-border/70 bg-muted/30 p-3 text-sm", children: [_jsx(Checkbox, { checked: reuseExistingRole, onCheckedChange: (checked) => setReuseExistingRole(checked === true) }), _jsxs("span", { className: "flex flex-col gap-1", children: [_jsx("span", { className: "font-medium", children: t('provision.role.reuseExisting') }), _jsx("span", { className: "text-muted-foreground", children: t('provision.role.reuseExistingHint') })] })] }), !reuseExistingRole && (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t('provision.role.channels') }), _jsx(MultipleSelector, { options: channelOptions, value: currentChannelOptions, placeholder: t('provision.role.channelsPlaceholder'), onChange: (options) => setChannelIds(options.map((option) => option.value)), hideClearAllButton: true }), errors.channelIds && _jsx("p", { className: "text-xs text-destructive", children: errors.channelIds })] }))] })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: t('provision.permissions.title') }), _jsx(CardDescription, { children: t('provision.permissions.description') })] }), _jsx(CardContent, { children: reuseExistingRole ? (_jsx("div", { className: "border border-dashed border-border p-4 text-sm text-muted-foreground", children: t('provision.permissions.reuseExisting') })) : (_jsxs("div", { className: "flex flex-col gap-3", children: [errors.permissions && _jsx("p", { className: "text-xs text-destructive", children: errors.permissions }), _jsx(PermissionsTable, { currentPermissions: selectedAssignablePermissions, onPermissionsChange: setPermissions })] })) })] })] }), _jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsxs(CardTitle, { className: "flex items-center gap-2", children: [_jsx(ShieldCheck, { className: "size-5 text-primary" }), t('provision.presets.title')] }), _jsx(CardDescription, { children: t('provision.presets.description') })] }), _jsxs(CardContent, { className: "flex flex-col gap-3", children: [_jsx("button", { type: "button", className: "border border-primary/40 bg-primary/10 p-4 text-left transition-colors hover:bg-primary/15", onClick: applyShopManagerPreset, children: _jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsx("p", { className: "font-medium", children: t('provision.presets.shopManager.title') }), _jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: t('provision.presets.shopManager.description') })] }), _jsx(Badge, { variant: "secondary", children: t('provision.presets.defaultBadge') })] }) }), _jsx(Separator, {}), _jsxs("div", { className: "flex items-start gap-3 text-sm", children: [_jsx(BadgeCheck, { className: "mt-0.5 size-4 text-green-600" }), _jsx("p", { className: "text-muted-foreground", children: t('provision.presets.backendNote') })] })] })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: t('provision.summary.title') }), _jsx(CardDescription, { children: t('provision.summary.description') })] }), _jsxs(CardContent, { className: "flex flex-col gap-3 text-sm", children: [_jsxs("div", { className: "flex items-center justify-between gap-4", children: [_jsx("span", { className: "text-muted-foreground", children: t('provision.summary.role') }), _jsx(Badge, { variant: "outline", children: normalizeRoleCode(roleCode) || '-' })] }), _jsxs("div", { className: "flex items-center justify-between gap-4", children: [_jsx("span", { className: "text-muted-foreground", children: t('provision.summary.channels') }), _jsx("span", { className: "font-medium", children: reuseExistingRole ? '-' : channelIds.length })] }), _jsxs("div", { className: "flex items-center justify-between gap-4", children: [_jsx("span", { className: "text-muted-foreground", children: t('provision.summary.permissions') }), _jsx("span", { className: "font-medium", children: reuseExistingRole ? '-' : selectedAssignablePermissions.length })] }), _jsxs("div", { className: "flex items-center justify-between gap-4", children: [_jsx("span", { className: "text-muted-foreground", children: t('provision.summary.mode') }), _jsx("span", { className: "font-medium", children: reuseExistingRole ? t('provision.summary.reuse') : t('provision.summary.create') })] })] })] })] })] })] }) }));
173
+ };
@@ -0,0 +1,53 @@
1
+ import { Permission } from '@deenruv/admin-types';
2
+ export const SHOP_MANAGER_ROLE_PERMISSIONS = [
3
+ Permission.ReadOrder,
4
+ Permission.UpdateOrder,
5
+ Permission.ReadCustomer,
6
+ Permission.UpdateCustomer,
7
+ Permission.ReadProduct,
8
+ Permission.ReadCatalog,
9
+ Permission.ReadAsset,
10
+ Permission.ReadCollection,
11
+ Permission.ReadFacet,
12
+ Permission.ReadPaymentMethod,
13
+ Permission.ReadShippingMethod,
14
+ Permission.ReadCountry,
15
+ Permission.ReadStockLocation,
16
+ ];
17
+ export const normalizeRoleCode = (value) => value.trim().toLowerCase().replace(/\s+/g, '-');
18
+ export const getSelectedAssignablePermissions = (permissions, assignablePermissions) => {
19
+ if (!assignablePermissions)
20
+ return permissions;
21
+ return permissions.filter((permission) => assignablePermissions.has(permission));
22
+ };
23
+ export const validateAdminProvisionForm = (values, messages, assignablePermissions) => {
24
+ const errors = {};
25
+ const normalizedRoleCode = normalizeRoleCode(values.roleCode);
26
+ const selectedAssignablePermissions = getSelectedAssignablePermissions(values.permissions, assignablePermissions);
27
+ if (!values.firstName.trim())
28
+ errors.firstName = messages.firstNameRequired;
29
+ if (!values.lastName.trim())
30
+ errors.lastName = messages.lastNameRequired;
31
+ if (!values.emailAddress.trim())
32
+ errors.emailAddress = messages.emailRequired;
33
+ if (values.emailAddress.trim() && !values.emailAddress.includes('@')) {
34
+ errors.emailAddress = messages.emailInvalid;
35
+ }
36
+ if (!values.password.trim())
37
+ errors.password = messages.passwordRequired;
38
+ if (!normalizedRoleCode)
39
+ errors.roleCode = messages.roleCodeRequired;
40
+ if (!values.reuseExistingRole) {
41
+ if (!values.channelIds.length)
42
+ errors.channelIds = messages.channelsRequired;
43
+ if (!selectedAssignablePermissions.length) {
44
+ errors.permissions = messages.permissionsRequired;
45
+ }
46
+ }
47
+ return {
48
+ errors,
49
+ normalizedRoleCode,
50
+ selectedAssignablePermissions,
51
+ valid: Object.keys(errors).length === 0,
52
+ };
53
+ };
@@ -1,2 +1,3 @@
1
1
  export * from './Detail.js';
2
2
  export * from './List.js';
3
+ export * from './Provision.js';
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const ADMIN_DASHBOARD_VERSION = '1.0.17-dev.6';
1
+ export const ADMIN_DASHBOARD_VERSION = '1.0.17-dev.8';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deenruv/admin-dashboard",
3
- "version": "1.0.17-dev.6",
3
+ "version": "1.0.17-dev.8",
4
4
  "main": "dist/index.js",
5
5
  "style": "dist/index.css",
6
6
  "types": "dist/index.d.ts",
@@ -80,10 +80,10 @@
80
80
  "yup": "^1.7.1",
81
81
  "zod": "^4.4.3",
82
82
  "zustand": "^5.0.14",
83
- "@deenruv/admin-types": "1.0.17-dev.6",
84
- "@deenruv/deenruv-examples-plugin": "1.0.17-dev.6",
85
- "@deenruv/react-ui-devkit": "1.0.17-dev.6",
86
- "@deenruv/admin-dashboard": "1.0.17-dev.6"
83
+ "@deenruv/admin-dashboard": "1.0.17-dev.8",
84
+ "@deenruv/deenruv-examples-plugin": "1.0.17-dev.8",
85
+ "@deenruv/admin-types": "1.0.17-dev.8",
86
+ "@deenruv/react-ui-devkit": "1.0.17-dev.8"
87
87
  },
88
88
  "devDependencies": {
89
89
  "@tailwindcss/typography": "^0.5.20",