Skip to content

Commit 203ea32

Browse files
committed
Merge branch 'ms3-frontend' into ms3-jmsandiegoo/frontend-onboarding
2 parents 1375df3 + bb1e83e commit 203ea32

34 files changed

+1437
-59
lines changed

backend/auth-service/src/app.service.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export class AppService {
5656
const tokens = await this.generateTokens({
5757
id: userId,
5858
email: newUser.email,
59+
isOnboarded: newUser.isOnboarded,
5960
roles: newUser.roles,
6061
});
6162
await this.updateRefreshToken({
@@ -93,6 +94,7 @@ export class AppService {
9394
const tokens = await this.generateTokens({
9495
id: userId,
9596
email: user.email,
97+
isOnboarded: user.isOnboarded,
9698
roles: user.roles,
9799
});
98100
await this.updateRefreshToken({
@@ -141,6 +143,7 @@ export class AppService {
141143
const tokens = await this.generateTokens({
142144
id: id,
143145
email: user.email,
146+
isOnboarded: user.isOnboarded,
144147
roles: user.roles,
145148
});
146149
await this.updateRefreshToken({ id, refreshToken: tokens.refresh_token });
@@ -286,25 +289,23 @@ export class AppService {
286289

287290
// Could include other fields like roles in the future
288291
private async generateTokens(payload: TokenPayload): Promise<Token> {
289-
const { id, email, roles } = payload;
292+
const { id, ...rest } = payload;
290293

291294
const [accessToken, refreshToken] = await Promise.all([
292295
this.jwtService.signAsync(
293-
{
294-
sub: id,
295-
email,
296-
roles,
297-
},
298-
{
299-
secret: process.env.JWT_SECRET,
300-
expiresIn: '15m', // 15 minute
296+
{
297+
sub: id,
298+
...rest,
299+
},
300+
{
301+
secret: process.env.JWT_SECRET,
302+
expiresIn: '15m', // 15 minutes
301303
},
302304
),
303305
this.jwtService.signAsync(
304306
{
305307
sub: id,
306-
email,
307-
roles,
308+
...rest,
308309
},
309310
{
310311
secret: process.env.JWT_REFRESH_SECRET,
@@ -365,6 +366,7 @@ export class AppService {
365366
const jwtTokens = await this.generateTokens({
366367
id: user._id.toString(),
367368
email: user.email,
369+
isOnboarded: user.isOnboarded,
368370
roles: user.roles,
369371
});
370372

@@ -467,6 +469,7 @@ export class AppService {
467469
const jwtTokens = await this.generateTokens({
468470
id: user._id.toString(),
469471
email: user.email,
472+
isOnboarded: user.isOnboarded,
470473
roles: user.roles,
471474
});
472475

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export interface TokenPayload {
22
id: string;
33
email: string;
4+
isOnboarded: boolean;
45
roles: string[];
56
}

backend/gateway-service/src/modules/auth/auth.controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
import { AuthDto, ResetPasswordDto, ResetPasswordRequestDto } from './dto';
2020
import { Token } from './interfaces';
2121
import { ClientProxy } from '@nestjs/microservices';
22-
import { first, firstValueFrom } from 'rxjs';
22+
import { firstValueFrom } from 'rxjs';
2323
import { RtAuthGuard } from '../../common/guards';
2424
import { GetCurrentUserId } from 'src/common/decorators/get-current-user-id.decorator';
2525
import { GetCurrentUser, Public } from 'src/common/decorators';
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
"use client";
2+
3+
import {
4+
Dialog,
5+
DialogContent,
6+
DialogHeader,
7+
DialogTitle,
8+
} from "@/components/ui/dialog";
9+
10+
import React, { useCallback } from "react";
11+
import { zodResolver } from "@hookform/resolvers/zod";
12+
import { useForm } from "react-hook-form";
13+
import { z } from "zod";
14+
15+
import { Form, FormLabel } from "@/components/ui/form";
16+
import { Button } from "@/components/ui/button";
17+
import { TextInput } from "@/components/form/TextInput";
18+
import { RadioGroupInput } from "@/components/form/RadioGroupInput";
19+
import { useToast } from "@/hooks/use-toast";
20+
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
21+
import { CodeXml } from "lucide-react";
22+
import { updateProfile } from "@/services/profileService";
23+
import { useRouter } from "next/navigation";
24+
25+
interface EditProfileModalProps {
26+
isOpen: boolean;
27+
setIsOpen: (open: boolean) => void;
28+
userProfile: {
29+
displayName: string;
30+
username: string;
31+
email: string;
32+
proficiency: string;
33+
};
34+
}
35+
36+
const FormSchema = z.object({
37+
displayName: z.string().min(1, "Display Name is required"),
38+
username: z.string().min(1, "Username is required"),
39+
email: z.string().email("Invalid email format"),
40+
proficiency: z.enum(["Beginner", "Intermediate", "Advanced"]),
41+
});
42+
43+
export function EditProfile({
44+
isOpen,
45+
setIsOpen,
46+
userProfile,
47+
}: EditProfileModalProps) {
48+
const { toast } = useToast();
49+
const router = useRouter();
50+
51+
const form = useForm<z.infer<typeof FormSchema>>({
52+
resolver: zodResolver(FormSchema),
53+
defaultValues: {
54+
displayName: userProfile.displayName,
55+
username: userProfile.username,
56+
email: userProfile.email,
57+
},
58+
});
59+
60+
const onSubmit = useCallback(
61+
async (data: z.infer<typeof FormSchema>) => {
62+
const response = await updateProfile(data);
63+
64+
if (response.statusCode !== 200) {
65+
toast({
66+
variant: "destructive",
67+
title: "Error updating profile",
68+
description: response.message,
69+
});
70+
return;
71+
} else {
72+
toast({
73+
title: "Profile updated!",
74+
description: "Your profile has been updated successfully.",
75+
});
76+
setIsOpen(false);
77+
router.refresh();
78+
}
79+
},
80+
81+
[toast, setIsOpen, router]
82+
);
83+
84+
return (
85+
<Dialog open={isOpen} onOpenChange={setIsOpen}>
86+
<DialogContent className="max-w-md">
87+
<DialogHeader>
88+
<DialogTitle className="text-primary">Edit Profile</DialogTitle>
89+
<Form {...form}>
90+
<form
91+
onSubmit={form.handleSubmit(onSubmit)}
92+
className="flex flex-col space-y-4"
93+
>
94+
{/* Profile Image Upload */}
95+
{/*<FormLabel className="pt-8">Profile Image</FormLabel>
96+
<div className="flex flex-row justify-center items-center p-2">
97+
<input
98+
type="file"
99+
className="hidden"
100+
id="profile-upload"
101+
accept="image/*"
102+
/>
103+
104+
<Avatar>
105+
<AvatarImage/>
106+
<AvatarFallback className="text-base font-normal text-foreground">
107+
<CodeXml/>
108+
</AvatarFallback>
109+
</Avatar>
110+
111+
<div className="pl-6">
112+
<label
113+
htmlFor="profile-upload"
114+
className="bg-background-200 text-sm rounded-lg font-bold p-2 cursor-pointer"
115+
>
116+
Upload Image
117+
</label>
118+
<DialogDescription className="pt-2">.png, .jpeg files up to 2MB. Recommended size is 256x256px.</DialogDescription>
119+
</div>
120+
</div> */}
121+
122+
{/* Display Name */}
123+
<TextInput label="Display Name" name="displayName" placeholder="Display Name" />
124+
125+
{/* Username */}
126+
<TextInput label="Username" name="username" placeholder="Username" />
127+
{form.formState.errors.username && (
128+
<p className="text-destructive text-sm">
129+
{form.formState.errors.username.message || "Username already taken"}
130+
</p>
131+
)}
132+
133+
{/* Email */}
134+
{/* <TextInput label="Email" name="email" placeholder="Email" /> */}
135+
136+
{/* Proficiency Radio Buttons */}
137+
<RadioGroupInput
138+
label="Proficiency"
139+
name="proficiency"
140+
options={[
141+
{ value: "Beginner", optionLabel: "Beginner" },
142+
{ value: "Intermediate", optionLabel: "Intermediate" },
143+
{ value: "Advanced", optionLabel: "Advanced" },
144+
]}
145+
/>
146+
147+
<Button className="p-5" type="submit">
148+
{form.formState.isSubmitting
149+
? "Updating Profile"
150+
: "Save"}
151+
</Button>
152+
</form>
153+
</Form>
154+
</DialogHeader>
155+
</DialogContent>
156+
</Dialog>
157+
);
158+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
'use client'; // Make only this component a client component
2+
3+
import { useState } from 'react';
4+
import { EditProfile } from './EditProfile';
5+
import { Profile } from '@/types/Profile';
6+
7+
interface EditProfileButtonProps {
8+
profileDetails: Profile;
9+
}
10+
11+
export default function EditProfileButton({ profileDetails }: EditProfileButtonProps) {
12+
const [isOpen, setIsOpen] = useState(false);
13+
14+
return (
15+
<>
16+
<button
17+
onClick={() => setIsOpen(true)}
18+
className="bg-primary text-sm font-semibold py-2 rounded-md"
19+
>
20+
Edit Profile
21+
</button>
22+
23+
{/* Edit Profile Modal */}
24+
{isOpen && (
25+
<EditProfile
26+
isOpen={isOpen}
27+
setIsOpen={setIsOpen}
28+
userProfile={profileDetails}
29+
/>
30+
)}
31+
</>
32+
);
33+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { Card } from "@/components/ui/card";
2+
import CollaborationHeatmap from "./HeatMap";
3+
4+
export default function GitGraph() {
5+
const monthsData = [
6+
[1, 0, 0, 0, 1, 1, 0, 1, 0, 0], // January
7+
[0, 1, 1, 0, 0, 1, 0, 1, 0, 0], // February
8+
[1, 0, 0, 1, 0, 1, 1, 1, 0, 0], // March
9+
[1, 0, 0, 0, 1, 1, 0, 1, 0, 0], // April
10+
[0, 1, 1, 0, 0, 1, 0, 1, 0, 0], // May
11+
[1, 0, 0, 1, 0, 1, 1, 1, 0, 0], // June
12+
[1, 0, 0, 0, 1, 1, 0, 1, 0, 0], // July
13+
[0, 1, 1, 0, 0, 1, 0, 1, 0, 0], // August
14+
[1, 0, 0, 1, 0, 1, 1, 1, 0, 0], // Sept
15+
[1, 0, 0, 0, 1, 1, 0, 1, 0, 0], // Oct
16+
[0, 1, 1, 0, 0, 1, 0, 1, 0, 0], // Nov
17+
[1, 0, 0, 1, 0, 1, 1, 1, 0, 0], // Dec
18+
];
19+
20+
return (
21+
<Card className="flex flex-col rounded-md p-6 gap-4">
22+
<StatsHeader />
23+
<div className="flex flex-row">
24+
<StatsDetails/>
25+
<CollaborationHeatmap
26+
monthsData={monthsData}
27+
year={2024}
28+
/>
29+
</div>
30+
</Card>
31+
)
32+
}
33+
34+
function StatsHeader() {
35+
return (
36+
<div className="flex flex-row gap-2">
37+
<big className="font-bold">15</big>
38+
<big className="text-card-foreground-100"> collaborations done in the past one year</big>
39+
</div>
40+
)
41+
}
42+
function StatsDetails() {
43+
return (
44+
<div className="flex flex-col gap-2 pt-2 pl-2 pr-4">
45+
<div className="flex flex-col p-1">
46+
<small className="font-bold"> Total Active: </small>
47+
<small className="text-card-foreground-100"> 15 Days </small>
48+
</div>
49+
<div className="flex flex-col p-1">
50+
<small className="font-bold"> Max Streaks: </small>
51+
<small className="text-card-foreground-100"> 3 Days </small>
52+
</div>
53+
</div>
54+
)
55+
}
56+
57+
function MonthlyCommits({ month } : { month: string }) {
58+
59+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
interface CollaborationHeatmapProps {
2+
monthsData: Array<Array<number>>;
3+
year: number;
4+
}
5+
6+
export default function CollaborationHeatmap({
7+
monthsData,
8+
year,
9+
}: CollaborationHeatmapProps) {
10+
return (
11+
<section className="flex flex-row gap-8 rounded-lg max-w-4xl">
12+
<div className="grid grid-cols-12 gap-2 mt-4">
13+
{monthsData.map((month, monthIndex) => (
14+
<div key={monthIndex} className="flex flex-col items-center">
15+
<div className="grid grid-cols-3 gap-1">
16+
{month.map((active, dayIndex) => (
17+
<div
18+
key={dayIndex}
19+
className={`w-4 h-4 ${active ? "bg-primary" : "bg-background-100"}`}
20+
/>
21+
))}
22+
</div>
23+
<p className="text-sm text-card-foreground-100 pt-4">{getMonthName(monthIndex)}</p>
24+
</div>
25+
))}
26+
</div>
27+
28+
<div className="flex flex-col items-center">
29+
{[2024, 2023, 2022, 2021].map((yr) => (
30+
<button
31+
key={yr}
32+
className={`w-20 py-1 font-bold my-1 rounded-md ${year === yr ? "bg-primary" : "text-card-foreground-100"}`}
33+
>
34+
{yr}
35+
</button>
36+
))}
37+
</div>
38+
39+
</section>
40+
);
41+
}
42+
43+
// Convert month index to name
44+
function getMonthName(index: number): string {
45+
const months = [
46+
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
47+
"Jul", "Aug", "Sept", "Oct", "Nov", "Dec"
48+
];
49+
return months[index];
50+
}

0 commit comments

Comments
 (0)