• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wger-project / react / 18075690066

28 Sep 2025 02:32PM UTC coverage: 75.232% (-0.4%) from 75.672%
18075690066

push

github

web-flow
Merge pull request #1126 from wger-project/feature/exercise-submission

Use new endpoint for exercise submission

1723 of 2590 branches covered (66.53%)

Branch coverage included in aggregate %.

64 of 120 new or added lines in 14 files covered. (53.33%)

6 existing lines in 1 file now uncovered.

5500 of 7011 relevant lines covered (78.45%)

28.07 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.83
/src/components/Exercises/Add/Step6Overview.tsx
1
import NavigateNextIcon from '@mui/icons-material/NavigateNext';
2✔
2
import {
2✔
3
    Alert,
4
    AlertTitle,
5
    Box,
6
    Button,
7
    ImageListItem,
8
    Table,
9
    TableBody,
10
    TableCell,
11
    TableContainer,
12
    TableRow,
13
    Typography
14
} from "@mui/material";
15
import Grid from '@mui/material/Grid';
2✔
16
import ImageList from "@mui/material/ImageList";
2✔
17
import { LoadingPlaceholder } from "components/Core/LoadingWidget/LoadingWidget";
2✔
18
import { FormQueryErrors } from "components/Core/Widgets/FormError";
2✔
19
import { StepProps } from "components/Exercises/Add/AddExerciseStepper";
20
import {
2✔
21
    useAddExerciseFullQuery,
22
    useCategoriesQuery,
23
    useEquipmentQuery,
24
    useLanguageQuery,
25
    useMusclesQuery
26
} from "components/Exercises/queries";
27
import { useProfileQuery } from "components/User/queries/profile";
2✔
28
import React from "react";
2✔
29
import { useTranslation } from "react-i18next";
2✔
30
import { useNavigate } from "react-router-dom";
2✔
31
import { postExerciseImage } from "services";
2✔
32
import { useExerciseSubmissionStateValue } from "state";
2✔
33
import { ENGLISH_LANGUAGE_ID } from "utils/consts";
2✔
34
import { makeLink, WgerLink } from "utils/url";
2✔
35

36
export const Step6Overview = ({ onBack }: StepProps) => {
2✔
37
    const [t, i18n] = useTranslation();
2✔
38
    const [state] = useExerciseSubmissionStateValue();
2✔
39

40
    const navigate = useNavigate();
2✔
41
    const categoryQuery = useCategoriesQuery();
2✔
42
    const languageQuery = useLanguageQuery();
2✔
43
    const musclesQuery = useMusclesQuery();
2✔
44
    const equipmentQuery = useEquipmentQuery();
2✔
45
    const profileQuery = useProfileQuery();
2✔
46

47
    const addExerciseSubmissionMutation = useAddExerciseFullQuery();
2✔
48

49
    const submitExercise = async () => {
2✔
50

51
        // Create the exercise
NEW
52
        const exerciseId = await addExerciseSubmissionMutation.mutateAsync({
×
53
            exercise: {
54
                categoryId: state.category as number,
55
                equipmentIds: state.equipment,
56
                muscleIds: state.muscles,
57
                secondaryMuscleIds: state.musclesSecondary,
58
            },
59
            author: profileQuery.data!.username,
60
            variations: state.variationId,
61
            variationsConnectTo: state.newVariationExerciseId,
62
            translations: [
63
                {
64
                    language: ENGLISH_LANGUAGE_ID,
65
                    name: state.nameEn,
66
                    description: state.descriptionEn,
67
                    aliases: [
NEW
68
                        ...state.alternativeNamesEn.map(name => ({ alias: name }))
×
69
                    ],
70
                    comments: [
NEW
71
                        ...state.notesEn.map(name => ({ comment: name }))
×
72
                    ]
73
                },
74
                ...(state.languageId !== null ? [{
×
75
                    language: state.languageId,
76
                    name: state.nameI18n,
77
                    description: state.descriptionI18n,
78
                    aliases: [
NEW
79
                        ...state.alternativeNamesI18n.map(name => ({ alias: name }))
×
80
                    ],
81
                    comments: [
NEW
82
                        ...state.notesI18n.map(name => ({ comment: name }))
×
83
                    ]
84
                }] : [])
85
            ],
86
        });
87

88
        // Post the images individually, it seems to be more reliable this way
89
        // and most problems were happening when creating the exercise itself
90
        for (const image of state.images) {
×
91
            await postExerciseImage({
×
92
                exerciseId: exerciseId,
93
                image: image.file,
94
                imageData: image,
95
            });
96
        }
97

98
    };
99

100
    const variationText =
101
        state.variationId !== null
2!
102
            ? `Using variation ID ${state.variationId}`
103
            : state.newVariationExerciseId !== null
2!
104
                ? `Connecting to exercise ${state.newVariationExerciseId}`
105
                : '';
106

107
    return equipmentQuery.isLoading || languageQuery.isLoading || musclesQuery.isLoading || categoryQuery.isLoading
2!
108
        ? <LoadingPlaceholder />
109
        : <>
110
            <Typography variant={"h6"}>
111
                {t('exercises.step1HeaderBasics')}
112
            </Typography>
113
            <TableContainer>
114
                <Table>
115
                    <TableBody>
116
                        <TableRow>
117
                            <TableCell>{t('name')}</TableCell>
118
                            <TableCell>{state.nameEn}</TableCell>
119
                        </TableRow>
120
                        <TableRow>
121
                            <TableCell>{t('exercises.alternativeNames')}</TableCell>
122
                            <TableCell>{state.alternativeNamesEn.join(", ")}</TableCell>
123
                        </TableRow>
124
                        <TableRow>
125
                            <TableCell>{t('description')}</TableCell>
126
                            <TableCell>{state.descriptionEn}</TableCell>
127
                        </TableRow>
128
                        <TableRow>
129
                            <TableCell>{t('exercises.notes')}</TableCell>
130
                            <TableCell>{state.notesEn.map(note => <>{note}<br /></>)}</TableCell>
×
131
                        </TableRow>
132
                        <TableRow>
133
                            <TableCell>{t('category')}</TableCell>
134
                            <TableCell>{categoryQuery.data!.find(c => c.id === state.category)!.translatedName}</TableCell>
2✔
135
                        </TableRow>
136
                        <TableRow>
137
                            <TableCell>{t('exercises.equipment')}</TableCell>
138
                            <TableCell>{state.equipment.map(e => equipmentQuery.data!.find(value => value.id === e)!.translatedName).join(', ')}</TableCell>
4✔
139
                        </TableRow>
140
                        <TableRow>
141
                            <TableCell>{t('exercises.muscles')}</TableCell>
142
                            <TableCell>{state.muscles.map(m => musclesQuery.data!.find(value => value.id === m)!.getName()).join(', ')}</TableCell>
4✔
143
                        </TableRow>
144
                        <TableRow>
145
                            <TableCell>{t('exercises.secondaryMuscles')}</TableCell>
146
                            <TableCell>{state.musclesSecondary.map(m => musclesQuery.data!.find(value => value.id === m)!.getName()).join(', ')}</TableCell>
×
147
                        </TableRow>
148
                        <TableRow>
149
                            <TableCell>{t('exercises.variations')}</TableCell>
150
                            <TableCell>{variationText}</TableCell>
151
                        </TableRow>
152
                    </TableBody>
153
                </Table>
154
            </TableContainer>
155
            {state.images.length > 0 && (
2!
156
                <ImageList
157
                    cols={3}
158
                    style={{ maxHeight: "200px", }}>
159
                    {state.images.map(imageEntry => (
160
                        <ImageListItem key={imageEntry.url}>
×
161
                            <img
162
                                style={{ maxHeight: "200px", maxWidth: "200px" }}
163
                                src={imageEntry.url}
164
                                alt=""
165
                                loading="lazy"
166
                            />
167
                        </ImageListItem>
168
                    ))}
169
                </ImageList>
170
            )}
171

172

173
            {state.languageId !== null && (
4✔
174
                <>
175
                    <Typography variant={"h6"} sx={{ mt: 3 }}>
176
                        {languageQuery.data!.find(l => l.id === state.languageId)!.nameLong}
6✔
177
                    </Typography>
178
                    <TableContainer>
179
                        <Table>
180
                            <TableBody>
181
                                <TableRow>
182
                                    <TableCell>{t('name')}</TableCell>
183
                                    <TableCell>
184
                                        {state.nameI18n}
185
                                    </TableCell>
186
                                </TableRow>
187

188
                                <TableRow>
189
                                    <TableCell>{t('exercises.alternativeNames')}</TableCell>
190
                                    <TableCell>{state.alternativeNamesI18n.join(", ")}</TableCell>
191
                                </TableRow>
192

193

194
                                <TableRow>
195
                                    <TableCell>{t('description')}</TableCell>
196
                                    <TableCell>{state.descriptionI18n}</TableCell>
197
                                </TableRow>
198

199

200
                                <TableRow>
201
                                    <TableCell>{t('exercises.notes')}</TableCell>
202
                                    <TableCell>{state.notesI18n.map(note => <>{note}<br /></>)}</TableCell>
×
203
                                </TableRow>
204
                            </TableBody>
205
                        </Table>
206
                    </TableContainer>
207
                </>
208
            )}
209

210
            <Box sx={{ mt: 2 }}>
211
                {addExerciseSubmissionMutation.isIdle && <Alert severity="info">
2!
212
                    {t('exercises.checkInformationBeforeSubmitting')}
213
                </Alert>}
214

215
                {addExerciseSubmissionMutation.isSuccess && <Alert severity="success">
2!
216
                    <AlertTitle>{t('success')}</AlertTitle>
217
                    {t('exercises.cacheWarning')}
218
                </Alert>}
219

220
                <FormQueryErrors mutationQuery={addExerciseSubmissionMutation} />
221
            </Box>
222

223
            <Grid container>
224
                <Grid display="flex" justifyContent={"end"} size={12}>
225
                    <Box sx={{ mb: 2 }}>
226
                        <div>
227
                            {!addExerciseSubmissionMutation.isSuccess &&
4✔
228
                                <Button
229
                                    onClick={onBack}
230
                                    sx={{ mt: 1, mr: 1 }}
231
                                >
232
                                    {t('goBack')}
233
                                </Button>
234
                            }
235
                            {!addExerciseSubmissionMutation.isSuccess
4✔
236
                                && <Button
237
                                    variant="contained"
238
                                    disabled={addExerciseSubmissionMutation.isError || addExerciseSubmissionMutation.isPending}
4✔
239
                                    onClick={submitExercise}
240
                                    sx={{ mt: 1, mr: 1 }}
241
                                    color="info"
242
                                >
243
                                    {t('exercises.submitExercise')}
244
                                </Button>
245
                            }
246
                            {addExerciseSubmissionMutation.isSuccess
2!
247
                                && <Button
248
                                    variant="contained"
NEW
249
                                    onClick={() => navigate(makeLink(WgerLink.EXERCISE_DETAIL, i18n.language, { id: addExerciseSubmissionMutation.data! }))}
×
250
                                    sx={{ mt: 1, mr: 1 }}
251
                                    color="success"
252
                                >
253
                                    {t('seeDetails')}
254
                                    <NavigateNextIcon />
255
                                </Button>
256
                            }
257
                        </div>
258
                    </Box>
259
                </Grid>
260
            </Grid>
261
        </>;
262
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc