@wger-project/react-components 25.12.5 → 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.
- package/build/assets/ajax-loader.gif +0 -0
- package/build/assets/index.css +1 -1
- package/build/assets/slick.svg +14 -0
- package/build/locales/de/translation.json +13 -3
- package/build/locales/en/translation.json +3 -0
- package/build/locales/es/translation.json +22 -3
- package/build/locales/fr/translation.json +10 -1
- package/build/locales/hi/translation.json +4 -1
- package/build/locales/nl/translation.json +118 -11
- package/build/locales/pt_BR/translation.json +12 -3
- package/build/locales/ru/translation.json +39 -3
- package/build/locales/uk/translation.json +10 -1
- package/build/main.js +169 -169
- package/build/main.js.map +1 -1
- package/package.json +4 -1
- package/src/components/BodyWeight/TableDashboard/TableDashboard.tsx +4 -6
- package/src/components/Calendar/Components/CalendarComponent.test.tsx +18 -22
- package/src/components/Calendar/Components/CalendarComponent.tsx +11 -8
- package/src/components/Calendar/Components/CalendarHeader.tsx +3 -3
- package/src/components/Calendar/Components/Entries.tsx +8 -3
- package/src/components/Dashboard/CalendarCard.tsx +16 -0
- package/src/components/Dashboard/ConfigurableDashboard.test.ts +128 -0
- package/src/components/Dashboard/ConfigurableDashboard.tsx +306 -55
- package/src/components/Dashboard/DashboardCard.tsx +3 -2
- package/src/components/Dashboard/MeasurementCard.test.tsx +75 -0
- package/src/components/Dashboard/MeasurementCard.tsx +101 -0
- package/src/components/Dashboard/TrophiesCard.test.tsx +63 -0
- package/src/components/Dashboard/TrophiesCard.tsx +84 -0
- package/src/components/Dashboard/WeightCard.test.tsx +0 -10
- package/src/components/Measurements/Screens/MeasurementCategoryOverview.tsx +1 -1
- package/src/components/Measurements/models/Category.ts +13 -2
- package/src/components/Measurements/models/Entry.ts +13 -2
- package/src/components/Trophies/components/TrophiesDetail.test.tsx +34 -0
- package/src/components/Trophies/components/TrophiesDetail.tsx +88 -0
- package/src/components/Trophies/models/trophy.test.ts +33 -0
- package/src/components/Trophies/models/trophy.ts +75 -0
- package/src/components/Trophies/models/userTrophy.test.ts +38 -0
- package/src/components/Trophies/models/userTrophy.ts +67 -0
- package/src/components/Trophies/models/userTrophyProgression.test.ts +43 -0
- package/src/components/Trophies/models/userTrophyProgression.ts +68 -0
- package/src/components/Trophies/queries/trophies.ts +31 -0
- package/src/components/Trophies/services/trophies.ts +22 -0
- package/src/components/Trophies/services/userTrophies.ts +33 -0
- package/src/components/Trophies/services/userTrophyProgression.ts +16 -0
- package/src/components/index.ts +0 -2
- package/src/pages/Calendar/index.tsx +2 -2
- package/src/pages/WeightOverview/index.tsx +1 -1
- package/src/routes.tsx +6 -1
- package/src/services/measurements.ts +10 -17
- package/src/tests/trophies/trophiesTestData.ts +80 -0
- package/src/utils/consts.ts +18 -3
- package/src/utils/url.test.ts +32 -1
- package/src/utils/url.ts +24 -3
- package/src/components/Carousel/carousel.module.css +0 -43
- package/src/components/Carousel/carousel.module.css.map +0 -1
- package/src/components/Carousel/carousel.module.scss +0 -46
- package/src/components/Carousel/index.tsx +0 -66
- package/src/components/Dashboard/GoalCard.tsx +0 -71
|
@@ -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
|
+
|
package/src/components/index.ts
CHANGED
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
|
|
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
|
|
3
|
-
import { MeasurementEntry
|
|
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(
|
|
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(
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
};
|
package/src/utils/consts.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
package/src/utils/url.test.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
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
|
-
|
|
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"}
|