@wger-project/react-components 25.12.5 → 26.2.26

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 (72) 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/ar/translation.json +347 -236
  5. package/build/locales/cs/translation.json +257 -253
  6. package/build/locales/de/translation.json +13 -3
  7. package/build/locales/en/translation.json +4 -1
  8. package/build/locales/es/translation.json +26 -4
  9. package/build/locales/fr/translation.json +10 -1
  10. package/build/locales/hi/translation.json +4 -1
  11. package/build/locales/hr/translation.json +15 -3
  12. package/build/locales/mk/translation.json +138 -0
  13. package/build/locales/nl/translation.json +118 -11
  14. package/build/locales/pt/translation.json +338 -338
  15. package/build/locales/pt_BR/translation.json +15 -3
  16. package/build/locales/ru/translation.json +43 -3
  17. package/build/locales/ta/translation.json +1 -1
  18. package/build/locales/uk/translation.json +13 -1
  19. package/build/locales/zh_Hans/translation.json +25 -1
  20. package/build/locales/zh_Hant/translation.json +255 -243
  21. package/build/main.js +170 -170
  22. package/build/main.js.map +1 -1
  23. package/package.json +15 -13
  24. package/src/components/BodyWeight/TableDashboard/TableDashboard.tsx +4 -6
  25. package/src/components/Calendar/Components/CalendarComponent.test.tsx +18 -22
  26. package/src/components/Calendar/Components/CalendarComponent.tsx +11 -8
  27. package/src/components/Calendar/Components/CalendarHeader.tsx +3 -3
  28. package/src/components/Calendar/Components/Entries.tsx +8 -3
  29. package/src/components/Dashboard/CalendarCard.tsx +16 -0
  30. package/src/components/Dashboard/ConfigurableDashboard.test.ts +129 -0
  31. package/src/components/Dashboard/ConfigurableDashboard.tsx +352 -89
  32. package/src/components/Dashboard/DashboardCard.tsx +3 -2
  33. package/src/components/Dashboard/MeasurementCard.test.tsx +75 -0
  34. package/src/components/Dashboard/MeasurementCard.tsx +101 -0
  35. package/src/components/Dashboard/RoutineCard.tsx +1 -1
  36. package/src/components/Dashboard/TrophiesCard.test.tsx +63 -0
  37. package/src/components/Dashboard/TrophiesCard.tsx +84 -0
  38. package/src/components/Dashboard/WeightCard.test.tsx +0 -10
  39. package/src/components/Measurements/Screens/MeasurementCategoryOverview.tsx +1 -1
  40. package/src/components/Measurements/models/Category.ts +13 -2
  41. package/src/components/Measurements/models/Entry.ts +13 -2
  42. package/src/components/Trophies/components/TrophiesDetail.test.tsx +34 -0
  43. package/src/components/Trophies/components/TrophiesDetail.tsx +88 -0
  44. package/src/components/Trophies/models/trophy.test.ts +33 -0
  45. package/src/components/Trophies/models/trophy.ts +75 -0
  46. package/src/components/Trophies/models/userTrophy.test.ts +38 -0
  47. package/src/components/Trophies/models/userTrophy.ts +67 -0
  48. package/src/components/Trophies/models/userTrophyProgression.test.ts +43 -0
  49. package/src/components/Trophies/models/userTrophyProgression.ts +68 -0
  50. package/src/components/Trophies/queries/trophies.ts +31 -0
  51. package/src/components/Trophies/services/trophies.ts +22 -0
  52. package/src/components/Trophies/services/userTrophies.ts +33 -0
  53. package/src/components/Trophies/services/userTrophyProgression.ts +16 -0
  54. package/src/components/WorkoutRoutines/Detail/RoutineDetail.tsx +1 -1
  55. package/src/components/WorkoutRoutines/Detail/TemplateDetail.tsx +1 -1
  56. package/src/components/WorkoutRoutines/models/Routine.test.ts +17 -0
  57. package/src/components/WorkoutRoutines/models/Routine.ts +20 -3
  58. package/src/components/WorkoutRoutines/widgets/RoutineDetailsCard.tsx +1 -1
  59. package/src/components/index.ts +0 -2
  60. package/src/pages/Calendar/index.tsx +2 -2
  61. package/src/pages/WeightOverview/index.tsx +1 -1
  62. package/src/routes.tsx +6 -1
  63. package/src/services/measurements.ts +10 -17
  64. package/src/tests/trophies/trophiesTestData.ts +80 -0
  65. package/src/utils/consts.ts +18 -3
  66. package/src/utils/url.test.ts +32 -1
  67. package/src/utils/url.ts +24 -3
  68. package/src/components/Carousel/carousel.module.css +0 -43
  69. package/src/components/Carousel/carousel.module.css.map +0 -1
  70. package/src/components/Carousel/carousel.module.scss +0 -46
  71. package/src/components/Carousel/index.tsx +0 -66
  72. package/src/components/Dashboard/GoalCard.tsx +0 -71
@@ -0,0 +1,67 @@
1
+ import { ApiTrophyType, Trophy } from "components/Trophies/models/trophy";
2
+ import { Adapter } from "utils/Adapter";
3
+
4
+ export interface ApiUserTrophyType {
5
+ id: number,
6
+ trophy: ApiTrophyType,
7
+ earned_at: string,
8
+ progress: number,
9
+ "is_notified": boolean,
10
+ }
11
+
12
+ export type UserTrophyConstructorParams = {
13
+ id: number;
14
+ trophy: Trophy;
15
+ earnedAt: Date;
16
+ progress: number;
17
+ isNotified: boolean;
18
+ };
19
+
20
+ /*
21
+ * A list of trophies earned by a user, along with their progress.
22
+ */
23
+ export class UserTrophy {
24
+
25
+ public id: number;
26
+ public trophy: Trophy;
27
+ public earnedAt: Date;
28
+ public progress: number;
29
+ public isNotified: boolean;
30
+
31
+ constructor(params: UserTrophyConstructorParams) {
32
+ this.id = params.id;
33
+ this.trophy = params.trophy;
34
+ this.earnedAt = params.earnedAt;
35
+ this.progress = params.progress;
36
+ this.isNotified = params.isNotified;
37
+ }
38
+
39
+ static fromJson(json: ApiUserTrophyType): UserTrophy {
40
+ return adapter.fromJson(json);
41
+ }
42
+
43
+ toJson() {
44
+ return adapter.toJson(this);
45
+ }
46
+ }
47
+
48
+ class UserTrophyAdapter implements Adapter<UserTrophy> {
49
+ fromJson(item: ApiUserTrophyType) {
50
+ return new UserTrophy({
51
+ id: item.id,
52
+ trophy: Trophy.fromJson(item.trophy),
53
+ earnedAt: new Date(item.earned_at),
54
+ progress: item.progress,
55
+ isNotified: item.is_notified,
56
+ });
57
+ }
58
+
59
+ toJson(trophy: UserTrophy) {
60
+ return {
61
+ id: trophy.id,
62
+ "is_notified": trophy.isNotified,
63
+ };
64
+ }
65
+ }
66
+
67
+ const adapter = new UserTrophyAdapter();
@@ -0,0 +1,43 @@
1
+ import { trophyType } from "components/Trophies/models/trophy";
2
+ import { UserTrophyProgression } from "components/Trophies/models/userTrophyProgression";
3
+
4
+
5
+ describe('Test the UserTrophyProgression model', () => {
6
+
7
+ test('correctly creates an object from the API response', () => {
8
+ // Arrange
9
+ const apiResponse = {
10
+ trophy: {
11
+ id: 1,
12
+ uuid: "5362e55b-eaf1-4e34-9ef8-661538a3bdd9",
13
+ name: "Beginner",
14
+ description: "Complete your first workout",
15
+ image: "http://localhost:8000/static/trophies/count/5362e55b-eaf1-4e34-9ef8-661538a3bdd9.png",
16
+ "trophy_type": "count" as trophyType,
17
+ "is_hidden": false,
18
+ "is_progressive": false,
19
+ order: 1
20
+ },
21
+ "is_earned": true,
22
+ "earned_at": "2025-12-19T13:48:07.513138+01:00",
23
+ progress: 100.0,
24
+ "current_value": null,
25
+ "target_value": null,
26
+ "progress_display": null
27
+ };
28
+
29
+ // Act
30
+ const userTrophyProgression = UserTrophyProgression.fromJson(apiResponse);
31
+
32
+ // Assert
33
+ expect(userTrophyProgression.isEarned).toBe(true);
34
+ expect(userTrophyProgression.earnedAt).toStrictEqual(new Date("2025-12-19T13:48:07.513138+01:00"));
35
+ expect(userTrophyProgression.progress).toBe(100.0);
36
+ expect(userTrophyProgression.currentValue).toBe(null);
37
+ expect(userTrophyProgression.targetValue).toBe(null);
38
+ expect(userTrophyProgression.progressDisplay).toBe(null);
39
+
40
+ expect(userTrophyProgression.trophy.uuid).toBe("5362e55b-eaf1-4e34-9ef8-661538a3bdd9");
41
+
42
+ });
43
+ });
@@ -0,0 +1,68 @@
1
+ import { ApiTrophyType, Trophy } from "components/Trophies/models/trophy";
2
+ import { Adapter } from "utils/Adapter";
3
+
4
+ export interface ApiUserTrophyType {
5
+ trophy: ApiTrophyType,
6
+ is_earned: boolean
7
+ earned_at: string | null,
8
+ progress: number,
9
+ current_value: number | null,
10
+ target_value: number | null,
11
+ progress_display: string | null,
12
+ }
13
+
14
+ export type UserTrophyConstructorParams = {
15
+ trophy: Trophy;
16
+ isEarned: boolean;
17
+ earnedAt: Date | null;
18
+ progress: number;
19
+ currentValue: number | null;
20
+ targetValue: number | null;
21
+ progressDisplay: string | null;
22
+ };
23
+
24
+ export class UserTrophyProgression {
25
+
26
+ public trophy: Trophy;
27
+ public earnedAt: Date | null;
28
+ public isEarned: boolean;
29
+ public progress: number;
30
+ public currentValue: number | null;
31
+ public targetValue: number | null;
32
+ public progressDisplay: string | null;
33
+
34
+ constructor(params: UserTrophyConstructorParams) {
35
+ this.trophy = params.trophy;
36
+ this.earnedAt = params.earnedAt;
37
+ this.isEarned = params.isEarned;
38
+ this.progress = params.progress;
39
+ this.currentValue = params.currentValue;
40
+ this.targetValue = params.targetValue;
41
+ this.progressDisplay = params.progressDisplay;
42
+ }
43
+
44
+ static fromJson(json: ApiUserTrophyType): UserTrophyProgression {
45
+ return adapter.fromJson(json);
46
+ }
47
+ }
48
+
49
+ class UserTrophyAdapter implements Adapter<UserTrophyProgression> {
50
+ fromJson(item: ApiUserTrophyType) {
51
+ return new UserTrophyProgression({
52
+ trophy: Trophy.fromJson(item.trophy),
53
+ earnedAt: item.earned_at !== null ? new Date(item.earned_at) : null,
54
+ isEarned: item.is_earned,
55
+ progress: item.progress,
56
+ currentValue: item.current_value,
57
+ targetValue: item.target_value,
58
+ progressDisplay: item.progress_display,
59
+ });
60
+ }
61
+
62
+ toJson(item: UserTrophyProgression) {
63
+ return {};
64
+ }
65
+ }
66
+
67
+
68
+ const adapter = new UserTrophyAdapter();
@@ -0,0 +1,31 @@
1
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
2
+ import { getTrophies } from "components/Trophies/services/trophies";
3
+ import { getUserTrophies, setTrophyAsNotified } from "components/Trophies/services/userTrophies";
4
+ import { getUserTrophyProgression } from "components/Trophies/services/userTrophyProgression";
5
+ import { QueryKey } from "utils/consts";
6
+
7
+ export const useTrophiesQuery = () => useQuery({
8
+ queryFn: () => getTrophies(),
9
+ queryKey: [QueryKey.TROPHIES],
10
+ });
11
+
12
+ export const useUserTrophiesQuery = () => useQuery({
13
+ queryFn: () => getUserTrophies(),
14
+ queryKey: [QueryKey.USER_TROPHIES],
15
+ });
16
+
17
+ export const useSetTrophyAsNotifiedQuery = () => {
18
+ const queryClient = useQueryClient();
19
+
20
+ return useMutation({
21
+ mutationFn: (trophyId: number) => setTrophyAsNotified(trophyId),
22
+ onSuccess: () => queryClient.invalidateQueries({
23
+ queryKey: [QueryKey.USER_TROPHIES]
24
+ })
25
+ });
26
+ };
27
+
28
+ export const useUserTrophyProgressionQuery = () => useQuery({
29
+ queryFn: () => getUserTrophyProgression(),
30
+ queryKey: [QueryKey.USER_TROPHY_PROGRESSION],
31
+ });
@@ -0,0 +1,22 @@
1
+ import { Trophy } from "components/Trophies/models/trophy";
2
+ import { API_MAX_PAGE_SIZE, ApiPath } from "utils/consts";
3
+ import { fetchPaginated } from "utils/requests";
4
+ import { makeHeader, makeUrl } from "utils/url";
5
+
6
+
7
+ export const getTrophies = async (): Promise<Trophy[]> => {
8
+
9
+ const url = makeUrl(
10
+ ApiPath.API_TROPHIES_PATH,
11
+ { query: { limit: API_MAX_PAGE_SIZE } }
12
+ );
13
+ const out: Trophy[] = [];
14
+
15
+ for await (const page of fetchPaginated(url, makeHeader())) {
16
+ for (const logData of page) {
17
+ out.push(Trophy.fromJson(logData));
18
+ }
19
+ }
20
+ return out;
21
+ };
22
+
@@ -0,0 +1,33 @@
1
+ import axios from "axios";
2
+ import { UserTrophy } from "components/Trophies/models/userTrophy";
3
+ import { API_MAX_PAGE_SIZE, ApiPath } from "utils/consts";
4
+ import { fetchPaginated } from "utils/requests";
5
+ import { makeHeader, makeUrl } from "utils/url";
6
+
7
+
8
+ export const getUserTrophies = async (): Promise<UserTrophy[]> => {
9
+
10
+ const url = makeUrl(
11
+ ApiPath.API_USER_TROPHIES_PATH,
12
+ { query: { limit: API_MAX_PAGE_SIZE } }
13
+ );
14
+ const out: UserTrophy[] = [];
15
+
16
+ for await (const page of fetchPaginated(url, makeHeader())) {
17
+ for (const logData of page) {
18
+ out.push(UserTrophy.fromJson(logData));
19
+ }
20
+ }
21
+ return out;
22
+ };
23
+
24
+
25
+ export const setTrophyAsNotified = async (trophyId: number): Promise<void> => {
26
+
27
+ await axios.patch(
28
+ makeUrl(ApiPath.API_USER_TROPHIES_PATH, { id: trophyId }),
29
+ { id: trophyId, "is_notified": true },
30
+ { headers: makeHeader() }
31
+ );
32
+
33
+ };
@@ -0,0 +1,16 @@
1
+ import axios from "axios";
2
+ import { ApiUserTrophyType, UserTrophyProgression } from "components/Trophies/models/userTrophyProgression";
3
+ import { ApiPath } from "utils/consts";
4
+ import { makeHeader, makeUrl } from "utils/url";
5
+
6
+
7
+ export const getUserTrophyProgression = async (): Promise<UserTrophyProgression[]> => {
8
+
9
+ const { data: trophyData } = await axios.get(
10
+ makeUrl(ApiPath.API_USER_TROPHY_PROGRESSION_PATH),
11
+ { headers: makeHeader() }
12
+ );
13
+
14
+ return trophyData.map((item: ApiUserTrophyType) => UserTrophyProgression.fromJson(item));
15
+ };
16
+
@@ -51,7 +51,7 @@ export const RoutineDetail = () => {
51
51
  href={makeLink(WgerLink.ROUTINE_COPY, i18n.language, { id: routineId })}
52
52
  variant={"contained"}
53
53
  >{t('routines.copyAndUseTemplate')}</Button>}
54
- {routine!.dayDataCurrentIterationNoNulls.map((dayData) =>
54
+ {routine!.dayDataCurrentIterationFiltered.map((dayData) =>
55
55
  <DayDetailsCard
56
56
  routineId={routineId}
57
57
  dayData={dayData}
@@ -48,7 +48,7 @@ export const TemplateDetail = () => {
48
48
  variant={"contained"}
49
49
  >{t('routines.copyAndUseTemplate')}</Button>
50
50
 
51
- {routine!.dayDataCurrentIterationNoNulls.map((dayData) =>
51
+ {routine!.dayDataCurrentIterationFiltered.map((dayData) =>
52
52
  <DayDetailsCard
53
53
  dayData={dayData}
54
54
  routineId={routineId}
@@ -110,4 +110,21 @@ describe('Routine model tests', () => {
110
110
  // Assert
111
111
  expect(routine.dayDataCurrentIteration).toEqual(testRoutineDayData1);
112
112
  });
113
+
114
+ test('correctly filters out null days', () => {
115
+
116
+ // Arrange
117
+ routine.dayData = [
118
+ ...testRoutineDayData1,
119
+ ...testRoutineDayData1,
120
+ ...testRoutineDayData1,
121
+ ];
122
+ routine.dayData[0].date = new Date('2026-01-01');
123
+ routine.dayData[1].date = new Date('2026-01-02');
124
+ routine.dayData[2].date = new Date('2026-01-03');
125
+
126
+ // Assert
127
+ expect(routine.dayDataCurrentIteration.length).toEqual(3);
128
+ expect(routine.dayDataCurrentIterationFiltered).toEqual(testRoutineDayData1);
129
+ });
113
130
  });
@@ -62,12 +62,29 @@ export class Routine {
62
62
  }
63
63
 
64
64
  /*
65
- * Filter out dayData entries with null days
65
+ * Filter out dayData entries with null days as well as duplicated days from
66
+ * the "fixed weekly schedule" toggle.
66
67
  */
67
- get dayDataCurrentIterationNoNulls() {
68
- return this.dayDataCurrentIteration
68
+ get dayDataCurrentIterationFiltered() {
69
+ const sorted = this.dayDataCurrentIteration
69
70
  .filter((dayData) => dayData.day !== null)
70
71
  .sort((a, b) => a.day!.order - b.day!.order);
72
+
73
+ // Filter out entries where the day is the same as the previous one. This is
74
+ // necessary because if the user has the "Fixed weekly schedule" option enabled,
75
+ // there would be multiple entries for the same day.
76
+ const unique: RoutineDayData[] = [];
77
+ for (const dd of sorted) {
78
+ if (unique.length === 0 || unique[unique.length - 1].day!.id !== dd.day!.id) {
79
+ unique.push(dd);
80
+ } else {
81
+ // If the day id is the same as the previous entry, replace it with the current one
82
+ // so the last occurrence is kept.
83
+ unique[unique.length - 1] = dd;
84
+ }
85
+ }
86
+
87
+ return unique;
71
88
  }
72
89
 
73
90
  get groupedDayDataByIteration() {
@@ -53,7 +53,7 @@ export const RoutineDetailsCard = () => {
53
53
  </Typography>
54
54
  }
55
55
  <Stack spacing={2} sx={{ mt: 2 }}>
56
- {routineQuery.data!.dayDataCurrentIteration.filter((dayData) => dayData.day !== null).map((dayData, index) =>
56
+ {routineQuery.data!.dayDataCurrentIterationFiltered.map((dayData, index) =>
57
57
  <DayDetailsCard routineId={routineId} dayData={dayData} key={`dayDetails-${index}`} />
58
58
  )}
59
59
  </Stack>
@@ -1,3 +1 @@
1
- export { BodyWeight } from './BodyWeight/index';
2
1
  export { Header } from './Header';
3
- export { Carousel, CarouselItem } from './Carousel';
@@ -4,7 +4,7 @@ import CalendarComponent from "../../components/Calendar/Components/CalendarComp
4
4
  export const Calendar = () => {
5
5
  return (
6
6
  <div>
7
- <CalendarComponent/>
7
+ <CalendarComponent />
8
8
  </div>
9
9
  );
10
- };
10
+ };
@@ -1,5 +1,5 @@
1
+ import { BodyWeight } from "components/BodyWeight";
1
2
  import React from 'react';
2
- import { BodyWeight } from 'components';
3
3
 
4
4
  export const WeightOverview = () => {
5
5
  return <BodyWeight />;
package/src/routes.tsx CHANGED
@@ -6,6 +6,7 @@ import { BmiCalculator } from "components/Nutrition/components/BmiCalculator";
6
6
  import { NutritionDiaryOverview } from "components/Nutrition/components/NutritionDiaryOverview";
7
7
  import { PlanDetail } from "components/Nutrition/components/PlanDetail";
8
8
  import { PlansOverview } from "components/Nutrition/components/PlansOverview";
9
+ import { TrophiesDetail } from "components/Trophies/components/TrophiesDetail";
9
10
  import { RoutineAdd } from "components/WorkoutRoutines/Detail/RoutineAdd";
10
11
  import { RoutineDetail } from "components/WorkoutRoutines/Detail/RoutineDetail";
11
12
  import { RoutineDetailsTable } from "components/WorkoutRoutines/Detail/RoutineDetailsTable";
@@ -123,10 +124,14 @@ export const WgerRoutes = () => {
123
124
  <Route index element={<ConfigurableDashboard />} />
124
125
  <Route path="" element={<ConfigurableDashboard />} />
125
126
  </Route>
127
+ <Route path="trophies">
128
+ <Route index element={<TrophiesDetail />} />
129
+ <Route path="" element={<TrophiesDetail />} />
130
+ </Route>
126
131
  </Route>
127
132
  <Route path="/" element={<ConfigurableDashboard />} />
128
133
 
129
- {/* This route matches when no other route match, so a 404 */}
134
+ {/* This route matches when no other route matches, so a 404 */}
130
135
  <Route
131
136
  path="*"
132
137
  element={
@@ -1,6 +1,6 @@
1
1
  import axios from 'axios';
2
- import { MeasurementCategory, MeasurementCategoryAdapter } from "components/Measurements/models/Category";
3
- import { MeasurementEntry, MeasurementEntryAdapter } from "components/Measurements/models/Entry";
2
+ import { MeasurementCategory } from "components/Measurements/models/Category";
3
+ import { MeasurementEntry } from "components/Measurements/models/Entry";
4
4
  import { ApiMeasurementCategoryType } from 'types';
5
5
  import { API_MAX_PAGE_SIZE } from "utils/consts";
6
6
  import { dateToYYYYMMDD } from "utils/date";
@@ -18,8 +18,6 @@ export type MeasurementQueryOptions = {
18
18
  export const getMeasurementCategories = async (options?: MeasurementQueryOptions): Promise<MeasurementCategory[]> => {
19
19
  const { filtersetQueryCategories = {}, filtersetQueryEntries = {} } = options || {};
20
20
 
21
- const adapter = new MeasurementCategoryAdapter();
22
- const entryAdapter = new MeasurementEntryAdapter();
23
21
  const categories: MeasurementCategory[] = [];
24
22
  const categoryUrl = makeUrl(API_MEASUREMENTS_CATEGORY_PATH, {
25
23
  query: {
@@ -30,7 +28,7 @@ export const getMeasurementCategories = async (options?: MeasurementQueryOptions
30
28
 
31
29
  for await (const page of fetchPaginated(categoryUrl, makeHeader())) {
32
30
  for (const catData of page) {
33
- categories.push(adapter.fromJson(catData));
31
+ categories.push(MeasurementCategory.fromJson(catData));
34
32
  }
35
33
  }
36
34
 
@@ -48,7 +46,7 @@ export const getMeasurementCategories = async (options?: MeasurementQueryOptions
48
46
  // Collect all pages of entries
49
47
  for await (const page of fetchPaginated(url, makeHeader())) {
50
48
  for (const entries of page) {
51
- out.push(entryAdapter.fromJson(entries));
49
+ out.push(MeasurementEntry.fromJson(entries));
52
50
  }
53
51
  }
54
52
  return out;
@@ -73,15 +71,14 @@ export const getMeasurementCategory = async (id: number): Promise<MeasurementCat
73
71
  { headers: makeHeader() },
74
72
  );
75
73
 
76
- const category = new MeasurementCategoryAdapter().fromJson(receivedCategories);
77
- const adapter = new MeasurementEntryAdapter();
74
+ const category = MeasurementCategory.fromJson(receivedCategories);
78
75
  const measurements: MeasurementEntry[] = [];
79
76
  const url = makeUrl(API_MEASUREMENTS_ENTRY_PATH, { query: { category: category.id } });
80
77
 
81
78
  // Collect all pages of entries
82
79
  for await (const page of fetchPaginated(url, makeHeader())) {
83
80
  for (const entries of page) {
84
- measurements.push(adapter.fromJson(entries));
81
+ measurements.push(MeasurementEntry.fromJson(entries));
85
82
  }
86
83
  }
87
84
 
@@ -105,8 +102,7 @@ export const addMeasurementCategory = async (data: AddMeasurementCategoryParams)
105
102
  { headers: makeHeader() }
106
103
  );
107
104
 
108
- const adapter = new MeasurementCategoryAdapter();
109
- return adapter.fromJson(response.data);
105
+ return MeasurementCategory.fromJson(response.data);
110
106
  };
111
107
 
112
108
  export interface editMeasurementCategoryParams {
@@ -125,8 +121,7 @@ export const editMeasurementCategory = async (data: editMeasurementCategoryParam
125
121
  { headers: makeHeader() }
126
122
  );
127
123
 
128
- const adapter = new MeasurementCategoryAdapter();
129
- return adapter.fromJson(response.data);
124
+ return MeasurementCategory.fromJson(response.data);
130
125
  };
131
126
 
132
127
  export const deleteMeasurementCategory = async (id: number): Promise<void> => {
@@ -157,8 +152,7 @@ export const editMeasurementEntry = async (data: editMeasurementParams): Promise
157
152
  { headers: makeHeader() }
158
153
  );
159
154
 
160
- const adapter = new MeasurementEntryAdapter();
161
- return adapter.fromJson(response.data);
155
+ return MeasurementEntry.fromJson(response.data);
162
156
  };
163
157
 
164
158
  export interface AddMeasurementParams {
@@ -181,6 +175,5 @@ export const addMeasurementEntry = async (data: AddMeasurementParams): Promise<M
181
175
  { headers: makeHeader() }
182
176
  );
183
177
 
184
- const adapter = new MeasurementEntryAdapter();
185
- return adapter.fromJson(response.data);
178
+ return MeasurementEntry.fromJson(response.data);
186
179
  };
@@ -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