Skip to content

Commit e2ea91d

Browse files
committed
Add profile information to user
1 parent 229c287 commit e2ea91d

File tree

13 files changed

+287
-42
lines changed

13 files changed

+287
-42
lines changed

src/app/(authenticated)/profile/page.tsx

Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import authOptions from "@/app/api/auth/[...nextauth]/authOptions";
22
import GridList from "@/components/GridList";
33
import List from "@/components/List";
44
import ListCard from "@/components/ListCard";
5-
import MessageCard from "@/components/MessageCard";
65
import AchievementTile from "@/components/user/AchievementTile";
76
import CurriculumVitae from "@/components/user/CurriculumVitae";
87
import ProfileHeader from "@/components/user/ProfileHeader";
8+
import ProfileInformations from "@/components/user/ProfileInformations";
99
import { AchievementService } from "@/services/AchievementService";
1010
import { UserService } from "@/services/UserService";
1111
import { isCompany } from "@/utils/utils";
@@ -52,27 +52,8 @@ export default async function Profile() {
5252
</List>
5353
)}
5454

55-
{/* Academic information */}
56-
{!isCompany(user.role) && (
57-
<List title="Academic Information">
58-
<MessageCard
59-
type="info"
60-
content="This information only shows to companies that scanned your QR code"
61-
/>
62-
<ListCard
63-
title="Computer Science and Engineering"
64-
subtitle="Instituto Superior Técnico"
65-
headtext="Master degree"
66-
label="In progress"
67-
/>
68-
<ListCard
69-
title="Computer Science and Engineering"
70-
subtitle="Instituto Superior Técnico"
71-
headtext="Bachelors degree"
72-
label="Finished"
73-
/>
74-
</List>
75-
)}
55+
{/* User informations */}
56+
<ProfileInformations user={user} />
7657

7758
{/* Achievements */}
7859
<GridList
@@ -97,7 +78,7 @@ export default async function Profile() {
9778
</GridList>
9879

9980
{/* Connections */}
100-
{/* user.connections?.length ? (
81+
{/* !!user.connections?.length && (
10182
<List
10283
title="Connections"
10384
link="/profile/connections"
@@ -107,8 +88,6 @@ export default async function Profile() {
10788
<UserTile key={u.id} user={u} />
10889
))}
10990
</List>
110-
) : (
111-
<></>
11291
) */}
11392
</div>
11493
);

src/app/(authenticated)/qr/page.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@ export default async function QR() {
3838
switch (convertToAppRole(user.role)) {
3939
case "Member":
4040
case "Admin":
41-
return "#DB836E"; // Sinfo Tertiary
42-
case "Company":
4341
return "#A73939"; // SINFO Secondary
42+
case "Company":
43+
return "#DB836E"; // SINFO Tertiary
4444
case "Attendee":
4545
default:
4646
return "#323363"; // SINFO Primary
@@ -66,9 +66,9 @@ export default async function QR() {
6666
{company && (
6767
<Link href={`/companies/${company.id}`}>
6868
<Image
69-
className="h-[100px] w-[100px] object-contain"
70-
width={100}
71-
height={100}
69+
className="h-[80px] w-[80px] object-contain"
70+
width={80}
71+
height={80}
7272
src={company.img}
7373
alt={`${company.name} logo`}
7474
/>

src/app/(authenticated)/schedule/ScheduleTable.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export default function ScheduleTable({ sessions }: ScheduleTableProps) {
4040
.filter(
4141
(s) =>
4242
(!kindParam || s.kind.toLowerCase() === kindParam) &&
43-
(!placeParam || s.place.toLowerCase() === placeParam)
43+
(!placeParam || s.place.toLowerCase() === placeParam),
4444
)
4545
.sort((a, b) => a.date.localeCompare(b.date));
4646

@@ -50,13 +50,13 @@ export default function ScheduleTable({ sessions }: ScheduleTableProps) {
5050
const daySessions = [...(acc[day] || []), s];
5151
return { ...acc, [day]: daySessions };
5252
},
53-
{} as Record<string, SINFOSession[]>
53+
{} as Record<string, SINFOSession[]>,
5454
);
5555
}, [sessions]);
5656

5757
const sortedDays = useMemo(
5858
() => Object.keys(sessionsByDay).sort(),
59-
[sessionsByDay]
59+
[sessionsByDay],
6060
);
6161

6262
useEffect(() => {
@@ -74,7 +74,7 @@ export default function ScheduleTable({ sessions }: ScheduleTableProps) {
7474
} else {
7575
params.set("day", newDay);
7676
}
77-
router.push(`${pathname}?${params.toString()}`, { scroll: false });
77+
router.replace(`${pathname}?${params.toString()}`, { scroll: false });
7878
};
7979

8080
useEffect(() => {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default function ConnectButton() {}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"use client";
2+
3+
import { UserService } from "@/services/UserService";
4+
import { ThumbsDown } from "lucide-react";
5+
import { useRouter } from "next/navigation";
6+
7+
interface DemoteButtonProps {
8+
cannonToken: string;
9+
userId: string;
10+
}
11+
12+
export default function DemoteButton({
13+
cannonToken,
14+
userId,
15+
}: DemoteButtonProps) {
16+
const router = useRouter();
17+
18+
async function handleDemote() {
19+
if (await UserService.demote(cannonToken, userId)) router.refresh();
20+
else alert("Failed to demote!");
21+
}
22+
23+
return (
24+
<button
25+
className="button-primary !bg-sinfo-secondary text-sm w-full mt-2"
26+
onClick={handleDemote}
27+
>
28+
<ThumbsDown size={16} />
29+
Demote
30+
</button>
31+
);
32+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import authOptions from "@/app/api/auth/[...nextauth]/authOptions";
2+
import GridList from "@/components/GridList";
3+
import List from "@/components/List";
4+
import AchievementTile from "@/components/user/AchievementTile";
5+
import CurriculumVitae from "@/components/user/CurriculumVitae";
6+
import ProfileHeader from "@/components/user/ProfileHeader";
7+
import ProfileInformations from "@/components/user/ProfileInformations";
8+
import { AchievementService } from "@/services/AchievementService";
9+
import { UserService } from "@/services/UserService";
10+
import { isCompany, isMember } from "@/utils/utils";
11+
import { UserPlus } from "lucide-react";
12+
import { getServerSession } from "next-auth";
13+
import DemoteButton from "./DemoteButton";
14+
15+
interface UserProfileParams {
16+
id: string;
17+
}
18+
19+
export default async function UserProfile({
20+
params,
21+
}: {
22+
params: UserProfileParams;
23+
}) {
24+
const { id: userID } = params;
25+
26+
const session = (await getServerSession(authOptions))!;
27+
const user: User | null = await UserService.getMe(session.cannonToken);
28+
if (!user) return;
29+
30+
const userProfile = await UserService.getUser(session.cannonToken, userID);
31+
if (!userProfile) return <div>User not found.</div>;
32+
33+
const isMyself = userProfile.id === user.id;
34+
35+
const achievements = await AchievementService.getAchievements();
36+
const userAchievements = achievements?.filter((a) =>
37+
a.users?.includes(userProfile.id),
38+
);
39+
40+
return (
41+
<div className="container m-auto h-full">
42+
<ProfileHeader user={userProfile} />
43+
{!isMyself && (
44+
<div className="px-4 py-2">
45+
<button className="button-primary text-sm w-full mt-2">
46+
<UserPlus size={16} />
47+
Connect
48+
</button>
49+
{isMember(user.role) &&
50+
(isMember(userProfile.role) || isCompany(userProfile.role)) && (
51+
<DemoteButton
52+
cannonToken={session.cannonToken}
53+
userId={userProfile.id}
54+
/>
55+
)}
56+
</div>
57+
)}
58+
59+
{/* Notes */}
60+
{/* <List title="Notes">
61+
{userNotes && userNotes.trim() !== "" ? (
62+
<ShowMore lines={3}>
63+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
64+
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
65+
ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
66+
aliquip ex ea commodo consequat. Duis aute irure dolor in
67+
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
68+
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
69+
culpa qui officia deserunt mollit anim id est laborum.
70+
</ShowMore>
71+
) : (
72+
<ListCard title="Write a note" icon={NotebookPen} />
73+
)}
74+
</List> */}
75+
76+
{!isCompany(userProfile.role) &&
77+
(isCompany(user.role) || isMember(user.role)) && (
78+
<List title="Curriculum Vitae (CV)">
79+
<CurriculumVitae user={userProfile} session={session} />
80+
</List>
81+
)}
82+
83+
{/* User information */}
84+
<ProfileInformations user={userProfile} />
85+
86+
{/* Achievements */}
87+
{userAchievements?.length ? (
88+
<GridList
89+
title="Achievements"
90+
description={`Total points: ${userAchievements?.reduce((acc, a) => acc + a.value, 0) || 0}`}
91+
scrollable
92+
>
93+
{userAchievements.map((a) => (
94+
<AchievementTile key={a.id} achievement={a} achieved />
95+
))}
96+
</GridList>
97+
) : (
98+
<></>
99+
)}
100+
</div>
101+
);
102+
}

src/components/List.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export interface ListProps {
66
description?: string;
77
link?: string;
88
linkText?: string;
9-
linkProps?: LinkProps;
9+
linkProps?: Partial<LinkProps>;
1010
bottomLink?: string;
1111
bottomLinkText?: string;
1212
bottomLinkProps?: LinkProps;

src/components/ListCard.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,19 @@ interface ListCardProps {
2020
extraComponent?: ReactNode;
2121
}
2222

23+
interface ConditionalLinkProps extends Partial<LinkProps> {
24+
children: ReactNode;
25+
}
26+
27+
function ConditionalLink({ children, href, ...props }: ConditionalLinkProps) {
28+
if (!href) return children;
29+
return (
30+
<Link href={href} {...props}>
31+
{children}
32+
</Link>
33+
);
34+
}
35+
2336
export default function ListCard({
2437
title,
2538
img,
@@ -37,7 +50,7 @@ export default function ListCard({
3750
extraClassName,
3851
}: ListCardProps) {
3952
return (
40-
<Link href={link || "#"} {...linkProps}>
53+
<ConditionalLink href={link} {...linkProps}>
4154
<div
4255
className={`min-w-[340px] min-h-[74px] px-4 py-2 flex items-center justify-start gap-x-4 bg-white rounded-md shadow-md text-sm overflow-hidden hover:bg-slate-50 hover:shadow-sm active:bg-gray-200 active:shadow-none ${extraClassName || ""}`}
4356
>
@@ -78,6 +91,6 @@ export default function ListCard({
7891
</div>
7992
{extraComponent}
8093
</div>
81-
</Link>
94+
</ConditionalLink>
8295
);
8396
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import ListCard from "../ListCard";
2+
3+
interface AcademicTileProps {
4+
school: string;
5+
degree: string;
6+
field: string;
7+
grade?: string;
8+
start?: string;
9+
end?: string;
10+
}
11+
12+
export default function AcademicTile({
13+
school,
14+
degree,
15+
field,
16+
grade,
17+
start,
18+
end,
19+
}: AcademicTileProps) {
20+
let label;
21+
if (start && end) {
22+
const startDate = new Date(start);
23+
const endDate = new Date(end);
24+
label = `${startDate.getFullYear()} - ${endDate.getMonth().toString().padStart(2, "0")}/${endDate.getFullYear()}`;
25+
}
26+
27+
return (
28+
<ListCard title={field} subtitle={school} headtext={degree} label={label} />
29+
);
30+
}

src/components/user/ProfileHeader.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
import Image from "next/image";
22
import { SocialNetwork } from "@/components/SocialNetwork";
3-
import { convertToAppRole } from "@/utils/utils";
3+
import { convertToAppRole, isCompany } from "@/utils/utils";
4+
import { CompanyService } from "@/services/CompanyService";
45

56
interface ProfileHeaderProps {
67
user: User;
78
}
89

9-
export default function ProfileHeader({ user }: ProfileHeaderProps) {
10+
export default async function ProfileHeader({ user }: ProfileHeaderProps) {
11+
const company =
12+
isCompany(user.role) && user.company?.length
13+
? await CompanyService.getCompany(user.company[0].company)
14+
: undefined;
15+
1016
return (
1117
<>
1218
<header className="bg-sinfo-primary h-[150px] mb-6">
@@ -21,6 +27,8 @@ export default function ProfileHeader({ user }: ProfileHeaderProps) {
2127
<div className="p-4">
2228
<span className="text-gray-600 uppercase text-xs">
2329
{convertToAppRole(user.role)}
30+
&nbsp;
31+
{company && `(${company.name})`}
2432
</span>
2533
<h5 className="text-lg font-bold">{user.name}</h5>
2634
{user.title && <p className="text-gray-600">{user.title}</p>}

0 commit comments

Comments
 (0)