Skip to content

Commit baffae7

Browse files
committed
feat: Profile update without photo
1 parent b14f0fa commit baffae7

5 files changed

Lines changed: 111 additions & 141 deletions

File tree

src/admin/pages/ProfileEditPage.tsx

Lines changed: 41 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect, useState } from 'react';
1+
import React, { useEffect, useRef, useState } from 'react';
22
import {
33
Box,
44
Flex,
@@ -11,7 +11,7 @@ import {
1111
Avatar,
1212
Alert,
1313
} from '@strapi/design-system';
14-
import { User, Check, Upload, Lock } from '@strapi/icons';
14+
import { User, Check, Upload } from '@strapi/icons';
1515
import { useAuth, useFetchClient } from '@strapi/admin/strapi-admin';
1616

1717
interface ProfileData {
@@ -33,6 +33,8 @@ export default function ProfileEditPage() {
3333
const [uploadingAvatar, setUploadingAvatar] = useState(false);
3434
const [success, setSuccess] = useState(false);
3535

36+
const fileInputRef = useRef(null);
37+
3638
const [formData, setFormData] = useState({
3739
university: '',
3840
birth: '',
@@ -43,12 +45,6 @@ export default function ProfileEditPage() {
4345
lastname: '',
4446
});
4547

46-
const [passwordData, setPasswordData] = useState({
47-
currentPassword: '',
48-
newPassword: '',
49-
confirmPassword: '',
50-
});
51-
5248
useEffect(() => {
5349
const fetchProfile = async () => {
5450
if (!user?.id) return;
@@ -90,10 +86,6 @@ export default function ProfileEditPage() {
9086
setFormData(prev => ({ ...prev, [name]: value }));
9187
};
9288

93-
const handlePasswordChange = (name: string, value: string) => {
94-
setPasswordData(prev => ({ ...prev, [name]: value }));
95-
};
96-
9789
const handleAvatarUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
9890
const file = e.target.files?.[0];
9991
if (!file || !profile?.id) return;
@@ -130,12 +122,14 @@ export default function ProfileEditPage() {
130122

131123
try {
132124
setSaving(true);
133-
await put(`/api/profiles/${profile.id}`, {
125+
await put(`/api/profiles/me?userId=${user.id}`, {
134126
data: {
135127
university: formData.university,
136128
birth: formData.birth,
137129
phone: formData.phone,
138-
identifier: formData.identifier,
130+
firstname: formData.firstname,
131+
lastname: formData.lastname,
132+
email: formData.email,
139133
}
140134
});
141135

@@ -149,22 +143,6 @@ export default function ProfileEditPage() {
149143
}
150144
};
151145

152-
const handlePasswordSubmit = async (e: React.FormEvent) => {
153-
e.preventDefault();
154-
155-
if (passwordData.newPassword !== passwordData.confirmPassword) {
156-
alert('Passwords do not match');
157-
return;
158-
}
159-
160-
if (passwordData.newPassword.length < 6) {
161-
alert('Password must be at least 6 characters');
162-
return;
163-
}
164-
165-
alert('Password change feature requires admin API integration');
166-
};
167-
168146
const fullName = `${user?.firstname || ''} ${user?.lastname || ''}`.trim() || user?.username || 'User';
169147
const avatarUrl = profile?.avatar?.url
170148
? (profile.avatar.url.startsWith('http') ? profile.avatar.url : `${window.location.origin}${profile.avatar.url}`)
@@ -185,7 +163,6 @@ export default function ProfileEditPage() {
185163
<Flex gap={3} alignItems="center" marginBottom={6}>
186164
<User width={24} height={24} />
187165
<Box>
188-
<Typography variant="alpha">Edit Profile</Typography>
189166
<Typography variant="epsilon" textColor="neutral600">
190167
Manage your account information
191168
</Typography>
@@ -201,55 +178,50 @@ export default function ProfileEditPage() {
201178
</Box>
202179
)}
203180

204-
{/* Avatar Section */}
205-
<Box background="neutral0" padding={6} hasRadius shadow="tableShadow" marginBottom={4}>
206-
<Typography variant="delta" marginBottom={4}>Profile Photo</Typography>
207-
<Flex gap={4} alignItems="center">
208-
<Box position="relative">
209-
{avatarUrl ? (
210-
<Avatar.Item fallback={initials} src={avatarUrl} alt={fullName} />
211-
) : (
212-
<Avatar.Item fallback={initials}>{initials}</Avatar.Item>
213-
)}
214-
{uploadingAvatar && (
215-
<Box position="absolute" top="0" left="0" right="0" bottom="0" background="neutral800" style={{ opacity: 0.5 }}>
216-
<Loader small />
217-
</Box>
218-
)}
219-
</Box>
220-
<Box>
221-
<label htmlFor="avatar-upload">
222-
<Button variant="secondary" startIcon={<Upload />}>
223-
Change Photo
224-
</Button>
225-
</label>
181+
{/* Profile Form */}
182+
<form onSubmit={handleSubmit}>
183+
<Box background="neutral0" padding={6} hasRadius shadow="tableShadow" marginBottom={4}>
184+
<Flex gap={4} alignItems="center">
185+
<Box position="relative" marginBottom={12}>
186+
{avatarUrl ? (
187+
<Avatar.Item fallback={initials} src={avatarUrl} alt={fullName} />
188+
) : (
189+
<Avatar.Item fallback={initials}>{initials}</Avatar.Item>
190+
)}
191+
{uploadingAvatar && (
192+
<Box position="absolute" top="0" left="0" right="0" bottom="0" background="neutral800" style={{ opacity: 0.5 }}>
193+
<Loader small />
194+
</Box>
195+
)}
196+
</Box>
197+
{/* <Box>
198+
<Button
199+
variant="secondary"
200+
startIcon={<Upload />}
201+
onClick={() => fileInputRef.current?.click()}
202+
>
203+
Change Photo
204+
</Button>
226205
<input
227-
id="avatar-upload"
206+
ref={fileInputRef}
228207
type="file"
229208
accept="image/*"
230-
style={{ display: 'none' }}
209+
hidden
231210
onChange={handleAvatarUpload}
232211
/>
233212
<Typography variant="pi" textColor="neutral600" marginTop={2}>
234213
JPG, PNG or GIF. Max size 2MB.
235214
</Typography>
236-
</Box>
237-
</Flex>
238-
</Box>
239-
240-
{/* Profile Form */}
241-
<form onSubmit={handleSubmit}>
242-
<Box background="neutral0" padding={6} hasRadius shadow="tableShadow" marginBottom={4}>
243-
<Typography variant="delta" marginBottom={4}>Profile Information</Typography>
244-
215+
</Box> */}
216+
</Flex>
245217
<Flex gap={4} wrap="wrap">
246218
<Box flex="1" minWidth="280px">
247219
<Field.Root name="firstname">
248220
<Field.Label>First Name</Field.Label>
249221
<TextInput
250222
value={formData.firstname}
251-
disabled
252223
placeholder="Enter first name"
224+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleInputChange('firstname', e.target.value)}
253225
/>
254226
</Field.Root>
255227
</Box>
@@ -259,8 +231,8 @@ export default function ProfileEditPage() {
259231
<Field.Label>Last Name</Field.Label>
260232
<TextInput
261233
value={formData.lastname}
262-
disabled
263-
placeholder="Enter last name"
234+
placeholder="Enter last name"
235+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleInputChange('lastname', e.target.value)}
264236
/>
265237
</Field.Root>
266238
</Box>
@@ -271,8 +243,8 @@ export default function ProfileEditPage() {
271243
<TextInput
272244
type="email"
273245
value={formData.email}
274-
disabled
275246
placeholder="Enter email"
247+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleInputChange('email', e.target.value)}
276248
/>
277249
</Field.Root>
278250
</Box>
@@ -282,8 +254,8 @@ export default function ProfileEditPage() {
282254
<Field.Label>Member ID</Field.Label>
283255
<TextInput
284256
value={formData.identifier}
285-
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleInputChange('identifier', e.target.value)}
286257
placeholder="Enter member ID"
258+
disabled
287259
/>
288260
</Field.Root>
289261
</Box>
@@ -331,61 +303,6 @@ export default function ProfileEditPage() {
331303
</Flex>
332304
</Box>
333305
</form>
334-
335-
{/* Password Form */}
336-
<form onSubmit={handlePasswordSubmit}>
337-
<Box background="neutral0" padding={6} hasRadius shadow="tableShadow">
338-
<Typography variant="delta" marginBottom={4}>Change Password</Typography>
339-
340-
<Flex gap={4} wrap="wrap">
341-
<Box flex="1" minWidth="280px">
342-
<Field.Root name="currentPassword">
343-
<Field.Label>Current Password</Field.Label>
344-
<TextInput
345-
type="password"
346-
value={passwordData.currentPassword}
347-
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handlePasswordChange('currentPassword', e.target.value)}
348-
placeholder="Enter current password"
349-
/>
350-
</Field.Root>
351-
</Box>
352-
353-
<Box flex="1" minWidth="280px" />
354-
355-
<Box flex="1" minWidth="280px">
356-
<Field.Root name="newPassword">
357-
<Field.Label>New Password</Field.Label>
358-
<TextInput
359-
type="password"
360-
value={passwordData.newPassword}
361-
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handlePasswordChange('newPassword', e.target.value)}
362-
placeholder="Enter new password"
363-
/>
364-
</Field.Root>
365-
</Box>
366-
367-
<Box flex="1" minWidth="280px">
368-
<Field.Root name="confirmPassword">
369-
<Field.Label>Confirm Password</Field.Label>
370-
<TextInput
371-
type="password"
372-
value={passwordData.confirmPassword}
373-
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handlePasswordChange('confirmPassword', e.target.value)}
374-
placeholder="Confirm new password"
375-
/>
376-
</Field.Root>
377-
</Box>
378-
</Flex>
379-
380-
<Divider marginTop={6} marginBottom={4} />
381-
382-
<Flex justifyContent="flex-end">
383-
<Button type="submit" variant="secondary" startIcon={<Lock />} loading={saving}>
384-
Update Password
385-
</Button>
386-
</Flex>
387-
</Box>
388-
</form>
389306
</Box>
390307
);
391308
}

src/api/profile/controllers/profile.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,63 @@ export default factories.createCoreController('api::profile.profile', ({ strapi
3535

3636
return { data: profile };
3737
},
38+
async updateMe(ctx) {
39+
const { userId } = ctx.query;
40+
const {data} = ctx.request.body;
41+
console.log(data)
42+
43+
44+
if (!userId) {
45+
return ctx.badRequest('User ID is required');
46+
}
47+
48+
const profiles = await strapi.entityService.findMany('api::profile.profile', {
49+
filters: {
50+
user: {
51+
id: userId
52+
}
53+
},
54+
populate: {
55+
avatar: true,
56+
user: true,
57+
},
58+
limit: 1,
59+
});
60+
61+
const profile = profiles[0];
62+
63+
if (!profile) {
64+
return ctx.notFound('Profile not found matching this admin user');
65+
}
66+
67+
await strapi.entityService.update('api::profile.profile', profile.id, {
68+
data: {
69+
university: data.university,
70+
birth: data.birth,
71+
phone: data.phone,
72+
}
73+
});
74+
if (!Number.isInteger(Number(userId))) {
75+
return ctx.badRequest('Invalid userId');
76+
}
77+
78+
await strapi.entityService.update('admin::user', Number(userId), {
79+
data: {
80+
firstname: data.firstname,
81+
lastname: data.lastname,
82+
email: data.email,
83+
},
84+
});
85+
86+
// await strapi.documents('admin::user').update({
87+
// documentId: profile.user.documentId,
88+
// data: {
89+
// firstname: data.firstname,
90+
// lastname: data.lastname,
91+
// email: data.email,
92+
// },
93+
// });
94+
95+
return { data:profile };
96+
},
3897
}));

src/api/profile/routes/custom-profile.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,15 @@ export default {
55
path: '/profiles/me',
66
handler: 'api::profile.profile.getMe',
77
config: {
8-
auth: false,
9-
// policies: [],
10-
// middlewares: [],
8+
auth: false
9+
},
10+
},
11+
{
12+
method: 'PUT',
13+
path: '/profiles/me',
14+
handler: 'api::profile.profile.updateMe',
15+
config: {
16+
auth: false
1117
},
1218
},
1319
],

src/api/profile/routes/profile.ts

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,4 @@
44

55
import { factories } from '@strapi/strapi';
66

7-
export default factories.createCoreRouter('api::profile.profile', {
8-
config: {
9-
find: {
10-
auth: false, // Allow public access, filtering by user ID handles security
11-
},
12-
findOne: {
13-
auth: false,
14-
},
15-
update: {
16-
auth: false, // Will add proper admin auth later
17-
},
18-
}
19-
});
7+
export default factories.createCoreRouter('api::profile.profile');

src/extensions/documentation/documentation/1.0.0/full_documentation.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"name": "Apache 2.0",
1515
"url": "https://www.apache.org/licenses/LICENSE-2.0.html"
1616
},
17-
"x-generation-date": "2026-01-23T06:56:23.907Z"
17+
"x-generation-date": "2026-01-23T10:52:37.730Z"
1818
},
1919
"servers": [
2020
{

0 commit comments

Comments
 (0)