@dashadmin/dash-admin 1.3.45 → 1.3.46
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.
- package/dist/components/i18n/BridgedLocalesMenuButton.js +1 -2
- package/dist/components/menu/DarkToggleMode.js +1 -1
- package/dist/components/subscription/PlanPricesSettings.js +4 -2
- package/dist/components/tenancy/GlobalTenantWrapper.js +64 -0
- package/dist/components/tenancy/TenantSwitcher.js +389 -0
- package/dist/contexts/com/components/EchoNotificationsCenter.js +40 -0
- package/dist/default-theme/menu/AppMenuComponents/expanded/SidebarItem.js +1 -0
- package/dist/index.js +60 -40
- package/dist/pages/Profile.js +32 -38
- package/dist/providers/i18n/en.js +33 -0
- package/dist/providers/i18n/es.js +33 -0
- package/dist/resources/User/components/AvatarUploadField.js +61 -0
- package/dist/resources/User/components/PasswordFields.js +67 -0
- package/dist/resources/User/getProfileMenu.js +36 -0
- package/dist/resources/User/profileResource.js +33 -0
- package/dist/resources/User/userLayout.js +58 -0
- package/dist/resources/User/userResource.js +90 -0
- package/dist/resources/User/userSchema.js +206 -0
- package/dist/resources/User/usersFilters.js +22 -0
- package/dist/systemResources.js +30 -19
- package/package.json +1 -1
- package/src/components/i18n/BridgedLocalesMenuButton.tsx +1 -2
- package/src/components/menu/DarkToggleMode.tsx +1 -1
- package/src/components/subscription/PlanPricesSettings.tsx +2 -2
- package/src/components/tenancy/GlobalTenantWrapper.tsx +84 -0
- package/src/components/tenancy/TenantSwitcher.tsx +485 -0
- package/src/contexts/com/components/EchoNotificationsCenter.tsx +65 -0
- package/src/default-theme/menu/AppMaterialMenu.tsx +1 -1
- package/src/default-theme/menu/AppMenuComponents/expanded/SidebarItem.tsx +1 -0
- package/src/default-theme/menu/SidebarActions.tsx +1 -1
- package/src/index.tsx +15 -0
- package/src/pages/Profile.tsx +69 -107
- package/src/providers/i18n/en.ts +33 -0
- package/src/providers/i18n/es.ts +33 -0
- package/src/resources/User/components/AvatarUploadField.tsx +57 -0
- package/src/resources/User/components/PasswordFields.tsx +73 -0
- package/src/resources/User/getProfileMenu.ts +57 -0
- package/src/resources/User/profileResource.tsx +51 -0
- package/src/resources/User/userLayout.tsx +62 -0
- package/src/resources/User/userResource.tsx +111 -0
- package/src/resources/User/userSchema.tsx +213 -0
- package/src/resources/User/usersFilters.tsx +21 -0
- package/src/systemResources.tsx +31 -20
package/src/pages/Profile.tsx
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { FC, useEffect, useState } from 'react';
|
|
2
|
-
//import { Button as LoadingButton } from "@mui/material";
|
|
3
2
|
|
|
4
3
|
import { Button, Card, CardContent, CardHeader, Grid, Typography } from '@mui/material';
|
|
5
4
|
|
|
@@ -8,7 +7,7 @@ import {
|
|
|
8
7
|
useRefresh,
|
|
9
8
|
Loading,
|
|
10
9
|
useNotify,
|
|
11
|
-
|
|
10
|
+
useTranslate,
|
|
12
11
|
TextInput,
|
|
13
12
|
} from 'react-admin';
|
|
14
13
|
|
|
@@ -16,54 +15,61 @@ import { useDispatch } from 'react-redux';
|
|
|
16
15
|
import { DASH_REDUX_ACTIONS } from 'dash-admin-state';
|
|
17
16
|
|
|
18
17
|
import React from 'react';
|
|
19
|
-
import useAxios from '
|
|
20
|
-
import SingleImageUploader from '
|
|
18
|
+
import { useAxios } from 'dash-axios-hook';
|
|
19
|
+
import { SingleImageUploader } from 'dash-components';
|
|
21
20
|
import { useAuthContext } from '../contexts/auth';
|
|
22
|
-
import UserPreferences from
|
|
21
|
+
import UserPreferences from '../components/user/UserPreferences';
|
|
23
22
|
|
|
24
23
|
const Profile: FC = (_props) => {
|
|
25
24
|
const [currTab, setCurrTab] = useState('auth/update/info');
|
|
26
25
|
const [isLoading, setIsLoading] = useState(false);
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const { user, fetchAuth } = useAuthContext(); // Add fetchCompleteAuth here
|
|
26
|
+
|
|
27
|
+
const { user, fetchAuth } = useAuthContext();
|
|
30
28
|
const [avatar, setAvatar] = useState({ rawFile: null, urlFile: '' });
|
|
31
29
|
const notify = useNotify();
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
const translate = useTranslate();
|
|
31
|
+
|
|
32
|
+
const axios = useAxios();
|
|
34
33
|
const refresh = useRefresh();
|
|
35
34
|
|
|
35
|
+
const { showPreferences = false } = _props;
|
|
36
|
+
|
|
36
37
|
const dispatch = useDispatch();
|
|
37
38
|
|
|
38
39
|
useEffect(() => {
|
|
39
40
|
dispatch(
|
|
40
41
|
DASH_REDUX_ACTIONS.updatePage({
|
|
41
|
-
title: 'Perfil',
|
|
42
|
+
title: translate('profile.title', { _: 'Perfil' }),
|
|
42
43
|
//icon:
|
|
43
|
-
subTitle: 'Configuración de perfil',
|
|
44
|
+
subTitle: translate('profile.subtitle', { _: 'Configuración de perfil' }),
|
|
44
45
|
}),
|
|
45
46
|
);
|
|
46
|
-
|
|
47
|
+
// translate is included so the page header re-translates on locale change
|
|
48
|
+
// (react-admin returns a new translate reference when the locale changes)
|
|
49
|
+
}, [translate]);
|
|
47
50
|
|
|
48
51
|
const validateUserCreation = (values) => {
|
|
49
52
|
const errors: any = {};
|
|
50
53
|
if (currTab === 'auth/update/password') {
|
|
54
|
+
const required = translate('profile.validation.required', { _: 'El campo es requerido' });
|
|
55
|
+
const mismatch = translate('profile.validation.password_mismatch', { _: 'Las contraseñas no coinciden' });
|
|
56
|
+
|
|
51
57
|
if (
|
|
52
58
|
(values.password || values.password_confirmation) &&
|
|
53
59
|
!values.current_password
|
|
54
60
|
) {
|
|
55
|
-
errors.current_password =
|
|
61
|
+
errors.current_password = required;
|
|
56
62
|
}
|
|
57
63
|
if (
|
|
58
64
|
values.current_password &&
|
|
59
65
|
(!values.password || !values.password_confirmation)
|
|
60
66
|
) {
|
|
61
|
-
errors.password =
|
|
62
|
-
errors.password_confirmation =
|
|
67
|
+
errors.password = required;
|
|
68
|
+
errors.password_confirmation = required;
|
|
63
69
|
}
|
|
64
70
|
if (values.password_confirmation !== values.password) {
|
|
65
|
-
errors.password =
|
|
66
|
-
errors.password_confirmation =
|
|
71
|
+
errors.password = mismatch;
|
|
72
|
+
errors.password_confirmation = mismatch;
|
|
67
73
|
}
|
|
68
74
|
}
|
|
69
75
|
|
|
@@ -72,7 +78,7 @@ const Profile: FC = (_props) => {
|
|
|
72
78
|
|
|
73
79
|
const handleSubmit = async (values) => {
|
|
74
80
|
setIsLoading(true);
|
|
75
|
-
|
|
81
|
+
|
|
76
82
|
const formData = new FormData();
|
|
77
83
|
|
|
78
84
|
const passwords = {
|
|
@@ -96,31 +102,27 @@ const Profile: FC = (_props) => {
|
|
|
96
102
|
} else {
|
|
97
103
|
await axios.put(currTab, passwords);
|
|
98
104
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
105
|
+
|
|
102
106
|
// Refresh the auth context with updated user data
|
|
103
107
|
try {
|
|
104
|
-
|
|
105
108
|
await fetchAuth();
|
|
106
109
|
console.log('Auth context refreshed after profile update');
|
|
107
110
|
} catch (authError) {
|
|
108
|
-
console.error('
|
|
111
|
+
console.error('Failed to refresh auth context:', authError);
|
|
109
112
|
// Don't show error to user as the main operation succeeded
|
|
110
113
|
}
|
|
111
114
|
|
|
112
|
-
|
|
115
|
+
notify(translate('profile.notify.success', { _: 'Usuario actualizado correctamente' }), { type: 'success' });
|
|
113
116
|
refresh();
|
|
114
117
|
} catch (error: any) {
|
|
115
118
|
|
|
116
|
-
|
|
117
119
|
// Handle backend error messages gracefully
|
|
118
120
|
let mainMessage = error?.response?.data?.message || error?.body?.message || '';
|
|
119
121
|
let fieldErrors = error?.response?.data?.errors || error?.body?.errors || error;
|
|
120
122
|
if (mainMessage) {
|
|
121
|
-
notify(`Error al actualizar el usuario, ${mainMessage}
|
|
123
|
+
notify(translate('profile.notify.error_with_message', { message: mainMessage, _: `Error al actualizar el usuario, ${mainMessage}` }), { type: 'error' });
|
|
122
124
|
} else {
|
|
123
|
-
notify('Error al actualizar el usuario', { type: 'error' });
|
|
125
|
+
notify(translate('profile.notify.error', { _: 'Error al actualizar el usuario' }), { type: 'error' });
|
|
124
126
|
}
|
|
125
127
|
if (fieldErrors && typeof fieldErrors === 'object') {
|
|
126
128
|
const errors: any = {};
|
|
@@ -133,6 +135,8 @@ const Profile: FC = (_props) => {
|
|
|
133
135
|
}
|
|
134
136
|
} finally {
|
|
135
137
|
setIsLoading(false);
|
|
138
|
+
// TechDebt: the avatar update doesn't reflect immediately after the update, this is a quick fix to reload the page and show the new avatar, ideally we should update the auth context with the new avatar url and avoid reloading the page
|
|
139
|
+
window.location.reload();
|
|
136
140
|
}
|
|
137
141
|
};
|
|
138
142
|
|
|
@@ -146,17 +150,6 @@ const Profile: FC = (_props) => {
|
|
|
146
150
|
|
|
147
151
|
return (
|
|
148
152
|
<div className='dash-profile'>
|
|
149
|
-
|
|
150
|
-
{/*Page Header
|
|
151
|
-
<Row>
|
|
152
|
-
<Col xs={24} md={12}>
|
|
153
|
-
<div className="dash-mb-2">
|
|
154
|
-
<h2 className="dash-page-title">Perfil</h2>
|
|
155
|
-
</div>
|
|
156
|
-
</Col>
|
|
157
|
-
</Row>
|
|
158
|
-
*/}
|
|
159
|
-
|
|
160
153
|
|
|
161
154
|
{user ? (
|
|
162
155
|
<Form
|
|
@@ -165,16 +158,16 @@ const Profile: FC = (_props) => {
|
|
|
165
158
|
defaultValues={{ name: user?.name, email: user?.email, lastname: user?.lastname }}
|
|
166
159
|
className='dash-form'
|
|
167
160
|
>
|
|
168
|
-
|
|
161
|
+
|
|
169
162
|
<div className='dash-card-content dash-module dash-card-profile'>
|
|
170
|
-
|
|
171
|
-
<Grid
|
|
172
|
-
container
|
|
163
|
+
|
|
164
|
+
<Grid
|
|
165
|
+
container
|
|
173
166
|
spacing={{ xs: 2, md: 3 }}
|
|
174
167
|
sx={{ minHeight: '400px' }}
|
|
175
168
|
>
|
|
176
169
|
{/* Profile Image Section */}
|
|
177
|
-
<Grid
|
|
170
|
+
<Grid
|
|
178
171
|
size={{ xs: 12, md: 4 }}
|
|
179
172
|
display="flex"
|
|
180
173
|
justifyContent="center"
|
|
@@ -190,51 +183,18 @@ const Profile: FC = (_props) => {
|
|
|
190
183
|
width: '100%',
|
|
191
184
|
}}
|
|
192
185
|
>
|
|
193
|
-
|
|
194
|
-
{
|
|
195
|
-
<img src={avatar.urlFile || identity.image_url} alt='' />
|
|
196
|
-
) : (
|
|
197
|
-
<div className='dash-profile-icon'>
|
|
198
|
-
<Icon.User size={'90px'} color={'#fff'} />
|
|
199
|
-
</div>
|
|
200
|
-
)}
|
|
201
|
-
<IconButton
|
|
202
|
-
aria-label='upload picture'
|
|
203
|
-
component='label'
|
|
204
|
-
className='dash-profile-edit'
|
|
205
|
-
>
|
|
206
|
-
<input
|
|
207
|
-
hidden
|
|
208
|
-
accept='image/*'
|
|
209
|
-
type='file'
|
|
210
|
-
onChange={(e) =>
|
|
211
|
-
setAvatar({
|
|
212
|
-
rawFile: e.target.files[0],
|
|
213
|
-
urlFile: URL.createObjectURL(e.target.files[0]),
|
|
214
|
-
})
|
|
215
|
-
}
|
|
216
|
-
/>
|
|
217
|
-
<Icon.PenTool
|
|
218
|
-
size={'22px'}
|
|
219
|
-
color={'#000'}
|
|
220
|
-
style={{ opacity: '0.6' }}
|
|
221
|
-
/>
|
|
222
|
-
</IconButton>
|
|
223
|
-
</div>*/}
|
|
224
|
-
|
|
225
|
-
{"<!-- F -->"}
|
|
226
|
-
<SingleImageUploader
|
|
227
|
-
currentUrl={user?.image_path}
|
|
186
|
+
<SingleImageUploader
|
|
187
|
+
currentUrl={user?.image_path}
|
|
228
188
|
onChange={(file: File) => {
|
|
229
189
|
setAvatar({
|
|
230
190
|
rawFile: file,
|
|
231
191
|
urlFile: URL.createObjectURL(file),
|
|
232
192
|
});
|
|
233
|
-
}}
|
|
234
|
-
|
|
193
|
+
}}
|
|
194
|
+
classNamePrefix='dash-profile'
|
|
235
195
|
/>
|
|
236
|
-
<Typography
|
|
237
|
-
className='dash-profile-name'
|
|
196
|
+
<Typography
|
|
197
|
+
className='dash-profile-name'
|
|
238
198
|
variant='h2'
|
|
239
199
|
sx={{ mt: 2, textAlign: 'center' }}
|
|
240
200
|
>
|
|
@@ -257,16 +217,16 @@ const Profile: FC = (_props) => {
|
|
|
257
217
|
|
|
258
218
|
{/* User Info Section */}
|
|
259
219
|
<Grid size={{ xs: 12, md: 4 }}>
|
|
260
|
-
<CardHeader
|
|
261
|
-
sx={{ padding: '16px 25px 0' }}
|
|
262
|
-
title='Info'
|
|
220
|
+
<CardHeader
|
|
221
|
+
sx={{ padding: '16px 25px 0' }}
|
|
222
|
+
title={translate('profile.info.title', { _: 'Info' })}
|
|
263
223
|
/>
|
|
264
224
|
<CardContent sx={{ padding: '25px' }}>
|
|
265
225
|
<Grid container spacing={2}>
|
|
266
226
|
<Grid size={12}>
|
|
267
227
|
<TextInput
|
|
268
228
|
source='name'
|
|
269
|
-
label='Nombre'
|
|
229
|
+
label={translate('profile.info.name', { _: 'Nombre' })}
|
|
270
230
|
variant='outlined'
|
|
271
231
|
fullWidth
|
|
272
232
|
/>
|
|
@@ -274,7 +234,7 @@ const Profile: FC = (_props) => {
|
|
|
274
234
|
<Grid size={12}>
|
|
275
235
|
<TextInput
|
|
276
236
|
source='lastname'
|
|
277
|
-
label='Apellido'
|
|
237
|
+
label={translate('profile.info.lastname', { _: 'Apellido' })}
|
|
278
238
|
variant='outlined'
|
|
279
239
|
fullWidth
|
|
280
240
|
/>
|
|
@@ -282,7 +242,7 @@ const Profile: FC = (_props) => {
|
|
|
282
242
|
<Grid size={12}>
|
|
283
243
|
<TextInput
|
|
284
244
|
source='email'
|
|
285
|
-
label='Email'
|
|
245
|
+
label={translate('profile.info.email', { _: 'Email' })}
|
|
286
246
|
variant='outlined'
|
|
287
247
|
fullWidth
|
|
288
248
|
/>
|
|
@@ -303,7 +263,9 @@ const Profile: FC = (_props) => {
|
|
|
303
263
|
}
|
|
304
264
|
}}
|
|
305
265
|
>
|
|
306
|
-
{isLoading && currTab === 'auth/update/info'
|
|
266
|
+
{isLoading && currTab === 'auth/update/info'
|
|
267
|
+
? translate('profile.info.saving', { _: 'Guardando...' })
|
|
268
|
+
: translate('profile.info.save', { _: 'Guardar' })}
|
|
307
269
|
</Button>
|
|
308
270
|
</Grid>
|
|
309
271
|
</Grid>
|
|
@@ -314,7 +276,7 @@ const Profile: FC = (_props) => {
|
|
|
314
276
|
<Grid size={{ xs: 12, md: 4 }}>
|
|
315
277
|
<CardHeader
|
|
316
278
|
sx={{ padding: '16px 25px 0' }}
|
|
317
|
-
title='Contraseñas'
|
|
279
|
+
title={translate('profile.password.title', { _: 'Contraseñas' })}
|
|
318
280
|
/>
|
|
319
281
|
<CardContent sx={{ padding: '25px' }}>
|
|
320
282
|
<Grid container spacing={2}>
|
|
@@ -322,7 +284,7 @@ const Profile: FC = (_props) => {
|
|
|
322
284
|
<TextInput
|
|
323
285
|
type='password'
|
|
324
286
|
source='current_password'
|
|
325
|
-
label='Contraseña actual'
|
|
287
|
+
label={translate('profile.password.current', { _: 'Contraseña actual' })}
|
|
326
288
|
variant='outlined'
|
|
327
289
|
fullWidth
|
|
328
290
|
/>
|
|
@@ -331,14 +293,14 @@ const Profile: FC = (_props) => {
|
|
|
331
293
|
<TextInput
|
|
332
294
|
type='password'
|
|
333
295
|
source='password'
|
|
334
|
-
label='Nueva contraseña'
|
|
296
|
+
label={translate('profile.password.new', { _: 'Nueva contraseña' })}
|
|
335
297
|
variant='outlined'
|
|
336
298
|
fullWidth
|
|
337
299
|
/>
|
|
338
300
|
</Grid>
|
|
339
301
|
<Grid size={12}>
|
|
340
302
|
<TextInput
|
|
341
|
-
label='Repita nueva contraseña'
|
|
303
|
+
label={translate('profile.password.confirm', { _: 'Repita nueva contraseña' })}
|
|
342
304
|
type='password'
|
|
343
305
|
source='password_confirmation'
|
|
344
306
|
variant='outlined'
|
|
@@ -361,7 +323,9 @@ const Profile: FC = (_props) => {
|
|
|
361
323
|
}
|
|
362
324
|
}}
|
|
363
325
|
>
|
|
364
|
-
{isLoading && currTab === 'auth/update/password'
|
|
326
|
+
{isLoading && currTab === 'auth/update/password'
|
|
327
|
+
? translate('profile.password.saving', { _: 'Guardando Contraseñas...' })
|
|
328
|
+
: translate('profile.password.save', { _: 'Guardar Contraseñas' })}
|
|
365
329
|
</Button>
|
|
366
330
|
</Grid>
|
|
367
331
|
</Grid>
|
|
@@ -369,21 +333,19 @@ const Profile: FC = (_props) => {
|
|
|
369
333
|
</Grid>
|
|
370
334
|
|
|
371
335
|
{/* User Preferences Section */}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
</CardContent>
|
|
381
|
-
</Grid>
|
|
336
|
+
{showPreferences && (
|
|
337
|
+
<Grid size={{ xs: 12 }}>
|
|
338
|
+
<CardContent sx={{ padding: '25px' }}>
|
|
339
|
+
<UserPreferences />
|
|
340
|
+
</CardContent>
|
|
341
|
+
</Grid>
|
|
342
|
+
)}
|
|
343
|
+
|
|
382
344
|
</Grid>
|
|
383
345
|
</div>
|
|
384
|
-
|
|
346
|
+
|
|
385
347
|
</Form>
|
|
386
|
-
|
|
348
|
+
|
|
387
349
|
) : (
|
|
388
350
|
<Loading />
|
|
389
351
|
)}
|
package/src/providers/i18n/en.ts
CHANGED
|
@@ -605,6 +605,39 @@ const dashEnglish = {
|
|
|
605
605
|
'Uses AI to create a complete color scheme based on extracted colors and your description',
|
|
606
606
|
},
|
|
607
607
|
},
|
|
608
|
+
// Profile page
|
|
609
|
+
profile: {
|
|
610
|
+
title: 'Profile',
|
|
611
|
+
subtitle: 'Profile settings',
|
|
612
|
+
info: {
|
|
613
|
+
title: 'Info',
|
|
614
|
+
name: 'Name',
|
|
615
|
+
lastname: 'Last name',
|
|
616
|
+
email: 'Email',
|
|
617
|
+
save: 'Save',
|
|
618
|
+
saving: 'Saving...',
|
|
619
|
+
},
|
|
620
|
+
password: {
|
|
621
|
+
title: 'Passwords',
|
|
622
|
+
current: 'Current password',
|
|
623
|
+
new: 'New password',
|
|
624
|
+
confirm: 'Confirm new password',
|
|
625
|
+
save: 'Save Passwords',
|
|
626
|
+
saving: 'Saving Passwords...',
|
|
627
|
+
},
|
|
628
|
+
preferences: {
|
|
629
|
+
title: 'Preferences',
|
|
630
|
+
},
|
|
631
|
+
validation: {
|
|
632
|
+
required: 'This field is required',
|
|
633
|
+
password_mismatch: 'Passwords do not match',
|
|
634
|
+
},
|
|
635
|
+
notify: {
|
|
636
|
+
success: 'User updated successfully',
|
|
637
|
+
error: 'Error updating user',
|
|
638
|
+
error_with_message: 'Error updating user, %{message}',
|
|
639
|
+
},
|
|
640
|
+
},
|
|
608
641
|
};
|
|
609
642
|
|
|
610
643
|
export default {
|
package/src/providers/i18n/es.ts
CHANGED
|
@@ -611,6 +611,39 @@ const dashSpanish = {
|
|
|
611
611
|
'Usa IA para crear un esquema de color completo basado en los colores extraídos y tu descripción',
|
|
612
612
|
},
|
|
613
613
|
},
|
|
614
|
+
// Profile page
|
|
615
|
+
profile: {
|
|
616
|
+
title: 'Perfil',
|
|
617
|
+
subtitle: 'Configuración de perfil',
|
|
618
|
+
info: {
|
|
619
|
+
title: 'Info',
|
|
620
|
+
name: 'Nombre',
|
|
621
|
+
lastname: 'Apellido',
|
|
622
|
+
email: 'Email',
|
|
623
|
+
save: 'Guardar',
|
|
624
|
+
saving: 'Guardando...',
|
|
625
|
+
},
|
|
626
|
+
password: {
|
|
627
|
+
title: 'Contraseñas',
|
|
628
|
+
current: 'Contraseña actual',
|
|
629
|
+
new: 'Nueva contraseña',
|
|
630
|
+
confirm: 'Repita nueva contraseña',
|
|
631
|
+
save: 'Guardar Contraseñas',
|
|
632
|
+
saving: 'Guardando Contraseñas...',
|
|
633
|
+
},
|
|
634
|
+
preferences: {
|
|
635
|
+
title: 'Preferencias',
|
|
636
|
+
},
|
|
637
|
+
validation: {
|
|
638
|
+
required: 'El campo es requerido',
|
|
639
|
+
password_mismatch: 'Las contraseñas no coinciden',
|
|
640
|
+
},
|
|
641
|
+
notify: {
|
|
642
|
+
success: 'Usuario actualizado correctamente',
|
|
643
|
+
error: 'Error al actualizar el usuario',
|
|
644
|
+
error_with_message: 'Error al actualizar el usuario, %{message}',
|
|
645
|
+
},
|
|
646
|
+
},
|
|
614
647
|
};
|
|
615
648
|
|
|
616
649
|
export default {
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { IDashAutoAdminCustomFieldComponent } from 'dash-auto-admin';
|
|
3
|
+
import { useRecordContext } from 'react-admin';
|
|
4
|
+
import { SingleImageUploader } from 'dash-components';
|
|
5
|
+
import { useFormContext } from 'react-hook-form';
|
|
6
|
+
|
|
7
|
+
export type AvatarComponent = IDashAutoAdminCustomFieldComponent;
|
|
8
|
+
|
|
9
|
+
export const AvatarHandler: React.FC<AvatarComponent> = (props) => {
|
|
10
|
+
const { method, attribute } = props;
|
|
11
|
+
const record = useRecordContext();
|
|
12
|
+
const {
|
|
13
|
+
setValue,
|
|
14
|
+
formState: { errors },
|
|
15
|
+
} = useFormContext();
|
|
16
|
+
|
|
17
|
+
const onChange = (file: File) => {
|
|
18
|
+
setValue(attribute.attribute, file, { shouldDirty: true });
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<>
|
|
23
|
+
<SingleImageUploader
|
|
24
|
+
classNamePrefix='dash-profile'
|
|
25
|
+
{...method === "edit" && { currentUrl: `${record.image_path}`} }
|
|
26
|
+
onChange={onChange}
|
|
27
|
+
/>
|
|
28
|
+
{errors[attribute.attribute] && (
|
|
29
|
+
<span style={{ color: 'red' }}>
|
|
30
|
+
{errors[attribute.attribute]?.message?.toString() || 'Error'}
|
|
31
|
+
</span>
|
|
32
|
+
)}
|
|
33
|
+
</>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Generic user-avatar upload field for the shared user resource schema.
|
|
39
|
+
*
|
|
40
|
+
* Moved from kt-ecommerce (components/User/Avatar.tsx) - purely generic
|
|
41
|
+
* (dash-auto-admin/dash-components/react-hook-form only, no kitchntabs- or
|
|
42
|
+
* vanexa-specific logic), so it belongs alongside userSchema.tsx here rather
|
|
43
|
+
* than duplicated per domain. Renamed from "Avatar" to avoid colliding with
|
|
44
|
+
* dash-admin's unrelated components/user/AvatarComponent.tsx (the topbar
|
|
45
|
+
* user-menu avatar widget - a different component for a different purpose).
|
|
46
|
+
*/
|
|
47
|
+
const AvatarUploadField = ({ method, attribute, resourceConfig }: IDashAutoAdminCustomFieldComponent) => {
|
|
48
|
+
switch (method) {
|
|
49
|
+
case 'edit':
|
|
50
|
+
return <AvatarHandler attribute={attribute} method={method} resourceConfig={resourceConfig} />;
|
|
51
|
+
case 'create':
|
|
52
|
+
return <AvatarHandler attribute={attribute} method={method} resourceConfig={resourceConfig} />;
|
|
53
|
+
case 'view':
|
|
54
|
+
return <></>;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
export default AvatarUploadField;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { IDashAutoAdminCustomFieldComponent } from 'dash-auto-admin';
|
|
3
|
+
import {
|
|
4
|
+
PasswordInput,
|
|
5
|
+
} from 'react-admin';
|
|
6
|
+
import { Box } from '@mui/material';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Generic password + confirm-password field pair for the shared user
|
|
10
|
+
* resource schema. Moved from kt-ecommerce (components/User/Password.tsx) -
|
|
11
|
+
* no kitchntabs- or vanexa-specific logic. Renamed from "Password" to avoid
|
|
12
|
+
* ambiguity with other password-related components in this package.
|
|
13
|
+
*/
|
|
14
|
+
export type PasswordComponent = IDashAutoAdminCustomFieldComponent;
|
|
15
|
+
export const PasswordShow: React.FC<PasswordComponent> = (_props) => {
|
|
16
|
+
return <></>;
|
|
17
|
+
};
|
|
18
|
+
export const PasswordEdit: React.FC<PasswordComponent> = (_props) => {
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<>
|
|
22
|
+
<Box>
|
|
23
|
+
<PasswordInput
|
|
24
|
+
sx={{ width: '100%' }}
|
|
25
|
+
autoComplete='off'
|
|
26
|
+
source='Contraseña'
|
|
27
|
+
name='password'
|
|
28
|
+
/>
|
|
29
|
+
</Box>
|
|
30
|
+
<Box>
|
|
31
|
+
<PasswordInput
|
|
32
|
+
sx={{ width: '100%' }}
|
|
33
|
+
autoComplete='off'
|
|
34
|
+
source='Confirmar Contraseña'
|
|
35
|
+
name='password_confirmation'
|
|
36
|
+
/>
|
|
37
|
+
</Box>
|
|
38
|
+
</>
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
export const PasswordCreate: React.FC<PasswordComponent> = (_props) => {
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<>
|
|
46
|
+
<Box>
|
|
47
|
+
<PasswordInput
|
|
48
|
+
sx={{ width: '100%' }}
|
|
49
|
+
source='Contraseña'
|
|
50
|
+
name='password'
|
|
51
|
+
/>
|
|
52
|
+
</Box>
|
|
53
|
+
<Box>
|
|
54
|
+
<PasswordInput
|
|
55
|
+
sx={{ width: '100%' }}
|
|
56
|
+
source='Confirmar Contraseña'
|
|
57
|
+
name='password_confirmation'
|
|
58
|
+
/>
|
|
59
|
+
</Box>
|
|
60
|
+
</>
|
|
61
|
+
);
|
|
62
|
+
};
|
|
63
|
+
const PasswordFields = ({ method, attribute, resourceConfig }: IDashAutoAdminCustomFieldComponent) => {
|
|
64
|
+
switch (method) {
|
|
65
|
+
case 'edit':
|
|
66
|
+
return <PasswordEdit attribute={attribute} method={method} resourceConfig={resourceConfig} />;
|
|
67
|
+
case 'create':
|
|
68
|
+
return <PasswordCreate attribute={attribute} method={method} resourceConfig={resourceConfig} />;
|
|
69
|
+
case 'view':
|
|
70
|
+
return <PasswordShow attribute={attribute} method={method} resourceConfig={resourceConfig} />;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
export default PasswordFields;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Profile menu generator for user navigation.
|
|
3
|
+
*
|
|
4
|
+
* Moved here from kt-utils (getProfileMenu.ts) per the profile/user resource
|
|
5
|
+
* refactor - it is consumed by resources/User/profileResource.tsx, which now
|
|
6
|
+
* also lives in this package, and doesn't depend on anything kitchntabs- or
|
|
7
|
+
* vanexa-specific beyond the 'Cliente' role check below (kept verbatim - both
|
|
8
|
+
* domains currently use it identically; a domain that needs a different
|
|
9
|
+
* profile menu can still override profileResource.menu after importing it).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { dashStorage } from 'dash-utils';
|
|
13
|
+
import type IApplicationLayoutMenuItem from 'dash-auto-admin/src/interfaces/IDashApplicationLayoutMenuItem';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Generates the profile menu based on user roles
|
|
17
|
+
*/
|
|
18
|
+
const getProfileMenu = (resourceConfig?: any): IApplicationLayoutMenuItem[] => {
|
|
19
|
+
const userStr = dashStorage.getItem('user');
|
|
20
|
+
const rolesStr = dashStorage.getItem('roles');
|
|
21
|
+
|
|
22
|
+
const user = userStr ? JSON.parse(userStr) : null;
|
|
23
|
+
const roles: any[] = rolesStr ? JSON.parse(rolesStr) : [];
|
|
24
|
+
|
|
25
|
+
const profileMenu: IApplicationLayoutMenuItem[] = [
|
|
26
|
+
{
|
|
27
|
+
title: 'Perfil',
|
|
28
|
+
redirect: '/profile',
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
const isCliente = roles
|
|
33
|
+
? roles.map((role) => role.name).includes('Cliente')
|
|
34
|
+
: false;
|
|
35
|
+
|
|
36
|
+
if (isCliente) {
|
|
37
|
+
profileMenu.push(
|
|
38
|
+
{
|
|
39
|
+
title: 'Comunas',
|
|
40
|
+
redirect: '/profile/commune',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
title: 'Centros Logísticos',
|
|
44
|
+
redirect: '/profile/logistic-center',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
title: 'Notificaciones',
|
|
48
|
+
redirect: '/profile/notification',
|
|
49
|
+
},
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return profileMenu;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export default getProfileMenu;
|
|
57
|
+
export { getProfileMenu };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { IDashAutoAdminResourceConfig } from 'dash-auto-admin';
|
|
2
|
+
import ResourceTemplate from '../../templates/ResourceTemplate';
|
|
3
|
+
import Dashboard from '@mui/icons-material/Dashboard';
|
|
4
|
+
import {DASHAdminSystemConstants} from 'dash-constants';
|
|
5
|
+
|
|
6
|
+
import getProfileMenu from './getProfileMenu';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Shared "/profile" resource shell.
|
|
10
|
+
*
|
|
11
|
+
* Moved here from kt-ecommerce (resources/user/profileResource.tsx) - the
|
|
12
|
+
* config itself is generic. `listComponent` is intentionally left unset: the
|
|
13
|
+
* kt-ecommerce original hardcoded kt-pages' CustomProfile page, which would
|
|
14
|
+
* have made this package depend on a kitchntabs-flavored consumer package
|
|
15
|
+
* (backwards - dash-admin is core, kt-pages/kt-ecommerce are domain layers
|
|
16
|
+
* built on top of it). Each app supplies its own profile page component by
|
|
17
|
+
* overriding `listComponent` after importing this base config, e.g.:
|
|
18
|
+
*
|
|
19
|
+
* import profileResource from 'dash-admin/src/resources/User/profileResource';
|
|
20
|
+
* import CustomProfile from 'kt-pages/src/pages/Account/CustomProfile';
|
|
21
|
+
*
|
|
22
|
+
* export default {
|
|
23
|
+
* ...profileResource,
|
|
24
|
+
* listComponent: (_resourceConfig) => <CustomProfile />,
|
|
25
|
+
* };
|
|
26
|
+
*/
|
|
27
|
+
const profileResource: IDashAutoAdminResourceConfig = {
|
|
28
|
+
filterWithSubmit: true,
|
|
29
|
+
roles: ["*"
|
|
30
|
+
/*
|
|
31
|
+
DASHAdminSystemConstants.system.DASH_SYSTEM_ROLE,
|
|
32
|
+
DASHAppConstants.system.TENANT_ROLE,
|
|
33
|
+
DASHAppConstants.system.USER_ROLE,
|
|
34
|
+
DASHAppConstants.system.CREATOR_ROLE,
|
|
35
|
+
DASHAppConstants.system.CLOSING_ROLE,
|
|
36
|
+
DASHAppConstants.system.GUNNER_ROLE,
|
|
37
|
+
DASHAppConstants.system.DRIVER_ROLE
|
|
38
|
+
*/
|
|
39
|
+
],
|
|
40
|
+
component: ResourceTemplate,
|
|
41
|
+
hidden: true,
|
|
42
|
+
model: 'profile',
|
|
43
|
+
group: 'Perfil',
|
|
44
|
+
label: 'Perfil',
|
|
45
|
+
icon: <Dashboard />,
|
|
46
|
+
schema: null,
|
|
47
|
+
menu: getProfileMenu,
|
|
48
|
+
listProps : { perPage: DASHAdminSystemConstants.system.DEFAULT_PER_PAGE || 50}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export default profileResource;
|