Skip to content

Commit 4d5b4f7

Browse files
committed
Implement client-side URL validation for social media fields; retain server-side response handling
1 parent 3bcc643 commit 4d5b4f7

File tree

1 file changed

+80
-35
lines changed

1 file changed

+80
-35
lines changed

app/[locale]/dashboard/[entityType]/[entitySlug]/profile/userProfile.tsx

Lines changed: 80 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,22 @@ const updateUserMutation: any = graphql(`
3535
}
3636
`);
3737

38-
const urlRegex = /^(https?:\/\/)([\w-]+\.)+[\w-]{2,}(\/\S*)?$/;
38+
const githubRegex = /^https:\/\/github\.com\/[a-zA-Z0-9](?:[a-zA-Z0-9]|-(?=[a-zA-Z0-9])){0,38}$/;
39+
const linkedinRegex = /^https:\/\/(?:www\.)?linkedin\.com\/in\/[a-zA-Z0-9-]+\/?$/;
40+
const twitterRegex = /^https:\/\/(?:www\.)?(?:twitter\.com|x\.com)\/[a-zA-Z0-9_]+\/?$/;
41+
42+
const prettyField = (f: string) => {
43+
switch (f) {
44+
case 'github_profile':
45+
return 'GitHub URL';
46+
case 'linkedin_profile':
47+
return 'LinkedIn URL';
48+
case 'twitter_profile':
49+
return 'Twitter URL';
50+
default:
51+
return f.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
52+
}
53+
};
3954

4055
const UserProfile = () => {
4156
const params = useParams<{ entityType: string; entitySlug: string }>();
@@ -72,9 +87,11 @@ const UserProfile = () => {
7287

7388
const { mutate, isLoading: editMutationLoading } = useMutation(
7489
(input: { input: UpdateUserInput }) =>
75-
GraphQL(updateUserMutation, {
76-
[params.entityType]: params.entitySlug,
77-
}, input),
90+
GraphQL(
91+
updateUserMutation,
92+
{ [params.entityType]: params.entitySlug },
93+
input
94+
),
7895
{
7996
onSuccess: (res: any) => {
8097
toast('User details updated successfully');
@@ -94,8 +111,36 @@ const UserProfile = () => {
94111
me: res.updateUser,
95112
});
96113
},
114+
97115
onError: (error: any) => {
98-
toast(`Error: ${error.message}`);
116+
if (typeof error?.message === 'string') {
117+
const message: string = error.message;
118+
119+
// Try to extract field errors
120+
const tryField = (field: string) => {
121+
const m = message.match(
122+
new RegExp(`'${field}'\\s*:\\s*\\['([^']+)'\\]`)
123+
);
124+
if (m?.[1]) {
125+
const prettyName = prettyField(field);
126+
const errorMsg = `${prettyName}: ${m[1]}`;
127+
toast.error(errorMsg);
128+
}
129+
return Boolean(m?.[1]);
130+
};
131+
132+
const anyMatched =
133+
tryField('github_profile') ||
134+
tryField('linkedin_profile') ||
135+
tryField('twitter_profile');
136+
137+
if (!anyMatched) {
138+
toast.error(`Error: ${message}`);
139+
}
140+
return;
141+
}
142+
143+
toast.error('An unexpected error occurred.');
99144
},
100145
}
101146
);
@@ -114,39 +159,36 @@ const UserProfile = () => {
114159
if (!formValidation) {
115160
toast('Please fill all the required fields');
116161
return;
117-
}
118-
if (formData.githubProfile && !urlRegex.test(formData.githubProfile)) {
119-
toast.error('Enter a valid GitHub URL');
120-
return;
121-
}
122-
123-
if (formData.linkedinProfile && !urlRegex.test(formData.linkedinProfile)) {
124-
toast.error('Enter a valid LinkedIn URL');
125-
return;
126-
}
162+
}
163+
if (formData.githubProfile && !githubRegex.test(formData.githubProfile)) {
164+
toast.error('GitHub URL: Enter a valid URL.');
165+
return;
166+
}
167+
if (formData.linkedinProfile && !linkedinRegex.test(formData.linkedinProfile)) {
168+
toast.error('LinkedIn URL: Enter a valid URL.');
169+
return;
170+
}
171+
if (formData.twitterProfile && !twitterRegex.test(formData.twitterProfile)) {
172+
toast.error('Twitter URL: Enter a valid URL.');
173+
return;
174+
}
127175

128-
if (formData.twitterProfile && !urlRegex.test(formData.twitterProfile)) {
129-
toast.error('Enter a valid Twitter URL');
130-
return;
131-
}
132-
else {
133-
const inputData: UpdateUserInput = {
134-
firstName: formData.firstName,
135-
lastName: formData.lastName,
136-
bio: formData.bio,
137-
email: formData.email,
138-
githubProfile: formData.githubProfile,
139-
linkedinProfile: formData.linkedinProfile,
140-
twitterProfile: formData.twitterProfile,
141-
location: formData.location,
142-
};
176+
const inputData: UpdateUserInput = {
177+
firstName: formData.firstName,
178+
lastName: formData.lastName,
179+
bio: formData.bio,
180+
email: formData.email,
181+
githubProfile: formData.githubProfile,
182+
linkedinProfile: formData.linkedinProfile,
183+
twitterProfile: formData.twitterProfile,
184+
location: formData.location,
185+
};
143186

144-
// Only add logo if it has changed
145-
if (formData.profilePicture instanceof File) {
146-
inputData.profilePicture = formData.profilePicture;
147-
}
148-
mutate({ input: inputData });
187+
// Only add logo if it has changed
188+
if (formData.profilePicture instanceof File) {
189+
inputData.profilePicture = formData.profilePicture;
149190
}
191+
mutate({ input: inputData });
150192
};
151193

152194
return (
@@ -199,20 +241,23 @@ const UserProfile = () => {
199241
label="Github Profile"
200242
name="githubProfile"
201243
type="url"
244+
placeholder="https://github.com/username"
202245
value={formData.githubProfile}
203246
onChange={(e) => setFormData({ ...formData, githubProfile: e })}
204247
/>
205248
<TextField
206249
label="Linkedin Profile"
207250
name="linkedinProfile"
208251
type="url"
252+
placeholder="https://linkedin.com/in/username"
209253
value={formData.linkedinProfile}
210254
onChange={(e) => setFormData({ ...formData, linkedinProfile: e })}
211255
/>
212256
<TextField
213257
label="Twitter Profile"
214258
name="twitterProfile"
215259
type="url"
260+
placeholder="https://twitter.com/username"
216261
value={formData.twitterProfile}
217262
onChange={(e) => setFormData({ ...formData, twitterProfile: e })}
218263
/>

0 commit comments

Comments
 (0)