@wger-project/react-components 25.11.17 → 25.11.22

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/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "exports": {
12
12
  ".": "./build/index.js"
13
13
  },
14
- "version": "25.11.17",
14
+ "version": "25.11.22",
15
15
  "repository": "https://github.com/wger-project/react",
16
16
  "type": "module",
17
17
  "publishConfig": {
@@ -39,7 +39,7 @@
39
39
  "luxon": "^3.7.2",
40
40
  "react": "^19.2.0",
41
41
  "react-dom": "^19.2.0",
42
- "react-i18next": "^16.0.0",
42
+ "react-i18next": "^16.3.3",
43
43
  "react-is": "^19.2.0",
44
44
  "react-responsive": "^10.0.1",
45
45
  "react-router-dom": "^7.9.4",
@@ -127,7 +127,10 @@ describe("<Step1Basics />", () => {
127
127
  expect(setSecondaryMuscles).toHaveBeenNthCalledWith(2, [4]);
128
128
  expect(setNameEn).toHaveBeenCalledWith('Biceps enlarger');
129
129
  expect(setCategory).toHaveBeenCalledWith(1);
130
- expect(setAlternativeNamesEn).toHaveBeenCalledWith(['Biceps enlarger 2000', 'Arms exploder']);
130
+ expect(setAlternativeNamesEn).toHaveBeenCalledWith([
131
+ { 'alias': 'Biceps enlarger 2000' },
132
+ { 'alias': 'Arms exploder' }
133
+ ]);
131
134
  expect(setEquipment).toHaveBeenCalledWith([42]);
132
135
  });
133
136
  });
@@ -67,7 +67,17 @@ export const ExerciseDetailEdit = ({ exerciseId, language }: ViewProps) => {
67
67
  }, [exerciseQuery.data]);
68
68
 
69
69
 
70
- if (exerciseQuery.isLoading || musclesQuery.isLoading || exerciseQuery.isLoading || profileQuery.isLoading || addImagePermissionQuery.isLoading || deleteImagePermissionQuery.isLoading || addVideoPermissionQuery.isLoading || deleteVideoPermissionQuery.isLoading || editExercisePermissionQuery.isLoading) {
70
+ if (
71
+ exerciseQuery.isLoading
72
+ || musclesQuery.isLoading
73
+ || exerciseQuery.isLoading
74
+ || profileQuery.isLoading
75
+ || addImagePermissionQuery.isLoading
76
+ || deleteImagePermissionQuery.isLoading
77
+ || addVideoPermissionQuery.isLoading
78
+ || deleteVideoPermissionQuery.isLoading
79
+ || editExercisePermissionQuery.isLoading
80
+ ) {
71
81
  return <LoadingWidget />;
72
82
  }
73
83
 
@@ -89,7 +99,7 @@ export const ExerciseDetailEdit = ({ exerciseId, language }: ViewProps) => {
89
99
  <Formik
90
100
  initialValues={{
91
101
  name: exerciseTranslation.name,
92
- alternativeNames: exerciseTranslation.aliases.map(a => a.alias),
102
+ alternativeNames: exerciseTranslation.aliases.map(a => ({ id: a.id, alias: a.alias })),
93
103
  description: exerciseTranslation.description,
94
104
  }}
95
105
  enableReinitialize
@@ -114,24 +124,24 @@ export const ExerciseDetailEdit = ({ exerciseId, language }: ViewProps) => {
114
124
  author: profileQuery.data!.username
115
125
  });
116
126
 
117
- // Edit aliases (this is currently really hacky)
118
- // Since we only get the string from the form, we need to compare them to the list of
119
- // current aliases and decide which stay the same and which will be updated (which we
120
- // can't directly do, so the old one gets deleted and a new one created)
127
+ // Alias handling
128
+ const aliasOrig = (exerciseTranslation.aliases).map(a => ({ id: a.id, alias: a.alias }));
129
+ const aliasNew = values.alternativeNames ?? [];
121
130
 
122
- // https://stackoverflow.com/questions/1187518/
123
- const aliasOrig = exerciseTranslation.aliases.map(a => a.alias);
124
- const aliasNew = values.alternativeNames;
125
- const aliasToCreate = aliasNew.filter(x => !aliasOrig.includes(x));
126
- const aliasToDelete = aliasOrig.filter(x => !aliasNew.includes(x));
131
+ const aliasToCreate = aliasNew.filter(n => !aliasOrig.some(o => o.alias === n.alias));
132
+ const aliasToDelete = aliasOrig.filter(o => !aliasNew.some(n => n.alias === o.alias));
127
133
 
128
- aliasToCreate.forEach(alias => {
129
- postAlias(translation.id!, alias);
130
- });
134
+ // Create new aliases
135
+ for (const a of aliasToCreate) {
136
+ await postAlias(translation.id!, a.alias);
137
+ }
131
138
 
132
- aliasToDelete.forEach(alias => {
133
- deleteAlias(exerciseTranslation.aliases.find(a => a.alias === alias)!.id);
134
- });
139
+ // Delete removed aliases
140
+ for (const a of aliasToDelete) {
141
+ if (a.id) {
142
+ await deleteAlias(a.id);
143
+ }
144
+ }
135
145
 
136
146
  // Notify the user
137
147
  setAlertIsVisible(true);
@@ -161,11 +171,14 @@ export const ExerciseDetailEdit = ({ exerciseId, language }: ViewProps) => {
161
171
  <PaddingBox />
162
172
  </Grid>}
163
173
 
174
+ <Grid size={12}>
175
+ <Typography variant={'h5'}>{t('translation')}</Typography>
176
+ </Grid>
164
177
  <Grid size={6}>
165
- <Typography variant={'h5'}>{t('English')}</Typography>
178
+ <Typography variant={'h6'}>{t('English')}</Typography>
166
179
  </Grid>
167
180
  <Grid size={6}>
168
- <Typography variant={'h5'}>
181
+ <Typography variant={'h6'}>
169
182
  {language.nameLong} ({language.nameShort})
170
183
  </Typography>
171
184
  </Grid>
@@ -206,6 +219,9 @@ export const ExerciseDetailEdit = ({ exerciseId, language }: ViewProps) => {
206
219
  <Grid size={12}>
207
220
  <PaddingBox />
208
221
  </Grid>
222
+ <Grid size={12}>
223
+ <Typography variant={'h5'}>{t('nutrition.others')}</Typography>
224
+ </Grid>
209
225
  <Grid size={{ xs: 12, md: 6 }}>
210
226
  <EditExerciseCategory exerciseId={exercise.id!} initial={exercise.category.id} />
211
227
  </Grid>
@@ -256,7 +272,7 @@ export const ExerciseDetailEdit = ({ exerciseId, language }: ViewProps) => {
256
272
 
257
273
  {/* Images */}
258
274
  <PaddingBox />
259
- <Typography variant={'h6'}>{t('images')}</Typography>
275
+ <Typography variant={'h5'}>{t('images')}</Typography>
260
276
  <Grid container spacing={2} mt={2}>
261
277
  {addImagePermissionQuery.data && <Grid key={'add'} size={{ md: 3 }}>
262
278
  <AddImageCard exerciseId={exercise.id!} />
@@ -275,7 +291,7 @@ export const ExerciseDetailEdit = ({ exerciseId, language }: ViewProps) => {
275
291
 
276
292
  {/* Videos */}
277
293
  <PaddingBox />
278
- <Typography variant={'h6'}>{t('videos')}</Typography>
294
+ <Typography variant={'h5'}>{t('videos')}</Typography>
279
295
  <Grid container spacing={2} mt={2}>
280
296
  {addVideoPermissionQuery.data
281
297
  && <Grid key={'add'} size={{ md: 3 }}>
@@ -285,7 +301,8 @@ export const ExerciseDetailEdit = ({ exerciseId, language }: ViewProps) => {
285
301
 
286
302
  {exercise.videos.map(video => (
287
303
  <Grid key={video.id} size={{ md: 3 }}>
288
- <VideoEditCard exerciseId={exercise.id!} video={video} canDelete={deleteVideoPermissionQuery.data!} />
304
+ <VideoEditCard exerciseId={exercise.id!} video={video}
305
+ canDelete={deleteVideoPermissionQuery.data!} />
289
306
  </Grid>
290
307
  ))}
291
308
  </Grid>
@@ -294,9 +311,8 @@ export const ExerciseDetailEdit = ({ exerciseId, language }: ViewProps) => {
294
311
  {editExercisePermissionQuery.data
295
312
  && <>
296
313
  <PaddingBox />
297
- <Typography variant={'h6'}>{t('exercises.muscles')}</Typography>
314
+ <Typography variant={'h5'}>{t('exercises.muscles')}</Typography>
298
315
  <Grid container spacing={1} mt={2}>
299
-
300
316
  <Grid size={{ xs: 12, md: 6 }}>
301
317
  <EditExerciseMuscle
302
318
  exerciseId={exercise.id!}
@@ -305,8 +321,7 @@ export const ExerciseDetailEdit = ({ exerciseId, language }: ViewProps) => {
305
321
  blocked={secondaryMuscles}
306
322
  isMain
307
323
  />
308
- </Grid>
309
- <Grid size={{ xs: 12, md: 6 }}>
324
+ <Box sx={{ mt: 2 }} />
310
325
  <EditExerciseMuscle
311
326
  exerciseId={exercise.id!}
312
327
  value={secondaryMuscles}
@@ -315,7 +330,7 @@ export const ExerciseDetailEdit = ({ exerciseId, language }: ViewProps) => {
315
330
  isMain={false}
316
331
  />
317
332
  </Grid>
318
- <Grid size={{ sm: 6 }} offset={{ md: 3 }}>
333
+ <Grid size={{ xs: 12, md: 6 }}>
319
334
  <Grid container>
320
335
  <Grid display="flex" justifyContent={"center"} size={6}>
321
336
  <MuscleOverview
@@ -333,6 +348,7 @@ export const ExerciseDetailEdit = ({ exerciseId, language }: ViewProps) => {
333
348
  </Grid>
334
349
  </Grid>
335
350
  </Grid>
351
+
336
352
  </Grid>
337
353
  </>
338
354
  }
@@ -11,6 +11,7 @@ import { MuscleOverview } from "components/Muscles/MuscleOverview";
11
11
  import { useCanContributeExercises } from "components/User/queries/contribute";
12
12
  import React from "react";
13
13
  import { useTranslation } from "react-i18next";
14
+ import { dateTimeToLocale } from "utils/date";
14
15
 
15
16
 
16
17
  const TranslateExerciseBanner = ({ setEditMode }: { setEditMode: (mode: boolean) => void }) => {
@@ -60,6 +61,15 @@ export const ExerciseDetailView = ({
60
61
  const currentTranslation = exercise.getTranslation(language);
61
62
  const isNewTranslation = language && language.id !== currentTranslation.language;
62
63
 
64
+ const muscleLegendStyle = {
65
+ height: '17px',
66
+ width: '17px',
67
+ display: 'inline-block',
68
+ verticalAlign: 'middle',
69
+ borderRadius: 4,
70
+ opacity: 0.8,
71
+ };
72
+
63
73
  return (
64
74
  <Grid container>
65
75
  {isNewTranslation
@@ -92,8 +102,7 @@ export const ExerciseDetailView = ({
92
102
  </>}
93
103
 
94
104
  <Typography variant="h5">{t("exercises.description")}</Typography>
95
- <div
96
- dangerouslySetInnerHTML={{ __html: currentTranslation?.description! }} />
105
+ <div dangerouslySetInnerHTML={{ __html: currentTranslation?.description! }} />
97
106
  <PaddingBox />
98
107
 
99
108
  {currentTranslation?.notes.length > 0 && <Typography variant="h5">{t("exercises.notes")}</Typography>}
@@ -124,7 +133,12 @@ export const ExerciseDetailView = ({
124
133
  xs: 6,
125
134
  md: 3
126
135
  }}>
127
- <h3>{t("exercises.primaryMuscles")}</h3>
136
+ <div>
137
+ <div style={{ ...muscleLegendStyle, backgroundColor: '#fc0000' }}></div>
138
+ <b style={{ marginLeft: 8, verticalAlign: 'middle' }}>
139
+ {t("exercises.primaryMuscles")}
140
+ </b>
141
+ </div>
128
142
  <ul>
129
143
  {exercise.muscles.map((m: Muscle) => (
130
144
  <li key={m.id}>{m.getName()}</li>
@@ -152,7 +166,13 @@ export const ExerciseDetailView = ({
152
166
  xs: 6,
153
167
  md: 3
154
168
  }}>
155
- <h3>{t("exercises.secondaryMuscles")}</h3>
169
+ <div>
170
+ <div style={{ ...muscleLegendStyle, backgroundColor: '#f57900' }}></div>
171
+ <b style={{ marginLeft: 8, verticalAlign: 'middle' }}>
172
+ {t("exercises.secondaryMuscles")}
173
+ </b>
174
+ </div>
175
+
156
176
  <ul>
157
177
  {exercise.musclesSecondary.map((m: Muscle) => (
158
178
  <li key={m.id}>{m.getName()}</li>
@@ -197,19 +217,19 @@ export const ExerciseDetailView = ({
197
217
  */}
198
218
 
199
219
  {/* This gallery only displays on medium screens upwards */}
200
- <SideGallery
201
- mainImage={exercise.mainImage}
202
- sideImages={exercise.sideImages}
203
- />
204
-
205
- <PaddingBox />
206
- <SideVideoGallery videos={exercise.videos} />
220
+ <Box sx={{ pl: 1 }}>
221
+ <SideGallery
222
+ mainImage={exercise.mainImage}
223
+ sideImages={exercise.sideImages}
224
+ />
225
+ <PaddingBox />
226
+ <SideVideoGallery videos={exercise.videos} />
227
+ </Box>
207
228
  </Grid>
208
229
 
209
230
 
210
231
  <Grid order={{ xs: 3 }} size={12}>
211
232
 
212
- <Divider />
213
233
  <PaddingBox />
214
234
 
215
235
  {variations.length > 0 && <Typography variant={"h5"}>{t('exercises.variations')}</Typography>}
@@ -230,11 +250,31 @@ export const ExerciseDetailView = ({
230
250
  )}
231
251
  </Grid>
232
252
  </Grid>
233
- <Grid order={{ xs: 4 }} size={12}>
234
- <Typography variant="caption" display="block" mt={2}>
235
- The text on this page is available under the <a
236
- href="https://creativecommons.org/licenses/by-sa/4.0/deed">CC BY-SA 4
237
- License</a>.
253
+ <Grid order={{ xs: 4, }} sx={{ mt: 3 }} size={12}>
254
+
255
+ <Divider />
256
+ <Typography
257
+ variant="caption"
258
+ display="block"
259
+ mt={1}
260
+ sx={{ display: "flex", alignItems: "center", gap: 1 }}
261
+ >
262
+ <i className="fa-brands fa-creative-commons" style={{ fontSize: 20 }}></i>
263
+ The content on this page is available under the
264
+ <a href="https://creativecommons.org/licenses/by-sa/4.0/deed">
265
+ CC BY-SA 4 License
266
+ </a>.
267
+ </Typography>
268
+
269
+ <Typography
270
+ variant="caption"
271
+ display="block" mt={1}
272
+ sx={{ display: "flex", alignItems: "center", gap: 1 }}
273
+ >
274
+ <>
275
+ Authors: {exercise.authors.join(", ")}<br />
276
+ Last update: {dateTimeToLocale(exercise.lastUpdateGlobal)}
277
+ </>
238
278
  </Typography>
239
279
  </Grid>
240
280
  </Grid>
@@ -27,17 +27,13 @@ describe("Test the exercise overview card component", () => {
27
27
  const category = new Category(10, "Abs");
28
28
  const equipment1 = new Equipment(10, "Kettlebell");
29
29
  const equipment2 = new Equipment(1, "Test 123");
30
- const exercise = new Exercise(
31
- 345,
32
- "c788d643-150a-4ac7-97ef-84643c6419bf",
33
- category,
34
- [equipment1, equipment2],
35
- [],
36
- [],
37
- [],
38
- null,
39
- [exerciseTranslation1, exerciseTranslation2]
40
- );
30
+ const exercise = new Exercise({
31
+ id: 345,
32
+ uuid: "c788d643-150a-4ac7-97ef-84643c6419bf",
33
+ category: category,
34
+ equipment: [equipment1, equipment2],
35
+ translations: [exerciseTranslation1, exerciseTranslation2]
36
+ });
41
37
 
42
38
  test("Render the overview card, no language selected -> use english", () => {
43
39
  // Act
@@ -7,6 +7,7 @@ import { MuscleFilter, MuscleFilterDropdown } from "components/Exercises/Filter/
7
7
  import { NameAutocompleter } from "components/Exercises/Filter/NameAutcompleter";
8
8
  import { Category } from "components/Exercises/models/category";
9
9
  import { Equipment } from "components/Exercises/models/equipment";
10
+ import { Exercise } from "components/Exercises/models/exercise";
10
11
  import { Muscle } from "components/Exercises/models/muscle";
11
12
  import { ExerciseGrid } from "components/Exercises/Overview/ExerciseGrid";
12
13
  import { ExerciseGridSkeleton } from "components/Exercises/Overview/ExerciseGridLoadingSkeleton";
@@ -15,7 +16,6 @@ import { useExercisesQuery } from "components/Exercises/queries";
15
16
  import React, { useContext, useMemo, useState } from "react";
16
17
  import { useTranslation } from "react-i18next";
17
18
  import { Link, useNavigate } from "react-router-dom";
18
- import { Exercise } from "components/Exercises/models/exercise";
19
19
  import { makeLink, WgerLink } from "utils/url";
20
20
  import { ExerciseFiltersContext } from './Filter/ExerciseFiltersContext';
21
21
  import { FilterDrawer } from './Filter/FilterDrawer';
@@ -73,7 +73,7 @@ const NoResultsBanner = () => {
73
73
  };
74
74
 
75
75
  export const ExerciseOverviewList = () => {
76
- const basesQuery = useExercisesQuery();
76
+ const exerciseQuery = useExercisesQuery();
77
77
  const [t, i18n] = useTranslation();
78
78
  const navigate = useNavigate();
79
79
  const { selectedCategories, selectedEquipment, selectedMuscles } = useContext(ExerciseFiltersContext);
@@ -91,7 +91,7 @@ export const ExerciseOverviewList = () => {
91
91
  };
92
92
 
93
93
  const filteredExercises = useMemo(() => {
94
- let filteredExercises = basesQuery.data || [];
94
+ let filteredExercises = exerciseQuery.data || [];
95
95
 
96
96
  // Filter exercise bases by categories
97
97
  if (selectedCategories.length > 0) {
@@ -123,7 +123,7 @@ export const ExerciseOverviewList = () => {
123
123
  }
124
124
 
125
125
  return filteredExercises;
126
- }, [basesQuery.data, selectedCategories, selectedEquipment, selectedMuscles]);
126
+ }, [exerciseQuery.data, selectedCategories, selectedEquipment, selectedMuscles]);
127
127
 
128
128
  // Should be a multiple of three, since there are three columns in the grid
129
129
  const ITEMS_PER_PAGE = 21;
@@ -140,7 +140,7 @@ export const ExerciseOverviewList = () => {
140
140
  return;
141
141
  }
142
142
 
143
- navigate(makeLink(WgerLink.EXERCISE_DETAIL, i18n.language, { id: exercise.id }));
143
+ navigate(makeLink(WgerLink.EXERCISE_DETAIL, i18n.language, { id: exercise.id! }));
144
144
  };
145
145
 
146
146
  return (
@@ -251,7 +251,7 @@ export const ExerciseOverviewList = () => {
251
251
  xs: 12,
252
252
  sm: 9
253
253
  }}>
254
- {basesQuery.isLoading
254
+ {exerciseQuery.isLoading
255
255
  ? <ExerciseGridSkeleton />
256
256
  : <>
257
257
  <ExerciseGrid exercises={paginatedExercises} />
@@ -15,8 +15,7 @@ export const ExerciseGrid = ({ exercises }: ExerciseGridProps) => {
15
15
 
16
16
  const languageQuery = useLanguageQuery();
17
17
 
18
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
19
- const [t, i18n] = useTranslation();
18
+ const { i18n } = useTranslation();
20
19
 
21
20
  let currentUserLanguage: Language | undefined;
22
21
  if (languageQuery.isSuccess) {
@@ -1,37 +1,94 @@
1
- import { Autocomplete, Chip, TextField } from "@mui/material";
1
+ import { Autocomplete, Chip, InputAdornment, TextField } from "@mui/material";
2
2
  import { useField } from "formik";
3
3
  import React from "react";
4
4
  import { useTranslation } from "react-i18next";
5
5
 
6
+ type AliasItem = { id?: number; alias: string };
7
+
6
8
  export function ExerciseAliases(props: { fieldName: string }) {
7
9
  const [t] = useTranslation();
8
- const [field, meta, helpers] = useField(props.fieldName);
10
+ const [field, meta, helpers] = useField<AliasItem[]>(props.fieldName);
11
+
12
+ const normalize = (items: (AliasItem | string)[] | null | undefined): AliasItem[] =>
13
+ (items || []).map(item =>
14
+ typeof item === "string" ? { alias: item } : ("alias" in item ? (item as AliasItem) : { alias: String(item) })
15
+ );
16
+
17
+ /**
18
+ * Extract a human-readable error string from the Yup alias validator, which
19
+ * returns a list of errors.
20
+ */
21
+ const formatError = (err: unknown): string | undefined => {
22
+ if (!err) return undefined;
23
+ if (typeof err === "string") return err;
24
+
25
+ if (typeof err === "object") {
26
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
27
+ const o = err as any;
28
+ if (typeof o.alias === "string") return o.alias;
29
+ if (typeof o.message === "string") return o.message;
30
+
31
+ for (const k of Object.keys(o)) {
32
+ const v = o[k];
33
+ if (typeof v === "string") return v;
34
+ if (v && typeof v === "object") {
35
+ if (typeof v.alias === "string") return v.alias;
36
+ if (typeof v.message === "string") return v.message;
37
+ }
38
+ }
39
+ }
40
+
41
+ return String(err);
42
+ };
9
43
 
10
44
  return <Autocomplete
11
45
  multiple
12
46
  freeSolo
13
47
  id={props.fieldName}
14
- value={field.value}
15
- options={field.value}
16
- onChange={(event, newValue) => {
17
- helpers.setValue(newValue);
48
+ value={field.value || []}
49
+ options={[]}
50
+ getOptionLabel={(opt) => (typeof opt === "string" ? opt : opt.alias)}
51
+ isOptionEqualToValue={(option, value) => option.alias === value.alias && (option.id === value.id || option.id === undefined || value.id === undefined)}
52
+ onChange={(_, newValue) => {
53
+ helpers.setValue(normalize(newValue));
18
54
  }}
19
- renderValue={(value: readonly string[], getTagProps) => {
20
- return value.map((option: string, index: number) => (
21
- <Chip label={option} {...getTagProps({ index })} key={index} />
55
+ onBlur={field.onBlur}
56
+ renderInput={(params) => {
57
+ const chips = (field.value || []).map((option, index) => (
58
+ <Chip
59
+ label={option.alias}
60
+ onDelete={() => {
61
+ const newVal = [...(field.value || [])];
62
+ newVal.splice(index, 1);
63
+ helpers.setValue(newVal);
64
+ }}
65
+ key={option.id ?? `${option.alias}-${index}`}
66
+ />
22
67
  ));
68
+
69
+ return (
70
+ <TextField
71
+ {...params}
72
+ id="exerciseAliases"
73
+ variant="standard"
74
+ label={t("exercises.alternativeNames")}
75
+ error={meta.touched && Boolean(meta.error)}
76
+ helperText={meta.touched ? formatError(meta.error) : undefined}
77
+ slotProps={{
78
+ input: {
79
+ startAdornment: (
80
+ <InputAdornment
81
+ position="start"
82
+ sx={{ display: "flex", gap: 0.5, alignItems: "center" }}
83
+ >
84
+ {chips}
85
+ {/*{params.InputProps?.startAdornment}*/}
86
+ </InputAdornment>
87
+ ),
88
+ },
89
+ }}
90
+ />
91
+ );
23
92
  }}
24
- onBlur={field.onBlur}
25
- renderInput={params => (
26
- <TextField
27
- {...params}
28
- id="exerciseAliases"
29
- variant="standard"
30
- label={t("exercises.alternativeNames")}
31
- error={meta.touched && Boolean(meta.error)}
32
- helperText={meta.touched && meta.error}
33
- value={field.value}
34
- />
35
- )}
36
93
  />;
37
94
  }
@@ -20,10 +20,13 @@ export const alternativeNameValidator = () => yup
20
20
  .ensure()
21
21
  .compact()
22
22
  .of(
23
- yup
24
- .string()
25
- .min(MIN_CHAR_NAME, i18n.t("forms.minLength", { 'chars': MIN_CHAR_NAME }))
26
- .max(MAX_CHAR_NAME, i18n.t("forms.maxLength", { 'chars': MAX_CHAR_NAME }))
23
+ yup.object({
24
+ id: yup.number().nullable(),
25
+ alias: yup.string()
26
+ .min(MIN_CHAR_NAME, i18n.t("forms.minLength", { 'chars': MIN_CHAR_NAME }))
27
+ .max(MAX_CHAR_NAME, i18n.t("forms.maxLength", { 'chars': MAX_CHAR_NAME }))
28
+ .required()
29
+ })
27
30
  );
28
31
 
29
32
  export const descriptionValidator = () => yup