Skip to content

Commit 5e4778f

Browse files
committed
update profile page styles
1 parent 2ea2cc7 commit 5e4778f

File tree

6 files changed

+391
-248
lines changed

6 files changed

+391
-248
lines changed

src/components/Cards/OtherCards.tsx

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { Box, Card, Divider, Group, Stack, Text, Tooltip } from "@mantine/core";
22
import { PiBuildingsBold } from "react-icons/pi";
3-
import { FaGithub, FaLinkedin, FaTwitter } from "react-icons/fa";
43
import { ProfileAvatar } from "../Common";
54
import { Link } from "react-router-dom";
5+
import { SOCIAL_LINKS_DATA } from "@/utils/constants";
66

77
function UserProfileCard({ user }: { user: any }) {
88
const userStats = [
@@ -17,11 +17,6 @@ function UserProfileCard({ user }: { user: any }) {
1717
url: value,
1818
}));
1919

20-
const socialLinksIcons = {
21-
github: <FaGithub />,
22-
linkedin: <FaLinkedin />,
23-
twitter: <FaTwitter />,
24-
};
2520
return (
2621
<Card withBorder padding="xl" radius="md" w={350}>
2722
<Card.Section
@@ -43,32 +38,44 @@ function UserProfileCard({ user }: { user: any }) {
4338
</Text>
4439
<StatsList stats={userStats} />
4540
<Divider my="md" />
41+
<Stack gap={7}>
42+
<Text fz="sm" c="dimmed" ta="center">
43+
Joined Cranecloud {user?.age}
44+
</Text>
4645

47-
<Text fz="sm" mb="md" c="dimmed" ta="center">
48-
Joined Cranecloud {user?.age}
49-
</Text>
50-
<Stack gap={4}>
5146
{user?.biography && (
52-
<Text fz="sm" c="black" ta="center" lineClamp={3} fw={300}>
47+
<Text fz="sm" ta="center" lineClamp={3} fw={500}>
5348
{user?.biography}
5449
</Text>
5550
)}
56-
<Divider my="md" />
57-
51+
</Stack>
52+
<Divider my="md" />
53+
<Stack gap={7} fz="sm" fw={500}>
5854
{user?.organisation && (
59-
<Group gap={5} fz="0.9rem" fw={600} mt="sm">
60-
<PiBuildingsBold size={17} />
55+
<Group gap={10}>
56+
<PiBuildingsBold size={16} />
6157
{user?.organisation}
6258
</Group>
6359
)}
64-
{links.map((link, index) => (
65-
<Link key={index} to={link.url as string} target="_blank">
66-
<Group gap={5} fz="0.9rem">
67-
{socialLinksIcons[link.platform as keyof typeof socialLinksIcons]}
68-
{link.url as string}
69-
</Group>
70-
</Link>
71-
))}
60+
{links.map((link, index) => {
61+
const platform = SOCIAL_LINKS_DATA.find(
62+
(p) => p.value === link.platform,
63+
);
64+
const IconComponent = platform?.icon;
65+
66+
return (
67+
<Link key={index} to={link.url as string} target="_blank">
68+
<Group gap={10} align="center">
69+
{IconComponent && (
70+
<IconComponent size={16} color={platform?.color} />
71+
)}
72+
<Text size="sm" style={{ flex: 1 }}>
73+
{link.url as string}
74+
</Text>
75+
</Group>
76+
</Link>
77+
);
78+
})}
7279
</Stack>
7380
</Card>
7481
);
Lines changed: 223 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,50 @@
1-
import { Button, Stack, TextInput, Textarea, Group } from "@mantine/core";
2-
import { useState, useEffect } from "react";
1+
import {
2+
Button,
3+
Stack,
4+
TextInput,
5+
Textarea,
6+
Group,
7+
ActionIcon,
8+
Select,
9+
} from "@mantine/core";
10+
import { useEffect, useState } from "react";
311
import usePost from "@/utils/usePost";
12+
import useForm from "@/hooks/generic/useForm";
13+
import { FaCheck, FaPlus, FaTrashAlt } from "react-icons/fa";
14+
import { SOCIAL_LINKS_DATA } from "@/utils/constants";
415

516
type UpdateProfileForm = {
6-
user: any; // your user object, can type more strictly if you want
17+
user: any;
718
onCancel: () => void;
8-
onSuccess?: () => void;
19+
refresh?: () => void;
920
};
1021

1122
export const UpdateProfileForm = ({
1223
user,
1324
onCancel,
14-
onSuccess,
25+
refresh = () => {},
1526
}: UpdateProfileForm) => {
16-
const [username, setUsername] = useState(user?.username || "");
17-
const [email, setEmail] = useState(user?.email || "");
18-
const [biography, setBiography] = useState(user?.biography || "");
19-
const [organisation, setOrganisation] = useState(user?.organisation || "");
27+
const { form, onChange, updateFormValues, editedForm } = useForm();
2028

2129
const { uploadData, submitting, success } = usePost();
2230

31+
useEffect(() => {
32+
if (user) {
33+
updateFormValues({
34+
username: user?.username || "",
35+
email: user?.email || "",
36+
biography: user?.biography || "",
37+
organisation: user?.organisation || "",
38+
});
39+
}
40+
}, []);
41+
2342
useEffect(() => {
2443
if (success) {
25-
if (onSuccess) {
26-
onSuccess();
27-
}
44+
refresh();
45+
onCancel();
2846
}
29-
}, [success, onSuccess]);
47+
}, [success]);
3048

3149
const handleSubmit = (e: React.FormEvent) => {
3250
e.preventDefault();
@@ -39,12 +57,7 @@ export const UpdateProfileForm = ({
3957
api: "users",
4058
id: user.id,
4159
method: "PATCH",
42-
params: {
43-
username,
44-
email,
45-
biography,
46-
organisation,
47-
},
60+
params: user?.id ? editedForm : form,
4861
});
4962
};
5063

@@ -55,34 +68,38 @@ export const UpdateProfileForm = ({
5568
label="Username"
5669
placeholder="Enter your username"
5770
description="This will be your public username. Do not use spaces or special characters."
58-
value={username}
59-
onChange={(e) => setUsername(e.currentTarget.value)}
71+
name="username"
72+
value={form.username as string}
73+
onChange={onChange}
6074
required
6175
/>
6276

6377
<TextInput
6478
label="Email"
6579
type="email"
6680
placeholder="Enter your email"
67-
value={email}
68-
onChange={(e) => setEmail(e.currentTarget.value)}
81+
name="email"
82+
value={form.email as string}
83+
onChange={onChange}
6984
disabled
7085
required
7186
/>
7287

7388
<TextInput
7489
label="Organisation"
7590
placeholder="Your organisation or company"
76-
value={organisation}
77-
onChange={(e) => setOrganisation(e.currentTarget.value)}
91+
name="organisation"
92+
value={form.organisation as string}
93+
onChange={onChange}
7894
required
7995
/>
8096

8197
<Textarea
8298
label="Biography"
8399
placeholder="Tell us about yourself"
84-
value={biography}
85-
onChange={(e) => setBiography(e.currentTarget.value)}
100+
name="biography"
101+
value={form.biography as string}
102+
onChange={onChange}
86103
maxRows={3}
87104
/>
88105

@@ -95,6 +112,7 @@ export const UpdateProfileForm = ({
95112
loading={submitting}
96113
variant="filled"
97114
color="dark"
115+
disabled={user?.id && Object.keys(editedForm).length <= 0}
98116
>
99117
Save
100118
</Button>
@@ -103,3 +121,181 @@ export const UpdateProfileForm = ({
103121
</form>
104122
);
105123
};
124+
125+
export const SocialMediaLinksForm = ({
126+
user,
127+
onCancel,
128+
refresh = () => {},
129+
}: {
130+
user: any;
131+
onCancel: () => void;
132+
refresh?: () => void;
133+
}) => {
134+
const { uploadData, submitting, success } = usePost();
135+
136+
const [socialLinks, setSocialLinks] = useState<
137+
{ platform: string; url: string }[]
138+
>([]);
139+
140+
useEffect(() => {
141+
if (user) {
142+
const socialLinks = user?.social_links || {};
143+
const links =
144+
Object.entries(socialLinks).map(([key, value]) => ({
145+
platform: key,
146+
url: value as string,
147+
})) || [];
148+
setSocialLinks(links);
149+
}
150+
}, [user]);
151+
152+
useEffect(() => {
153+
if (success) {
154+
refresh();
155+
onCancel();
156+
}
157+
}, [success]);
158+
159+
const handleChange = (index: number, field: string, value: string) => {
160+
const newLinks = [...socialLinks];
161+
if (field === "url") {
162+
// Ensure URL starts with http:// or https://
163+
if (
164+
value &&
165+
!value.startsWith("http://") &&
166+
!value.startsWith("https://")
167+
) {
168+
const newValue = `https://${value}`;
169+
(newLinks[index] as any)[field] = newValue;
170+
} else {
171+
(newLinks[index] as any)[field] = value;
172+
}
173+
} else {
174+
(newLinks[index] as any)[field] = value;
175+
}
176+
setSocialLinks(newLinks);
177+
};
178+
179+
const addLink = () => {
180+
setSocialLinks([...socialLinks, { platform: "", url: "" }]);
181+
};
182+
183+
const removeLink = (index: number) => {
184+
const newLinks = [...socialLinks];
185+
newLinks.splice(index, 1);
186+
setSocialLinks(newLinks);
187+
};
188+
189+
const handleSubmit = (e: React.FormEvent) => {
190+
e.preventDefault();
191+
192+
if (!user?.id) {
193+
return;
194+
}
195+
196+
const socialLinksData = socialLinks.reduce(
197+
(acc, link) => {
198+
if (link.platform && link.url) {
199+
acc[link.platform] = link.url;
200+
}
201+
return acc;
202+
},
203+
{} as Record<string, string>,
204+
);
205+
206+
uploadData({
207+
api: "users",
208+
id: user.id,
209+
method: "PATCH",
210+
params: { social_links: socialLinksData },
211+
});
212+
};
213+
214+
return (
215+
<form onSubmit={handleSubmit}>
216+
<Stack gap={10}>
217+
{socialLinks?.map((link, index) => (
218+
<Group key={index} align="center" gap="md">
219+
<Select
220+
label="Platform"
221+
placeholder="e.g. Twitter"
222+
value={link.platform}
223+
onChange={(e) => handleChange(index, "platform", e || "")}
224+
data={SOCIAL_LINKS_DATA}
225+
leftSection={
226+
link.platform
227+
? (() => {
228+
const platform = SOCIAL_LINKS_DATA.find(
229+
(p) => p.value === link.platform,
230+
);
231+
const IconComponent = platform?.icon;
232+
return IconComponent ? (
233+
<IconComponent size={16} color={platform?.color} />
234+
) : undefined;
235+
})()
236+
: undefined
237+
}
238+
renderOption={({ option, checked }) => {
239+
const platform = SOCIAL_LINKS_DATA.find(
240+
(p) => p.value === option.value,
241+
);
242+
const IconComponent = platform?.icon;
243+
244+
return (
245+
<Group flex="1" gap="xs">
246+
{IconComponent && (
247+
<IconComponent size={16} color={platform?.color} />
248+
)}
249+
<span>{option.label}</span>
250+
{checked && <FaCheck size={14} color="#228BE6" />}
251+
</Group>
252+
);
253+
}}
254+
required
255+
/>
256+
<TextInput
257+
label="URL"
258+
placeholder="https://twitter.com/yourhandle"
259+
value={link.url}
260+
onChange={(e) => handleChange(index, "url", e.target.value)}
261+
flex="1"
262+
required
263+
/>
264+
<ActionIcon
265+
color="red"
266+
variant="filled"
267+
aria-label="Delete"
268+
mt="lg"
269+
size="2.2rem"
270+
onClick={() => removeLink(index)}
271+
>
272+
<FaTrashAlt />
273+
</ActionIcon>
274+
</Group>
275+
))}
276+
<Button
277+
variant="outline"
278+
onClick={addLink}
279+
disabled={submitting}
280+
leftSection={<FaPlus />}
281+
>
282+
Add New Link
283+
</Button>
284+
285+
<Group justify="flex-end" mt="md">
286+
<Button variant="outline" onClick={onCancel} disabled={submitting}>
287+
Cancel
288+
</Button>
289+
<Button
290+
type="submit"
291+
loading={submitting}
292+
variant="filled"
293+
color="dark"
294+
>
295+
Save Links
296+
</Button>
297+
</Group>
298+
</Stack>
299+
</form>
300+
);
301+
};

0 commit comments

Comments
 (0)