@wger-project/react-components 25.11.22 → 26.1.18

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 (69) hide show
  1. package/build/assets/ajax-loader.gif +0 -0
  2. package/build/assets/index.css +1 -1
  3. package/build/assets/slick.svg +14 -0
  4. package/build/locales/de/translation.json +25 -6
  5. package/build/locales/en/translation.json +12 -0
  6. package/build/locales/es/translation.json +22 -3
  7. package/build/locales/fr/translation.json +10 -1
  8. package/build/locales/hi/translation.json +8 -0
  9. package/build/locales/nl/translation.json +350 -239
  10. package/build/locales/pt_BR/translation.json +349 -255
  11. package/build/locales/ru/translation.json +39 -3
  12. package/build/locales/uk/translation.json +10 -1
  13. package/build/main.js +170 -166
  14. package/build/main.js.map +1 -1
  15. package/package.json +8 -2
  16. package/src/components/BodyWeight/TableDashboard/TableDashboard.tsx +4 -6
  17. package/src/components/Calendar/Components/CalendarComponent.test.tsx +18 -22
  18. package/src/components/Calendar/Components/CalendarComponent.tsx +11 -8
  19. package/src/components/Calendar/Components/CalendarHeader.tsx +3 -3
  20. package/src/components/Calendar/Components/Entries.tsx +8 -3
  21. package/src/components/Dashboard/CalendarCard.tsx +16 -0
  22. package/src/components/Dashboard/ConfigurableDashboard.test.ts +128 -0
  23. package/src/components/Dashboard/ConfigurableDashboard.tsx +479 -0
  24. package/src/components/Dashboard/DashboardCard.tsx +122 -0
  25. package/src/components/Dashboard/EmptyCard.tsx +3 -3
  26. package/src/components/Dashboard/MeasurementCard.test.tsx +75 -0
  27. package/src/components/Dashboard/MeasurementCard.tsx +101 -0
  28. package/src/components/Dashboard/NutritionCard.tsx +88 -96
  29. package/src/components/Dashboard/RoutineCard.tsx +54 -69
  30. package/src/components/Dashboard/TrophiesCard.test.tsx +63 -0
  31. package/src/components/Dashboard/TrophiesCard.tsx +84 -0
  32. package/src/components/Dashboard/WeightCard.test.tsx +0 -10
  33. package/src/components/Dashboard/WeightCard.tsx +36 -42
  34. package/src/components/Exercises/Detail/Head/ExerciseDeleteDialog.tsx +1 -1
  35. package/src/components/Measurements/Screens/MeasurementCategoryOverview.tsx +1 -1
  36. package/src/components/Measurements/models/Category.ts +13 -2
  37. package/src/components/Measurements/models/Entry.ts +13 -2
  38. package/src/components/Trophies/components/TrophiesDetail.test.tsx +34 -0
  39. package/src/components/Trophies/components/TrophiesDetail.tsx +88 -0
  40. package/src/components/Trophies/models/trophy.test.ts +33 -0
  41. package/src/components/Trophies/models/trophy.ts +75 -0
  42. package/src/components/Trophies/models/userTrophy.test.ts +38 -0
  43. package/src/components/Trophies/models/userTrophy.ts +67 -0
  44. package/src/components/Trophies/models/userTrophyProgression.test.ts +43 -0
  45. package/src/components/Trophies/models/userTrophyProgression.ts +68 -0
  46. package/src/components/Trophies/queries/trophies.ts +31 -0
  47. package/src/components/Trophies/services/trophies.ts +22 -0
  48. package/src/components/Trophies/services/userTrophies.ts +33 -0
  49. package/src/components/Trophies/services/userTrophyProgression.ts +16 -0
  50. package/src/components/WorkoutRoutines/Detail/WorkoutStats.tsx +1 -1
  51. package/src/components/WorkoutRoutines/widgets/forms/DayTypeSelect.tsx +1 -2
  52. package/src/components/WorkoutRoutines/widgets/forms/SlotForm.tsx +0 -4
  53. package/src/components/index.ts +0 -2
  54. package/src/index.tsx +0 -46
  55. package/src/pages/Calendar/index.tsx +2 -2
  56. package/src/pages/WeightOverview/index.tsx +1 -1
  57. package/src/routes.tsx +87 -79
  58. package/src/services/exerciseTranslation.ts +5 -6
  59. package/src/services/measurements.ts +10 -17
  60. package/src/services/video.test.ts +4 -4
  61. package/src/tests/trophies/trophiesTestData.ts +80 -0
  62. package/src/utils/consts.ts +18 -3
  63. package/src/utils/url.test.ts +32 -1
  64. package/src/utils/url.ts +24 -3
  65. package/src/components/Carousel/carousel.module.css +0 -43
  66. package/src/components/Carousel/carousel.module.css.map +0 -1
  67. package/src/components/Carousel/carousel.module.scss +0 -46
  68. package/src/components/Carousel/index.tsx +0 -66
  69. package/src/components/Dashboard/Dashboard.tsx +0 -22
@@ -0,0 +1,80 @@
1
+ import { Trophy } from "components/Trophies/models/trophy";
2
+ import { UserTrophy } from "components/Trophies/models/userTrophy";
3
+ import { UserTrophyProgression } from "components/Trophies/models/userTrophyProgression";
4
+
5
+ export const testTrophies = () => {
6
+ return [
7
+ new Trophy({
8
+ id: 123,
9
+ type: 'other',
10
+ isHidden: false,
11
+ uuid: 'trophy-123',
12
+ name: 'Beginner',
13
+ description: 'Complete your first workout',
14
+ image: 'https://example.com/images/beginner.png',
15
+ isProgressive: false,
16
+ }),
17
+ new Trophy({
18
+ id: 456,
19
+ type: 'count',
20
+ isHidden: false,
21
+ uuid: 'trophy-456',
22
+ name: 'Unstoppable',
23
+ description: 'Maintain a 30-day workout streak',
24
+ image: 'https://example.com/images/unstoppable.png',
25
+ isProgressive: true,
26
+ }),
27
+ new Trophy({
28
+ id: 789,
29
+ type: 'other',
30
+ isHidden: true,
31
+ uuid: 'trophy-789',
32
+ name: 'Secret Trophy',
33
+ description: 'This is a super secret trophy',
34
+ image: 'https://example.com/images/secret.png',
35
+ isProgressive: false,
36
+ })
37
+ ];
38
+ };
39
+
40
+ export const testUserProgressionTrophies = () => {
41
+ return [
42
+ new UserTrophyProgression({
43
+ trophy: testTrophies()[0],
44
+ isEarned: true,
45
+ earnedAt: new Date('2025-12-19T10:00:00Z'),
46
+ progress: 100,
47
+ currentValue: null,
48
+ targetValue: null,
49
+ progressDisplay: null,
50
+ }),
51
+ new UserTrophyProgression({
52
+ trophy: testTrophies()[1],
53
+ isEarned: false,
54
+ earnedAt: new Date('2025-12-19T10:00:00Z'),
55
+ progress: 13.333333333333334,
56
+ currentValue: 4,
57
+ targetValue: 30,
58
+ progressDisplay: '4/30',
59
+ }),
60
+ ];
61
+ };
62
+
63
+ export const testUserTrophies = () => {
64
+ return [
65
+ new UserTrophy({
66
+ id: 123,
67
+ isNotified: true,
68
+ trophy: testTrophies()[0],
69
+ earnedAt: new Date('2025-12-19T10:00:00Z'),
70
+ progress: 100,
71
+ }),
72
+ new UserTrophy({
73
+ id: 456,
74
+ isNotified: true,
75
+ trophy: testTrophies()[1],
76
+ earnedAt: new Date('2025-12-19T10:00:00Z'),
77
+ progress: 100,
78
+ }),
79
+ ];
80
+ };
@@ -35,6 +35,8 @@ export const QUERY_MEASUREMENTS_CATEGORIES = 'measurements-categories';
35
35
  * These don't have any meaning, they just need to be globally unique
36
36
  */
37
37
  export enum QueryKey {
38
+
39
+ // Routines
38
40
  ROUTINE_OVERVIEW = 'routine-overview',
39
41
  ROUTINE_DETAIL = 'routine-detail',
40
42
  SESSION_SEARCH = 'session-search',
@@ -46,20 +48,23 @@ export enum QueryKey {
46
48
  ROUTINES_SHALLOW = 'routines-shallow',
47
49
  PRIVATE_TEMPLATES = 'private-templates',
48
50
  PUBLIC_TEMPLATES = 'public-templates',
51
+ ROUTINE_WEIGHT_UNITS = 'weight-units',
52
+ ROUTINE_REP_UNITS = 'rep-units',
49
53
 
54
+ // Nutrition
50
55
  NUTRITIONAL_PLANS = 'nutritional-plans',
51
56
  NUTRITIONAL_PLAN = 'nutritional-plan',
52
57
  NUTRITIONAL_PLAN_DIARY = 'nutritional-plan-diary',
53
58
  NUTRITIONAL_PLAN_LAST = 'nutritional-plan-last',
54
59
  INGREDIENT = 'ingredient',
55
60
 
61
+ // Body weight
56
62
  BODY_WEIGHT = 'body-weight',
57
63
 
58
- ROUTINE_WEIGHT_UNITS = 'weight-units',
59
- ROUTINE_REP_UNITS = 'rep-units',
60
-
64
+ // Profile
61
65
  QUERY_PROFILE = 'profile',
62
66
 
67
+ // Exercises
63
68
  EXERCISES = 'exercises',
64
69
  EXERCISE_VARIATIONS = 'variations',
65
70
  EXERCISE_DETAIL = 'detail',
@@ -68,6 +73,11 @@ export enum QueryKey {
68
73
  EQUIPMENT = 'equipment',
69
74
  MUSCLES = 'muscles',
70
75
  QUERY_NOTES = 'notes',
76
+
77
+ // Trophies
78
+ TROPHIES = 'trophies',
79
+ USER_TROPHIES = 'user-trophies',
80
+ USER_TROPHY_PROGRESSION = 'user-trophy-progression',
71
81
  }
72
82
 
73
83
  /*
@@ -106,6 +116,11 @@ export enum ApiPath {
106
116
 
107
117
  // Profile
108
118
  API_PROFILE_PATH = 'userprofile',
119
+
120
+ // Trophies
121
+ API_TROPHIES_PATH = 'trophy',
122
+ API_USER_TROPHIES_PATH = 'user-trophy',
123
+ API_USER_TROPHY_PROGRESSION_PATH = 'trophy/progress',
109
124
  }
110
125
 
111
126
 
@@ -1,4 +1,5 @@
1
- import { makeHeader, makeLink, makeUrl, WgerLink } from "utils/url";
1
+ import i18n from "i18n";
2
+ import { getAcceptLanguage, makeHeader, makeLink, makeUrl, WgerLink } from "utils/url";
2
3
 
3
4
  describe("test url utility", () => {
4
5
 
@@ -67,6 +68,7 @@ describe("test the header utility", () => {
67
68
  test('generate header', () => {
68
69
  const result = makeHeader('123');
69
70
  expect(result).toStrictEqual({
71
+ 'Accept-Language': 'en',
70
72
  'Authorization': `Token 123`,
71
73
  'Content-Type': 'application/json',
72
74
  });
@@ -75,6 +77,7 @@ describe("test the header utility", () => {
75
77
  test('generate header - token from config', () => {
76
78
  const result = makeHeader();
77
79
  expect(result).toStrictEqual({
80
+ 'Accept-Language': 'en',
78
81
  'Authorization': `Token 122333444455555666666`,
79
82
  'Content-Type': 'application/json',
80
83
  });
@@ -123,5 +126,33 @@ describe("test the makeLink helper", () => {
123
126
  const result = makeLink(WgerLink.WEIGHT_OVERVIEW, 'de',);
124
127
  expect(result).toEqual('/de/weight/overview');
125
128
  });
129
+ });
130
+
131
+ describe("test the getAcceptLanguage helper", () => {
132
+
133
+ beforeEach(() => {
134
+ // @ts-expect-error - test helper assignment
135
+ i18n.languages = undefined;
136
+ });
137
+
138
+ test("returns default when languages is undefined", () => {
139
+ // @ts-expect-error - test helper assignment
140
+ i18n.languages = undefined;
141
+ expect(getAcceptLanguage()).toBe("en");
142
+ });
126
143
 
144
+ test("returns default when languages is an empty array", () => {
145
+ i18n.languages = [];
146
+ expect(getAcceptLanguage()).toBe("en");
147
+ });
148
+
149
+ test("returns single language code", () => {
150
+ i18n.languages = ["de"];
151
+ expect(getAcceptLanguage()).toBe("de");
152
+ });
153
+
154
+ test("joins multiple language codes with comma", () => {
155
+ i18n.languages = ["de-DE", "fr-FR"];
156
+ expect(getAcceptLanguage()).toBe("de-DE, fr-FR");
157
+ });
127
158
  });
package/src/utils/url.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { AxiosRequestConfig } from "axios";
2
2
  import { IS_PROD, VITE_API_KEY, VITE_API_SERVER } from "config";
3
+ import i18n from "i18n";
3
4
  import slug from "slug";
4
5
 
5
6
  interface makeUrlInterface {
@@ -50,8 +51,10 @@ export function makeUrl(path: string, params?: makeUrlInterface) {
50
51
 
51
52
 
52
53
  export enum WgerLink {
54
+ // Dashboard
53
55
  DASHBOARD,
54
56
 
57
+ // Routines
55
58
  ROUTINE_OVERVIEW,
56
59
  ROUTINE_DETAIL,
57
60
  ROUTINE_EDIT,
@@ -70,16 +73,20 @@ export enum WgerLink {
70
73
  PRIVATE_TEMPLATE_OVERVIEW,
71
74
  PUBLIC_TEMPLATE_OVERVIEW,
72
75
 
76
+ // Exercises
73
77
  EXERCISE_DETAIL,
74
78
  EXERCISE_OVERVIEW,
75
79
  EXERCISE_CONTRIBUTE,
76
80
 
81
+ // Body weight
77
82
  WEIGHT_OVERVIEW,
78
83
  WEIGHT_ADD,
79
84
 
85
+ // Measurements
80
86
  MEASUREMENT_OVERVIEW,
81
87
  MEASUREMENT_DETAIL,
82
88
 
89
+ // Nutrition
83
90
  NUTRITION_OVERVIEW,
84
91
  NUTRITION_DETAIL,
85
92
  NUTRITION_PLAN_PDF,
@@ -88,7 +95,9 @@ export enum WgerLink {
88
95
 
89
96
  INGREDIENT_DETAIL,
90
97
 
91
- CALENDAR
98
+ // Other
99
+ CALENDAR,
100
+ TROPHIES,
92
101
  }
93
102
 
94
103
  type UrlParams = { id: number, id2?: number, slug?: string, date?: string };
@@ -182,6 +191,9 @@ export function makeLink(link: WgerLink, language?: string, params?: UrlParams):
182
191
  case WgerLink.INGREDIENT_DETAIL:
183
192
  return `/${language}/nutrition/ingredient/${params!.id}/view`;
184
193
 
194
+ case WgerLink.TROPHIES:
195
+ return `/${language}/trophies`;
196
+
185
197
  // Dashboard
186
198
  case WgerLink.DASHBOARD:
187
199
  default:
@@ -222,7 +234,7 @@ export function makeHeader(token?: string) {
222
234
 
223
235
  const out: AxiosRequestConfig['headers'] = {};
224
236
  out['Content-Type'] = 'application/json';
225
-
237
+ out['Accept-Language'] = getAcceptLanguage();
226
238
  if (token) {
227
239
  out['Authorization'] = `Token ${token}`;
228
240
  }
@@ -231,7 +243,16 @@ export function makeHeader(token?: string) {
231
243
  if (IS_PROD && csrfCookie != undefined) {
232
244
  out['X-CSRFToken'] = csrfCookie;
233
245
  }
234
-
235
246
  return out;
236
247
  }
237
248
 
249
+ export function getAcceptLanguage(): string {
250
+ const languages = i18n.languages || [];
251
+ if (languages.length === 0) {
252
+ return 'en';
253
+ }
254
+
255
+ return languages.map((language) => {
256
+ return language;
257
+ }).join(', ');
258
+ }
@@ -1,43 +0,0 @@
1
- .carousel {
2
- overflow: hidden;
3
- margin: 2rem 0;
4
- }
5
- @media screen and (min-width: 700px) {
6
- .carousel {
7
- display: none;
8
- }
9
- }
10
- .carousel .inner {
11
- white-space: nowrap;
12
- transition: transform 0.3s;
13
- }
14
-
15
- .carousel_item {
16
- display: inline-flex;
17
- align-items: center;
18
- justify-content: center;
19
- height: 200px;
20
- background-color: #7795bd;
21
- color: #fff;
22
- border-radius: 10px;
23
- }
24
- .indicators {
25
- display: flex;
26
- justify-content: center;
27
- }
28
-
29
- .indicators > button {
30
- margin: 5px;
31
- border: none;
32
- background-color: #2a4c7d;
33
- color: #fff;
34
- border-radius: 50%;
35
- cursor: pointer;
36
- }
37
-
38
- .indicators > button.active {
39
- background-color: green;
40
- color: #fff;
41
- }
42
-
43
- /*# sourceMappingURL=carousel.module.css.map */
@@ -1 +0,0 @@
1
- {"version":3,"sourceRoot":"","sources":["carousel.module.scss"],"names":[],"mappings":"AAAA;EACI;EACA;;AAEA;EAJJ;IAKQ;;;AAGJ;EACI;EACA;;;AAIN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAOF;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA","file":"carousel.module.css"}
@@ -1,46 +0,0 @@
1
- .carousel {
2
- overflow: hidden;
3
- margin: 2rem 0;
4
-
5
- @media screen and (min-width: 700px) {
6
- display: none;
7
- }
8
-
9
- .inner {
10
- white-space: nowrap;
11
- transition: transform 0.3s;
12
- }
13
- }
14
-
15
- .carousel_item {
16
- display: inline-flex;
17
- align-items: center;
18
- justify-content: center;
19
- height: 200px;
20
- background-color: rgb(119, 149, 189);
21
- color: #fff;
22
- border-radius: 10px;
23
-
24
- img {
25
-
26
- }
27
- }
28
-
29
- .indicators {
30
- display: flex;
31
- justify-content: center;
32
- }
33
-
34
- .indicators > button {
35
- margin: 5px;
36
- border: none;
37
- background-color: #2a4c7d;
38
- color: #fff;
39
- border-radius: 50%;
40
- cursor: pointer;
41
- }
42
-
43
- .indicators > button.active {
44
- background-color: green;
45
- color: #fff;
46
- }
@@ -1,66 +0,0 @@
1
- import React, { useEffect, useState } from 'react';
2
- import styles from './carousel.module.css';
3
-
4
- export interface CarouselItemProps {
5
- children: React.ReactNode;
6
- }
7
-
8
- export const CarouselItem = ({ children }: CarouselItemProps) => {
9
- return (
10
- <div className={styles.carousel_item}>
11
- {children}
12
- </div>
13
- );
14
- };
15
-
16
- export interface CarouselProps {
17
- children: React.ReactNode | React.ReactNode[];
18
- }
19
-
20
- export const Carousel: React.FC<CarouselProps> = ({ children }: CarouselProps) => {
21
- const [activeIndex, setActiveIndex] = useState(0);
22
-
23
- const updateIndex = (newIndex: number) => {
24
- if (newIndex < 0) {
25
- newIndex = React.Children.count(children) - 1;
26
-
27
- } else if (newIndex >= React.Children.count(children)) {
28
- newIndex = 0;
29
- }
30
-
31
- setActiveIndex(newIndex);
32
- };
33
-
34
- useEffect(() => {
35
-
36
- });
37
-
38
- return (
39
- <div className={styles.carousel}>
40
- <div
41
- className={styles.inner}
42
- style={{ transform: `translateX(-${activeIndex * 100}%)` }}
43
- >
44
- {React.Children.map(children, (child) => {
45
- if (React.isValidElement(child)) {
46
- return React.cloneElement(child);
47
- }
48
- })}
49
- </div>
50
- <div className={styles.indicators}>
51
- {React.Children.map(children, (child, index) => {
52
- return (
53
- <button
54
- className={`${index === activeIndex ? "active" : ""}`}
55
- onClick={() => {
56
- updateIndex(index);
57
- }}
58
- >
59
- {index + 1}
60
- </button>
61
- );
62
- })}
63
- </div>
64
- </div>
65
- );
66
- };
@@ -1,22 +0,0 @@
1
- import Grid from '@mui/material/Grid';
2
- import { NutritionCard } from "components/Dashboard/NutritionCard";
3
- import { RoutineCard } from "components/Dashboard/RoutineCard";
4
- import { WeightCard } from "components/Dashboard/WeightCard";
5
- import React from 'react';
6
-
7
- export const Dashboard = () => {
8
-
9
- return (
10
- <Grid container spacing={2}>
11
- <Grid size={{ xs: 12, sm: 4 }}>
12
- <RoutineCard />
13
- </Grid>
14
- <Grid size={{ xs: 12, sm: 4 }}>
15
- <NutritionCard />
16
- </Grid>
17
- <Grid size={{ xs: 12, sm: 4 }}>
18
- <WeightCard />
19
- </Grid>
20
- </Grid>
21
- );
22
- };