Skip to content

Commit dbb98ac

Browse files
committed
Add bios page for courses, fix word wrapping
1 parent 32e1a64 commit dbb98ac

File tree

14 files changed

+538
-243
lines changed

14 files changed

+538
-243
lines changed

csm_web/frontend/src/components/App.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ const App = () => {
4444
<Route path="policies/*" element={<Policies />} />
4545
<Route path="export/*" element={<DataExport />} />
4646
<Route path="profile/*" element={<UserProfile />} />
47-
<Route path="profile/:id/*" element={<UserProfile />} />
4847
<Route path="*" element={<NotFound />} />
4948
</Route>
5049
</Routes>

csm_web/frontend/src/components/UserProfile.tsx

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { useState } from "react";
2-
import { useParams } from "react-router-dom";
2+
import { Routes, Route, useParams } from "react-router-dom";
33
import { PermissionError } from "../utils/queries/helpers";
44
import { UpdateUserMutationResponse, useUserInfo, useUserInfoUpdateMutation } from "../utils/queries/profiles";
55
import LoadingSpinner from "./LoadingSpinner";
@@ -26,6 +26,15 @@ const MAX_SIZE_MB = 2;
2626
const MAX_FILE_SIZE_BYTES = MAX_SIZE_MB * 1024 * 1024;
2727

2828
const UserProfile: React.FC = () => {
29+
return (
30+
<Routes>
31+
<Route path=":id/*" element={<UserProfileContent />} />
32+
<Route index element={<UserProfileContent />} />
33+
</Routes>
34+
);
35+
};
36+
37+
const UserProfileContent: React.FC = () => {
2938
let userId = Number(useParams().id);
3039

3140
// We always need to get the current user and the viewed profile to check if the current profile page being viewed is the current user (and therefore editable)
@@ -101,6 +110,25 @@ const UserProfile: React.FC = () => {
101110
const handleFormSubmit = () => {
102111
setShowSaveSpinner(true);
103112

113+
// Length checks
114+
if (formData["preferredName"].length > 100) {
115+
setValidationText("Preferred name is over 100 characters.");
116+
setShowSaveSpinner(false);
117+
return;
118+
} else if (formData["pronouns"].length > 50) {
119+
setValidationText("Pronouns are over 50 characters.");
120+
setShowSaveSpinner(false);
121+
return;
122+
} else if (formData["pronunciation"].length > 100) {
123+
setValidationText("Pronounciation is over 100 characters.");
124+
setShowSaveSpinner(false);
125+
return;
126+
} else if (formData["bio"].length > 700) {
127+
setValidationText("Bio is over 700 characters.");
128+
setShowSaveSpinner(false);
129+
return;
130+
}
131+
104132
const userInfo = new FormData();
105133
userInfo.append("id", userId.toString());
106134

@@ -126,7 +154,8 @@ const UserProfile: React.FC = () => {
126154
updateMutation.mutate(userInfo, {
127155
onSuccess: () => {
128156
setFile("");
129-
setViewing(true); // Exit edit mode after successful save
157+
setValidationText("");
158+
setViewing(true);
130159
setShowSaveSpinner(false);
131160
},
132161
onError: ({ detail }: UpdateUserMutationResponse) => {
@@ -166,9 +195,9 @@ const UserProfile: React.FC = () => {
166195
className="user-profile-tooltip"
167196
>
168197
<div>
169-
Edit your profile so others can get to know you. <br /> <br />
170-
Students: your profile is visible to mentors and coordinators. <br />
171-
Mentors: your profile is publicly viewable.
198+
Edit your profile so others can learn about you. <br /> <br />
199+
Students: visible to your mentors and coordinators. <br />
200+
Mentors: visible to everyone.
172201
</div>
173202
</Tooltip>
174203
)}
@@ -214,7 +243,7 @@ const UserProfile: React.FC = () => {
214243
</div>
215244
</div>
216245
<div className="user-profile-bio">
217-
{requestedData.bio?.trim() && <p className="form-static">{requestedData.bio}</p>}
246+
{requestedData.bio?.trim() && <p className="user-profile-text">{requestedData.bio}</p>}
218247
</div>
219248

220249
<div className="form-actions">
@@ -286,6 +315,7 @@ const UserProfile: React.FC = () => {
286315
type="text"
287316
id="preferredName"
288317
name="preferredName"
318+
maxLength={100}
289319
className="form-input user-profile-input-field"
290320
value={formData.preferredName}
291321
onChange={handleInputChange}
@@ -299,7 +329,7 @@ const UserProfile: React.FC = () => {
299329
type="text"
300330
id="pronouns"
301331
name="pronouns"
302-
maxLength={20}
332+
maxLength={50}
303333
className="form-input user-profile-input-field"
304334
value={formData.pronouns}
305335
onChange={handleInputChange}
@@ -313,7 +343,7 @@ const UserProfile: React.FC = () => {
313343
type="text"
314344
id="pronunciation"
315345
name="pronunciation"
316-
maxLength={50}
346+
maxLength={100}
317347
className="form-input user-profile-input-field"
318348
value={formData.pronunciation}
319349
onChange={handleInputChange}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import React from "react";
2+
import { Link } from "react-router-dom";
3+
import { formatSpacetimeInterval } from "../../utils/datetime";
4+
import { RawUserInfo, Spacetime } from "../../utils/types";
5+
import ClockIcon from "../../../static/frontend/img/clock.svg";
6+
import LogoNoText from "../../../static/frontend/img/logo_no_text.svg";
7+
8+
interface BioEntryProps {
9+
user: RawUserInfo;
10+
sectionTimes: Spacetime[][];
11+
}
12+
13+
const BioEntry = ({ user, sectionTimes }: BioEntryProps) => {
14+
return (
15+
<div key={user.id} className="profile-container">
16+
<Link to={`/profile/${user.id}`}>
17+
<div className={"profile-item profile-image-container" + (user.profileImage?.trim() ? "" : " profile-border")}>
18+
{user.profileImage?.trim() ? (
19+
<img src={user.profileImage} className="profile-image" />
20+
) : (
21+
<LogoNoText id="logo" className="profile-image profile-placeholder" />
22+
)}
23+
</div>
24+
</Link>
25+
<div className="profile-info">
26+
<div className="profile-item">
27+
<p className="profile-text profile-name">
28+
{user.preferredName}
29+
{user.pronouns.trim() && (
30+
<>
31+
&nbsp;
32+
<small>[{user.pronouns.toLowerCase()}]</small>
33+
</>
34+
)}
35+
</p>
36+
</div>
37+
38+
<div className="profile-item">
39+
{user.pronunciation.trim() && (
40+
<>
41+
<p className="profile-text profile-pronunciation">{user.pronunciation}</p>
42+
</>
43+
)}
44+
</div>
45+
46+
<div className="profile-item">
47+
<p className="profile-text profile-email">{user.email}</p>
48+
</div>
49+
50+
<div className="profile-section-times">
51+
{sectionTimes.map((spacetimes, index) => (
52+
<div key={index} className="profile-section-time">
53+
<ClockIcon /> <span>{spacetimes.map(formatSpacetimeInterval).join(" + ")}</span>
54+
</div>
55+
))}
56+
</div>
57+
{user.bio.trim() && <p>{user.bio}</p>}
58+
</div>
59+
</div>
60+
);
61+
};
62+
export default BioEntry;

0 commit comments

Comments
 (0)