Skip to content

Commit f6126f8

Browse files
fusharclaude
andcommitted
Admin: show and edit all user info fields
Display and allow editing of home address, shirt size, and institution fields (name, country, province, city) in the admin user view page. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 8c07c5a commit f6126f8

File tree

3 files changed

+129
-13
lines changed

3 files changed

+129
-13
lines changed

judgels-client/src/routes/admin/users/UserEditInfoForm/UserEditInfoForm.jsx

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import { Field, Form } from 'react-final-form';
33

44
import { countriesData } from '../../../../assets/data/countries';
55
import { ActionButtons } from '../../../../components/ActionButtons/ActionButtons';
6+
import { HorizontalInnerDivider } from '../../../../components/HorizontalInnerDivider/HorizontalInnerDivider';
67
import { FormTableSelect } from '../../../../components/forms/FormTableSelect/FormTableSelect';
8+
import { FormTableTextArea } from '../../../../components/forms/FormTableTextArea/FormTableTextArea';
79
import { FormTableTextInput } from '../../../../components/forms/FormTableTextInput/FormTableTextInput';
810
import { withSubmissionError } from '../../../../modules/form/submissionError';
911

@@ -22,6 +24,36 @@ const countryField = {
2224
label: 'Country',
2325
};
2426

27+
const homeAddressField = {
28+
name: 'homeAddress',
29+
label: 'Home address',
30+
};
31+
32+
const shirtSizeField = {
33+
name: 'shirtSize',
34+
label: 'Shirt size',
35+
};
36+
37+
const institutionField = {
38+
name: 'institutionName',
39+
label: 'Institution name',
40+
};
41+
42+
const institutionCountryField = {
43+
name: 'institutionCountry',
44+
label: 'Institution country',
45+
};
46+
47+
const institutionProvinceField = {
48+
name: 'institutionProvince',
49+
label: 'Institution province/state',
50+
};
51+
52+
const institutionCityField = {
53+
name: 'institutionCity',
54+
label: 'Institution city',
55+
};
56+
2557
export default function UserEditInfoForm({ onSubmit, initialValues, onCancel }) {
2658
const countryOptions = countriesData.map(country => (
2759
<option key={country.code} value={country.code}>
@@ -45,6 +77,25 @@ export default function UserEditInfoForm({ onSubmit, initialValues, onCancel })
4577
<option />
4678
{countryOptions}
4779
</Field>
80+
<Field component={FormTableTextArea} {...homeAddressField} />
81+
<Field component={FormTableSelect} {...shirtSizeField}>
82+
<option />
83+
<option value="XXS">XXS</option>
84+
<option value="XS">XS</option>
85+
<option value="S">S</option>
86+
<option value="M">M</option>
87+
<option value="L">L</option>
88+
<option value="XL">XL</option>
89+
<option value="XXL">XXL</option>
90+
<option value="XXXL">XXXL</option>
91+
</Field>
92+
<Field component={FormTableTextInput} {...institutionField} />
93+
<Field component={FormTableSelect} {...institutionCountryField}>
94+
<option />
95+
{countryOptions}
96+
</Field>
97+
<Field component={FormTableTextInput} {...institutionProvinceField} />
98+
<Field component={FormTableTextInput} {...institutionCityField} />
4899
</tbody>
49100
</HTMLTable>
50101
<hr />

judgels-client/src/routes/admin/users/UserViewPage/UserViewPage.jsx

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@ import { useMutation, useSuspenseQuery } from '@tanstack/react-query';
55
import { useParams } from '@tanstack/react-router';
66
import { useState } from 'react';
77

8+
import { getCountryName } from '../../../../assets/data/countries';
89
import { ContentCard } from '../../../../components/ContentCard/ContentCard';
10+
import { HorizontalInnerDivider } from '../../../../components/HorizontalInnerDivider/HorizontalInnerDivider';
911
import { FormTable } from '../../../../components/forms/FormTable/FormTable';
12+
import { userInfoGender } from '../../../../modules/api/jophiel/userInfo';
1013
import { userByUsernameQueryOptions } from '../../../../modules/queries/user';
1114
import { updateUserInfoMutationOptions, userInfoQueryOptions } from '../../../../modules/queries/userInfo';
1215
import UserEditInfoForm from '../UserEditInfoForm/UserEditInfoForm';
@@ -23,7 +26,7 @@ export default function UserViewPage() {
2326

2427
const [isEditingInfo, setIsEditingInfo] = useState(false);
2528

26-
const keyStyles = { width: '200px' };
29+
const keyStyles = { width: '220px' };
2730

2831
const generalRows = [
2932
{ key: 'jid', title: 'JID', value: user.jid },
@@ -40,9 +43,15 @@ export default function UserViewPage() {
4043
};
4144

4245
const infoRows = [
43-
{ key: 'name', title: 'Name', value: userInfo.name || '-' },
44-
{ key: 'gender', title: 'Gender', value: userInfo.gender || '-' },
45-
{ key: 'country', title: 'Country', value: userInfo.country || '-' },
46+
{ key: 'name', title: 'Name', value: userInfo.name },
47+
{ key: 'gender', title: 'Gender', value: userInfo.gender && userInfoGender[userInfo.gender] },
48+
{ key: 'country', title: 'Country', value: getCountryName(userInfo.country) },
49+
{ key: 'homeAddress', title: 'Home address', value: userInfo.homeAddress },
50+
{ key: 'shirtSize', title: 'Shirt size', value: userInfo.shirtSize },
51+
{ key: 'institutionName', title: 'Institution name', value: userInfo.institutionName },
52+
{ key: 'institutionCountry', title: 'Institution country', value: getCountryName(userInfo.institutionCountry) },
53+
{ key: 'institutionProvince', title: 'Institution province/state', value: userInfo.institutionProvince },
54+
{ key: 'institutionCity', title: 'Institution city', value: userInfo.institutionCity },
4655
];
4756

4857
const updateUserInfo = data => {
@@ -69,6 +78,12 @@ export default function UserViewPage() {
6978
name: userInfo.name || '',
7079
gender: userInfo.gender || '',
7180
country: userInfo.country || '',
81+
homeAddress: userInfo.homeAddress || '',
82+
shirtSize: userInfo.shirtSize || '',
83+
institutionName: userInfo.institutionName || '',
84+
institutionCountry: userInfo.institutionCountry || '',
85+
institutionProvince: userInfo.institutionProvince || '',
86+
institutionCity: userInfo.institutionCity || '',
7287
};
7388
return (
7489
<UserEditInfoForm

judgels-client/src/routes/admin/users/UserViewPage/UserViewPage.test.jsx

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ describe('UserViewPage', () => {
2424
name: 'Andi Smith',
2525
gender: 'MALE',
2626
country: 'ID',
27+
homeAddress: '123 Main St',
28+
shirtSize: 'M',
29+
institutionName: 'MIT',
30+
institutionCountry: 'US',
31+
institutionProvince: 'Massachusetts',
32+
institutionCity: 'Cambridge',
2733
});
2834

2935
await act(async () =>
@@ -65,8 +71,14 @@ describe('UserViewPage', () => {
6571
)
6672
).toEqual([
6773
['Name', 'Andi Smith'],
68-
['Gender', 'MALE'],
69-
['Country', 'ID'],
74+
['Gender', 'Male'],
75+
['Country', 'Indonesia'],
76+
['Home address', '123 Main St'],
77+
['Shirt size', 'M'],
78+
['Institution name', 'MIT'],
79+
['Institution country', 'United States'],
80+
['Institution province/state', 'Massachusetts'],
81+
['Institution city', 'Cambridge'],
7082
]);
7183
});
7284

@@ -78,29 +90,67 @@ describe('UserViewPage', () => {
7890
const button = await screen.findByRole('button', { name: /edit/i });
7991
await user.click(button);
8092

81-
const name = screen.getByRole('textbox', { name: /name/i });
82-
expect(name).toHaveValue('Andi Smith');
83-
await user.clear(name);
84-
await user.type(name, 'Caca');
93+
const names = screen.getAllByRole('textbox', { name: /name/i });
94+
expect(names[0]).toHaveValue('Andi Smith');
95+
await user.clear(names[0]);
96+
await user.type(names[0], 'Caca');
8597

8698
const gender = screen.getByRole('combobox', { name: /gender/i });
8799
expect(gender).toHaveValue('MALE');
88100
await user.selectOptions(gender, 'FEMALE');
89101

90-
const country = screen.getByRole('combobox', { name: /country/i });
91-
expect(country).toHaveValue('ID');
92-
await user.selectOptions(country, 'US');
102+
const countries = screen.getAllByRole('combobox', { name: /country/i });
103+
expect(countries[0]).toHaveValue('ID');
104+
await user.selectOptions(countries[0], 'US');
105+
106+
const homeAddress = document.querySelector('textarea[name="homeAddress"]');
107+
expect(homeAddress).toHaveValue('123 Main St');
108+
await user.clear(homeAddress);
109+
await user.type(homeAddress, '456 Oak Ave');
110+
111+
const shirtSize = screen.getByRole('combobox', { name: /shirt size/i });
112+
expect(shirtSize).toHaveValue('M');
113+
await user.selectOptions(shirtSize, 'L');
114+
115+
expect(names[1]).toHaveValue('MIT');
116+
await user.clear(names[1]);
117+
await user.type(names[1], 'Stanford');
118+
119+
expect(countries[1]).toHaveValue('US');
120+
await user.selectOptions(countries[1], 'GB');
121+
122+
const institutionProvince = screen.getByRole('textbox', { name: /institution province/i });
123+
expect(institutionProvince).toHaveValue('Massachusetts');
124+
await user.clear(institutionProvince);
125+
await user.type(institutionProvince, 'England');
126+
127+
const institutionCity = screen.getByRole('textbox', { name: /institution city/i });
128+
expect(institutionCity).toHaveValue('Cambridge');
129+
await user.clear(institutionCity);
130+
await user.type(institutionCity, 'London');
93131

94132
nockJophiel()
95133
.put('/users/JIDUSER123/info', {
96134
name: 'Caca',
97135
gender: 'FEMALE',
98136
country: 'US',
137+
homeAddress: '456 Oak Ave',
138+
shirtSize: 'L',
139+
institutionName: 'Stanford',
140+
institutionCountry: 'GB',
141+
institutionProvince: 'England',
142+
institutionCity: 'London',
99143
})
100144
.reply(200, {
101145
name: 'Caca',
102146
gender: 'FEMALE',
103147
country: 'US',
148+
homeAddress: '456 Oak Ave',
149+
shirtSize: 'L',
150+
institutionName: 'Stanford',
151+
institutionCountry: 'GB',
152+
institutionProvince: 'England',
153+
institutionCity: 'London',
104154
});
105155

106156
await user.click(screen.getByRole('button', { name: /save/i }));

0 commit comments

Comments
 (0)