Skip to content

Commit e374be9

Browse files
committed
added aboutme, skills and role to backend
1 parent 1ed5852 commit e374be9

File tree

6 files changed

+118
-18
lines changed

6 files changed

+118
-18
lines changed

nextstep-backend/src/controllers/users_controller.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,25 @@ export const getUserById = async (req: Request, res: Response): Promise<void> =>
3636
}
3737

3838

39+
export const updateUserProfile = async (req: Request, res: Response) => {
40+
const { aboutMe, skills, selectedRole } = req.body;
41+
42+
if (!req.params.id) {
43+
return res.status(400).json({ error: 'User ID is required' });
44+
}
45+
46+
try {
47+
const updatedUser = await usersService.updateUserProfile(req.params.id, aboutMe, skills, selectedRole);
48+
if (!updatedUser) {
49+
return res.status(404).json({ error: 'User not found' });
50+
}
51+
52+
res.status(200).json(updatedUser);
53+
} catch (error) {
54+
handleError(error, res);
55+
}
56+
};
57+
3958
export const updateUserById = async (req: Request, res: Response): Promise<void> => {
4059
try {
4160
const user = await usersService.updateUserById(req.params.id, req.body);

nextstep-backend/src/models/user_model.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,18 @@ const userSchema: Schema = new Schema({
2323
type: Date,
2424
default: Date.now
2525
},
26+
aboutMe: {
27+
type: String,
28+
default: ""
29+
},
30+
skills: {
31+
type: [String],
32+
default: []
33+
},
34+
selectedRole: {
35+
type: String,
36+
default: ""
37+
},
2638
authProvider: {
2739
type: String
2840
}
@@ -37,7 +49,10 @@ userSchema.set('toJSON', {
3749
password: ret.password,
3850
imageFilename: ret?.imageFilename,
3951
createdAt: ret.createdAt,
40-
updatedAt: ret.updatedAt
52+
updatedAt: ret.updatedAt,
53+
aboutMe: ret?.aboutMe,
54+
skills: ret?.skills,
55+
selectedRole: ret?.selectedRole,
4156
};
4257
}
4358
});

nextstep-backend/src/routes/users_routes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ router.delete('/:id', validateUserId, handleValidationErrors, (req: Request, res
1313

1414
router.patch('/:id', validateUserId, validateUserDataOptional, handleValidationErrors, (req: Request, res: Response) => usersController.updateUserById(req, res));
1515

16-
// router.post('/', validateUserRegister, handleValidationErrors, (req: Request, res: Response) => usersController.createUser(req, res));
16+
router.put('/:id', validateUserId, handleValidationErrors, (req: Request, res: Response) => usersController.updateUserProfile(req, res));
1717

1818

1919
export default router;

nextstep-backend/src/services/users_service.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ export const getUserById = async (id: string): Promise<UserData | null> => {
3838
};
3939

4040

41+
export const updateUserProfile = async (userId: string, aboutMe: string, skills: string[], selectedRole: string) => {
42+
const updatedUser = await UserModel.findByIdAndUpdate(
43+
userId,
44+
{ aboutMe, skills, selectedRole },
45+
{ new: true, runValidators: true }
46+
);
47+
return updatedUser ? userToUserData(updatedUser) : null;
48+
};
4149

4250
export const updateUserById = async (id: string, updateData: Partial<UserData>): Promise<UserData | null> => {
4351
if (updateData.password) {

nextstep-backend/src/types/user_types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ export interface UserData {
1717
imageFilename?: string;
1818
createdAt?: string,
1919
updatedAt?: string,
20+
aboutMe?: string;
21+
skills?: string[];
22+
selectedRole?: string;
2023
}
2124

2225

nextstep-frontend/src/pages/MainDashboard.tsx

Lines changed: 71 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,10 @@ const skillsList = [
7676

7777
const MainDashboard: React.FC = () => {
7878
const theme = useTheme()
79-
const [aboutMe, setAboutMe] = useState(() => localStorage.getItem("aboutMe") || "")
80-
const [skills, setSkills] = useState<string[]>(() => JSON.parse(localStorage.getItem("skills") || "[]"))
79+
const [aboutMe, setAboutMe] = useState("")
80+
const [skills, setSkills] = useState<string[]>([])
8181
const [newSkill, setNewSkill] = useState("")
82-
const [selectedRole, setSelectedRole] = useState(() => localStorage.getItem("selectedRole") || "")
82+
const [selectedRole, setSelectedRole] = useState("")
8383
const [repos, setRepos] = useState<{ id: number; name: string; html_url: string }[]>([])
8484
const [useOAuth, setUseOAuth] = useState(true)
8585
const [showAuthOptions, setShowAuthOptions] = useState(false)
@@ -114,17 +114,6 @@ const MainDashboard: React.FC = () => {
114114
setShowJobRecommendations(!showJobRecommendations)
115115
}
116116

117-
// Persist to localStorage
118-
useEffect(() => {
119-
localStorage.setItem("aboutMe", aboutMe)
120-
}, [aboutMe])
121-
useEffect(() => {
122-
localStorage.setItem("skills", JSON.stringify(skills))
123-
}, [skills])
124-
useEffect(() => {
125-
localStorage.setItem("selectedRole", selectedRole)
126-
}, [selectedRole])
127-
128117
// Handle GitHub OAuth callback
129118
useEffect(() => {
130119
const code = new URLSearchParams(window.location.search).get("code")
@@ -146,6 +135,13 @@ const MainDashboard: React.FC = () => {
146135
useEffect(() => {
147136
const fetchResumeData = async () => {
148137
try {
138+
const user_data = await api.get("/user/" + getUserAuth().userId);
139+
if (user_data.data.aboutMe) {
140+
setAboutMe(user_data.data.aboutMe || "")
141+
setSkills(user_data.data.skills || [])
142+
setSelectedRole(user_data.data.selectedRole || "")
143+
}
144+
149145
const response = await api.get("/resume")
150146
if (response.data && response.data.parsedData) {
151147
const parsedData = response.data.parsedData
@@ -184,11 +180,13 @@ const MainDashboard: React.FC = () => {
184180
const trimmed = skill.trim()
185181
if (!trimmed || skills.includes(trimmed)) return
186182
setSkills((prev) => [trimmed, ...prev])
183+
setIsProfileDirty(true);
187184
setNewSkill("")
188185
}
189186

190187
const handleDeleteSkill = (skillToDelete: string) => {
191188
setSkills((prev) => prev.filter((s) => s !== skillToDelete))
189+
setIsProfileDirty(true);
192190
}
193191

194192
const handleGitHubConnect = async () => {
@@ -256,6 +254,7 @@ const MainDashboard: React.FC = () => {
256254
setResumeExperience(aiExp)
257255
setCurrentResumeId(uploadedResume)
258256
localStorage.setItem("lastResumeId", uploadedResume)
257+
updateUserProfile(aiAbout, aiSkills, aiRole);
259258
setHasResumeChanged(false)
260259
} catch (err) {
261260
console.error(err)
@@ -282,6 +281,7 @@ const MainDashboard: React.FC = () => {
282281
setResumeExperience(aiExp)
283282
localStorage.setItem("lastResumeId", currentResumeId)
284283
setHasResumeChanged(false)
284+
updateUserProfile(aiAbout, aiSkills, aiRole);
285285
} catch (err) {
286286
console.error(err)
287287
alert("Failed to sync with resume.")
@@ -333,8 +333,52 @@ const MainDashboard: React.FC = () => {
333333

334334
const handleRemoveAllSkills = () => {
335335
setSkills([])
336+
setIsProfileDirty(true);
336337
}
337338

339+
const [isProfileDirty, setIsProfileDirty] = useState(false);
340+
const [isSaving, setIsSaving] = useState(false);
341+
342+
343+
useEffect(() => {
344+
setIsProfileDirty(true);
345+
}, [aboutMe, skills, selectedRole]);
346+
347+
const handleAboutMeChange = (e: React.ChangeEvent<HTMLInputElement>) => {
348+
setAboutMe(e.target.value);
349+
setIsProfileDirty(true);
350+
};
351+
352+
const handleSelectedRoleChange = (_: any, val: string | null) => {
353+
setSelectedRole(val || "");
354+
setIsProfileDirty(true);
355+
};
356+
357+
const updateUserProfile = async (newAboutMe?: string, newSkills?: string, newSelectedRole?: string) => {
358+
setIsSaving(true);
359+
try {
360+
await api.put(`/user/${getUserAuth().userId}`, {
361+
aboutMe: newAboutMe ? newAboutMe : aboutMe,
362+
skills: newSkills ? newSkills : skills,
363+
selectedRole: newSelectedRole ? newSelectedRole : selectedRole,
364+
}, {
365+
headers: {
366+
Authorization: `Bearer ${getUserAuth().accessToken}`,
367+
},
368+
});
369+
// // Optionally, update localStorage or context with new values
370+
// localStorage.setItem("aboutMe", aboutMe);
371+
// localStorage.setItem("skills", JSON.stringify(skills));
372+
// localStorage.setItem("selectedRole", selectedRole);
373+
// setIsProfileDirty(false);
374+
} catch (error) {
375+
console.error("Failed to update profile:", error);
376+
alert("Failed to update profile. Please try again.");
377+
} finally {
378+
setIsSaving(false);
379+
}
380+
};
381+
338382
return (
339383
<Box sx={{ minHeight: "100vh", py: 4 }}>
340384
<Container maxWidth="xl">
@@ -560,7 +604,7 @@ const MainDashboard: React.FC = () => {
560604
variant="outlined"
561605
placeholder="Describe your background, experience, and career aspirations..."
562606
value={aboutMe}
563-
onChange={(e) => setAboutMe(e.target.value)}
607+
onChange={handleAboutMeChange}
564608
sx={{
565609
"& .MuiOutlinedInput-root": {
566610
borderRadius: 3,
@@ -756,7 +800,7 @@ const MainDashboard: React.FC = () => {
756800
freeSolo
757801
options={roles}
758802
value={selectedRole}
759-
onInputChange={(_, val) => setSelectedRole(val)}
803+
onInputChange={handleSelectedRoleChange}
760804
renderInput={(params) => (
761805
<TextField
762806
{...params}
@@ -1090,6 +1134,17 @@ const MainDashboard: React.FC = () => {
10901134
</Stack>
10911135
</Grid>
10921136
</Grid>
1137+
{isProfileDirty && (
1138+
<Button
1139+
variant="contained"
1140+
color="primary"
1141+
onClick={updateUserProfile}
1142+
disabled={isSaving}
1143+
sx={{ mt: 2 }}
1144+
>
1145+
{isSaving ? <CircularProgress size={24} /> : "Save Changes"}
1146+
</Button>
1147+
)}
10931148
</Container>
10941149
</Box>
10951150
)

0 commit comments

Comments
 (0)