From 31955e36faba2ce669410ca36438e099d334cfc9 Mon Sep 17 00:00:00 2001 From: jeydinpham Date: Wed, 8 Apr 2026 18:20:35 -0500 Subject: [PATCH 1/8] style: Begin redesigning this page --- src/components/Profile/AcademicInfo.tsx | 104 ++++++++++------- src/components/Profile/ImageUpdate.tsx | 37 +++--- src/components/Profile/ProfileView.tsx | 110 ++++++++++------- src/components/Profile/UpdateAcademics.tsx | 130 +++++++++------------ src/components/Profile/UpdateName.tsx | 51 +++----- src/components/Socials/ExternalLinks.tsx | 65 +++++------ src/routes/_authed/profile.tsx | 2 - tsconfig.json | 1 + 8 files changed, 248 insertions(+), 252 deletions(-) diff --git a/src/components/Profile/AcademicInfo.tsx b/src/components/Profile/AcademicInfo.tsx index d10aaea..80d7493 100644 --- a/src/components/Profile/AcademicInfo.tsx +++ b/src/components/Profile/AcademicInfo.tsx @@ -2,14 +2,54 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { useQuery } from "@tanstack/react-query"; import UpdateAcademics from "./UpdateAcademics"; import { getOfficerByIdQuery, getOfficerQuery } from "@/queries/officer"; +import type { Officer } from "@/schemas/officer"; type Props = { officerId?: string; archived?: boolean; editable?: boolean; + variant?: "card" | "inline"; }; -export function AcademicInfo({ officerId, archived = false, editable = false }: Props) { +function AcademicContent({ officer }: { officer: Officer }) { + return ( +
+
+
+ Year Standing +
+
+ {officer.yearStanding} +
+
+ +
+
+ Credit Standing +
+
+ {officer.creditStanding} +
+
+ +
+
+ Expected Graduation +
+
+ {officer.expectedGrad.term} {officer.expectedGrad.year} +
+
+
+ ); +} + +export function AcademicInfo({ + officerId, + archived = false, + editable = false, + variant = "card", +}: Props) { const { data: officer } = useQuery( officerId ? getOfficerByIdQuery(officerId, archived) : getOfficerQuery ); @@ -18,6 +58,23 @@ export function AcademicInfo({ officerId, archived = false, editable = false }: return null; } + const content = editable ? ( + + ) : ( + + ); + + if (variant === "inline") { + return ( +
+ + Academic Information + +
{content}
+
+ ); + } + return ( @@ -25,50 +82,7 @@ export function AcademicInfo({ officerId, archived = false, editable = false }: Academic Information - - {editable ? ( - - ) : ( -
-
-
- Net ID -
-
- {officer.netId} -
-
- -
-
-
- Year Standing -
-
- {officer.yearStanding} -
-
-
-
- Credit Standing -
-
- {officer.creditStanding} -
-
-
- -
-
- Expected Graduation -
-
- {officer.expectedGrad.term} {officer.expectedGrad.year} -
-
-
- )} -
+ {content}
); } diff --git a/src/components/Profile/ImageUpdate.tsx b/src/components/Profile/ImageUpdate.tsx index 9d4f8be..e6f752b 100644 --- a/src/components/Profile/ImageUpdate.tsx +++ b/src/components/Profile/ImageUpdate.tsx @@ -15,11 +15,18 @@ type Props = { officerId: string; firstName: string; lastName: string; + editable?: boolean; }; -export function ImageUpdate({ photo, officerId, firstName, lastName }: Props) { +export function ImageUpdate({ + photo, + officerId, + firstName, + lastName, + editable = false, +}: Props) { const avatarOutputSize = 720; const defaultAdjustments: ImageAdjustments = { @@ -243,19 +250,21 @@ export function ImageUpdate({ photo, officerId, firstName, lastName }: Props) { accept="image/*" className="hidden" /> - + {editable && ( + + )} ); diff --git a/src/components/Profile/ProfileView.tsx b/src/components/Profile/ProfileView.tsx index 5f49d8a..3432c8f 100644 --- a/src/components/Profile/ProfileView.tsx +++ b/src/components/Profile/ProfileView.tsx @@ -1,9 +1,11 @@ import { cn } from "@/lib/utils"; +import { useState } from "react"; import { RoleList } from "./RoleList"; import { ExternalLinks } from "../Socials/ExternalLinks"; import { ImageUpdate } from "./ImageUpdate"; import { UserAvatar } from "./UserAvatar"; import { UpdateName } from "./UpdateName"; +import { AcademicInfo } from "./AcademicInfo"; import { getOfficerQuery, getOfficerByIdQuery, @@ -29,6 +31,7 @@ export function ProfileView({ officerId, archived = false, editable = false }: P const { data: officer, isLoading } = useQuery( officerId ? getOfficerByIdQuery(officerId, archived) : getOfficerQuery ); + const [isEditing, setIsEditing] = useState(false); const { data: viewer } = useQuery(getOfficerQuery); const isViewerExecutive = viewer ? isExecutive(viewer) : false; @@ -46,8 +49,8 @@ export function ProfileView({ officerId, archived = false, editable = false }: P } return (
-
-
+
+
{isViewerExecutive && officerId && ( @@ -94,9 +97,9 @@ export function ProfileView({ officerId, archived = false, editable = false }: P className="justify-start rounded-lg text-xs text-destructive hover:bg-destructive/10 hover:text-destructive" disabled={isArchiving || isUnarchiving} onClick={() => - officer.isArchived - ? unarchive(officer.id) - : archive(officer.id) + officer.isArchived + ? unarchive(officer.id) + : archive(officer.id) } > {(isArchiving || isUnarchiving) && ( @@ -110,35 +113,38 @@ export function ProfileView({ officerId, archived = false, editable = false }: P )}
-
-
+
+
- {officer.isActive ? "Active" : "Inactive"} + > +
+ {officer.isActive ? "Active" : "Inactive"} +
-
+
{editable ? ( ) : ( )} + + {editable ? ( + + ) : ( +

+ {officer.firstName} {officer.lastName} +

+ )} + +
+ +
- {editable ? ( - + +
+ - ) : ( -

- {officer.firstName} {officer.lastName} -

- )} - -
- -
-
- + -
- - Socials - - +
+ + Socials + + setIsEditing(true)} + isEditing={isEditing} + onCancelEdit={() => setIsEditing(false)} + onFinishEdit={() => setIsEditing(false)} + /> +
+
); diff --git a/src/components/Profile/UpdateAcademics.tsx b/src/components/Profile/UpdateAcademics.tsx index 26058ca..7098563 100644 --- a/src/components/Profile/UpdateAcademics.tsx +++ b/src/components/Profile/UpdateAcademics.tsx @@ -1,7 +1,6 @@ import { type Officer, StandingSchema, TermSchema } from "@/schemas/officer"; import { useMutation } from "@tanstack/react-query"; import { Button } from "../ui/button"; -import { Input } from "../ui/input"; import { updateAcademicInfoMutationOptions } from "@/queries/officer"; import { Field, @@ -24,7 +23,6 @@ import { toast } from "sonner"; import { useEffect } from "react"; const UpdateAcademicsSchema = z.object({ - netId: z.string().min(1, "Net ID is required"), creditStanding: StandingSchema, yearStanding: StandingSchema, expectedGrad: TermSchema, @@ -37,14 +35,12 @@ export default function UpdateAcademics({ officer }: { officer: Officer }) { const startYear = 2020; // matches TermSchema minimum const years = Array.from({ length: currentYear + 6 - startYear + 1 }, (_, i) => startYear + i); const initialValues = { - netId: officer.netId, creditStanding: officer.creditStanding, yearStanding: officer.yearStanding, expectedGrad: officer.expectedGrad, }; const { - register, handleSubmit, formState: { errors, isDirty }, control, @@ -61,7 +57,6 @@ export default function UpdateAcademics({ officer }: { officer: Officer }) { useEffect(() => { reset(initialValues); }, [ - officer.netId, officer.creditStanding, officer.yearStanding, officer.expectedGrad.term, @@ -71,7 +66,11 @@ export default function UpdateAcademics({ officer }: { officer: Officer }) { const onSubmit = async (data: UpdateAcademicsFormData) => { try { - await updateAcademicInfo({ officerId: officer.id, ...data }); + await updateAcademicInfo({ + officerId: officer.id, + netId: officer.netId, + ...data, + }); reset(data); toast.success("Academic info updated successfully"); } catch (error) { @@ -83,84 +82,67 @@ export default function UpdateAcademics({ officer }: { officer: Officer }) { - - Net ID + + Standing (by year) - ( + + )} /> - + -
- - - - Standing (by year) - - ( - - )} - /> - - - - - - - - Standing (by credit) - - ( - - )} - /> - - - -
+ + + + Standing (by credit) + + ( + + )} + /> + + + Expected Graduation -
-
+
+
-
+
{ - reset(initialValues); - setIsEditing(false); - }; - - if (!isEditing) { + if (!editable) { return (

{firstName} {lastName}

-
); } @@ -114,23 +97,15 @@ export function UpdateName({ officerId, firstName, lastName }: Props) { )}
-
- - -
+ ); } diff --git a/src/components/Socials/ExternalLinks.tsx b/src/components/Socials/ExternalLinks.tsx index 3b7e0fc..0982643 100644 --- a/src/components/Socials/ExternalLinks.tsx +++ b/src/components/Socials/ExternalLinks.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from "react"; +import { useMemo } from "react"; import type { SocialLinks } from "@/schemas/officer"; import { Button } from "@/components/ui/button"; @@ -12,30 +12,26 @@ type Props = { officerId?: string; links: SocialLinks; editable?: boolean; + isEditing?: boolean; + onEditRequest?: () => void; + onCancelEdit?: () => void; + onFinishEdit?: () => void; }; -export function ExternalLinks({ officerId, links, editable = false }: Props) { +export function ExternalLinks({ + officerId, + links, + editable = false, + isEditing = false, + onEditRequest, + onCancelEdit, + onFinishEdit, +}: Props) { const hasLinks = useMemo( () => Boolean(links.linkedin || links.github || links.instagram || links.personalEmail), [links.github, links.instagram, links.linkedin, links.personalEmail] ); - const [isEditing, setIsEditing] = useState(false); - - useEffect(() => { - if (!editable) { - setIsEditing(false); - } - }, [editable]); - - const handleCancel = () => { - setIsEditing(false); - }; - - const handleSuccess = () => { - setIsEditing(false); - }; - if (!editable) { return (
@@ -52,9 +48,19 @@ export function ExternalLinks({ officerId, links, editable = false }: Props) { ); } + if (isEditing) { + return ( + + ); + } + return ( -
-
+
{hasLinks ? ( <> {links.linkedin && } @@ -67,28 +73,17 @@ export function ExternalLinks({ officerId, links, editable = false }: Props) { No social links yet. Add one below. )} - - {editable && !isEditing && ( + {editable && onEditRequest && ( )}
- - {editable && isEditing && ( - - )} -
); } diff --git a/src/routes/_authed/profile.tsx b/src/routes/_authed/profile.tsx index 8641659..2d7259a 100644 --- a/src/routes/_authed/profile.tsx +++ b/src/routes/_authed/profile.tsx @@ -1,5 +1,4 @@ import { ACMErrorComponent } from "@/components/ErrorComponent"; -import { AcademicInfo } from "@/components/Profile/AcademicInfo"; import { InternshipList } from "@/components/Profile/Internship/InternshipList"; import { ProfileView } from "@/components/Profile/ProfileView"; import { ResearchList } from "@/components/Profile/Research/ResearchList"; @@ -38,7 +37,6 @@ function RouteComponent() {
- diff --git a/tsconfig.json b/tsconfig.json index 7920df9..3fc572e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,6 +20,7 @@ "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, "noUncheckedSideEffectImports": true, + "ignoreDeprecations": "6.0", "baseUrl": ".", "paths": { "@/*": ["./src/*"], From fd1879b89633e2344c5b1b4f2b2a2b693e8a5a02 Mon Sep 17 00:00:00 2001 From: jeydinpham Date: Wed, 8 Apr 2026 18:37:39 -0500 Subject: [PATCH 2/8] style: This is a good start --- src/components/Profile/AcademicInfo.tsx | 18 +- src/components/Profile/ProfileView.tsx | 67 +++++-- src/components/Profile/UpdateAcademics.tsx | 199 +++++++++------------ src/components/Socials/ExternalLinks.tsx | 10 +- 4 files changed, 156 insertions(+), 138 deletions(-) diff --git a/src/components/Profile/AcademicInfo.tsx b/src/components/Profile/AcademicInfo.tsx index 80d7493..ef63360 100644 --- a/src/components/Profile/AcademicInfo.tsx +++ b/src/components/Profile/AcademicInfo.tsx @@ -3,6 +3,7 @@ import { useQuery } from "@tanstack/react-query"; import UpdateAcademics from "./UpdateAcademics"; import { getOfficerByIdQuery, getOfficerQuery } from "@/queries/officer"; import type { Officer } from "@/schemas/officer"; +import { CalendarDays, GraduationCap } from "lucide-react"; type Props = { officerId?: string; @@ -15,7 +16,8 @@ function AcademicContent({ officer }: { officer: Officer }) { return (
-
+
+ Year Standing
@@ -24,16 +26,8 @@ function AcademicContent({ officer }: { officer: Officer }) {
-
- Credit Standing -
-
- {officer.creditStanding} -
-
- -
-
+
+ Expected Graduation
@@ -70,7 +64,7 @@ export function AcademicInfo({ Academic Information -
{content}
+
{content}
); } diff --git a/src/components/Profile/ProfileView.tsx b/src/components/Profile/ProfileView.tsx index 3432c8f..dedac38 100644 --- a/src/components/Profile/ProfileView.tsx +++ b/src/components/Profile/ProfileView.tsx @@ -19,7 +19,7 @@ import { isExecutive } from "@/lib/admin"; import { Button } from "../ui/button"; import { Separator } from "@/components/ui/separator"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; -import { EllipsisVertical, Loader2 } from "lucide-react"; +import { CalendarDays, EllipsisVertical, GraduationCap, Loader2 } from "lucide-react"; type Props = { officerId?: string; @@ -175,31 +175,70 @@ export function ProfileView({ officerId, archived = false, editable = false }: P -
- + {isEditing ? ( +
+ - + +
+ + Socials + + setIsEditing(true)} + isEditing + onCancelEdit={() => setIsEditing(false)} + onFinishEdit={() => setIsEditing(false)} + /> +
+
+ ) : (
- - Socials - +
+
+ +
+
+ Year Standing +
+ {officer.yearStanding} +
+
+
+ +
+
+ Expected Graduation +
+ + {officer.expectedGrad.term} {officer.expectedGrad.year} + +
+
+
+ +
+ setIsEditing(true)} - isEditing={isEditing} onCancelEdit={() => setIsEditing(false)} onFinishEdit={() => setIsEditing(false)} />
-
+ )}
); diff --git a/src/components/Profile/UpdateAcademics.tsx b/src/components/Profile/UpdateAcademics.tsx index 7098563..004e6d2 100644 --- a/src/components/Profile/UpdateAcademics.tsx +++ b/src/components/Profile/UpdateAcademics.tsx @@ -21,9 +21,9 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { toast } from "sonner"; import { useEffect } from "react"; +import { CalendarDays, GraduationCap } from "lucide-react"; const UpdateAcademicsSchema = z.object({ - creditStanding: StandingSchema, yearStanding: StandingSchema, expectedGrad: TermSchema, }); @@ -35,7 +35,6 @@ export default function UpdateAcademics({ officer }: { officer: Officer }) { const startYear = 2020; // matches TermSchema minimum const years = Array.from({ length: currentYear + 6 - startYear + 1 }, (_, i) => startYear + i); const initialValues = { - creditStanding: officer.creditStanding, yearStanding: officer.yearStanding, expectedGrad: officer.expectedGrad, }; @@ -57,7 +56,6 @@ export default function UpdateAcademics({ officer }: { officer: Officer }) { useEffect(() => { reset(initialValues); }, [ - officer.creditStanding, officer.yearStanding, officer.expectedGrad.term, officer.expectedGrad.year, @@ -69,6 +67,7 @@ export default function UpdateAcademics({ officer }: { officer: Officer }) { await updateAcademicInfo({ officerId: officer.id, netId: officer.netId, + creditStanding: officer.creditStanding, ...data, }); reset(data); @@ -80,115 +79,95 @@ export default function UpdateAcademics({ officer }: { officer: Officer }) { return (
- - - - Standing (by year) - - ( - - )} - /> - - - +
+ + + + + + Standing (by year) + + + ( + + )} + /> + + + - - - - Standing (by credit) - - ( - - )} - /> - - - - - - - - Expected Graduation - -
-
- ( - - )} - /> -
-
- ( - - )} - /> + + + + + + Expected Graduation + + +
+
+ ( + + )} + /> +
+
+ ( + + )} + /> +
-
- - - + + + +
diff --git a/src/components/Socials/ExternalLinks.tsx b/src/components/Socials/ExternalLinks.tsx index 0982643..04a216c 100644 --- a/src/components/Socials/ExternalLinks.tsx +++ b/src/components/Socials/ExternalLinks.tsx @@ -13,6 +13,7 @@ type Props = { links: SocialLinks; editable?: boolean; isEditing?: boolean; + compact?: boolean; onEditRequest?: () => void; onCancelEdit?: () => void; onFinishEdit?: () => void; @@ -23,6 +24,7 @@ export function ExternalLinks({ links, editable = false, isEditing = false, + compact = false, onEditRequest, onCancelEdit, onFinishEdit, @@ -60,7 +62,7 @@ export function ExternalLinks({ } return ( -
+
{hasLinks ? ( <> {links.linkedin && } @@ -78,7 +80,11 @@ export function ExternalLinks({ type="button" variant="outline" size="sm" - className="border-white/20 bg-white/10 text-white hover:bg-white/20 hover:text-white" + className={ + compact + ? "mt-1 border-white/20 bg-white/10 text-white hover:bg-white/20 hover:text-white" + : "border-white/20 bg-white/10 text-white hover:bg-white/20 hover:text-white" + } onClick={onEditRequest} > Edit Profile From 56b7a11cbe0a2b20af1d511c277ec5eda828c68b Mon Sep 17 00:00:00 2001 From: jeydinpham Date: Wed, 8 Apr 2026 18:41:55 -0500 Subject: [PATCH 3/8] fix: Error --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 3fc572e..697b784 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,7 +20,7 @@ "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, "noUncheckedSideEffectImports": true, - "ignoreDeprecations": "6.0", + "ignoreDeprecations": "5.0", "baseUrl": ".", "paths": { "@/*": ["./src/*"], From 9752dfb6efdbba0c57baa0ed4ded442f7c70de46 Mon Sep 17 00:00:00 2001 From: bennettfei Date: Tue, 14 Apr 2026 23:52:17 -0500 Subject: [PATCH 4/8] fix: Adjusted spacing and layout in profile page. --- src/components/Profile/AcademicInfo.tsx | 2 +- src/components/Profile/UpdateAcademics.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Profile/AcademicInfo.tsx b/src/components/Profile/AcademicInfo.tsx index ef63360..f7eaa3a 100644 --- a/src/components/Profile/AcademicInfo.tsx +++ b/src/components/Profile/AcademicInfo.tsx @@ -64,7 +64,7 @@ export function AcademicInfo({ Academic Information -
{content}
+ {content}
); } diff --git a/src/components/Profile/UpdateAcademics.tsx b/src/components/Profile/UpdateAcademics.tsx index 004e6d2..b49e07a 100644 --- a/src/components/Profile/UpdateAcademics.tsx +++ b/src/components/Profile/UpdateAcademics.tsx @@ -83,7 +83,7 @@ export default function UpdateAcademics({ officer }: { officer: Officer }) { - + Standing (by year) From 69c9d67573e6231e7aef097e9b7693efcd966ea9 Mon Sep 17 00:00:00 2001 From: jeydinpham Date: Wed, 15 Apr 2026 00:07:23 -0500 Subject: [PATCH 5/8] feat: Make it so only one Save Changes button will update everything --- src/components/Profile/AcademicInfo.tsx | 12 ++++- src/components/Profile/ProfileView.tsx | 7 ++- .../Profile/Socials/EditSocials.tsx | 13 ++++- src/components/Profile/UpdateAcademics.tsx | 47 ++++++++++++++----- src/components/Socials/ExternalLinks.tsx | 3 ++ 5 files changed, 66 insertions(+), 16 deletions(-) diff --git a/src/components/Profile/AcademicInfo.tsx b/src/components/Profile/AcademicInfo.tsx index f7eaa3a..b7d1ee1 100644 --- a/src/components/Profile/AcademicInfo.tsx +++ b/src/components/Profile/AcademicInfo.tsx @@ -1,6 +1,6 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { useQuery } from "@tanstack/react-query"; -import UpdateAcademics from "./UpdateAcademics"; +import UpdateAcademics, { type UpdateAcademicsHandle } from "./UpdateAcademics"; import { getOfficerByIdQuery, getOfficerQuery } from "@/queries/officer"; import type { Officer } from "@/schemas/officer"; import { CalendarDays, GraduationCap } from "lucide-react"; @@ -10,6 +10,8 @@ type Props = { archived?: boolean; editable?: boolean; variant?: "card" | "inline"; + hideSubmitButton?: boolean; + academicFormRef?: React.Ref; }; function AcademicContent({ officer }: { officer: Officer }) { @@ -43,6 +45,8 @@ export function AcademicInfo({ archived = false, editable = false, variant = "card", + hideSubmitButton = false, + academicFormRef, }: Props) { const { data: officer } = useQuery( officerId ? getOfficerByIdQuery(officerId, archived) : getOfficerQuery @@ -53,7 +57,11 @@ export function AcademicInfo({ } const content = editable ? ( - + ) : ( ); diff --git a/src/components/Profile/ProfileView.tsx b/src/components/Profile/ProfileView.tsx index dedac38..294b2cc 100644 --- a/src/components/Profile/ProfileView.tsx +++ b/src/components/Profile/ProfileView.tsx @@ -1,5 +1,5 @@ import { cn } from "@/lib/utils"; -import { useState } from "react"; +import { useRef, useState } from "react"; import { RoleList } from "./RoleList"; import { ExternalLinks } from "../Socials/ExternalLinks"; import { ImageUpdate } from "./ImageUpdate"; @@ -20,6 +20,7 @@ import { Button } from "../ui/button"; import { Separator } from "@/components/ui/separator"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { CalendarDays, EllipsisVertical, GraduationCap, Loader2 } from "lucide-react"; +import type { UpdateAcademicsHandle } from "./UpdateAcademics"; type Props = { officerId?: string; @@ -32,6 +33,7 @@ export function ProfileView({ officerId, archived = false, editable = false }: P officerId ? getOfficerByIdQuery(officerId, archived) : getOfficerQuery ); const [isEditing, setIsEditing] = useState(false); + const academicFormRef = useRef(null); const { data: viewer } = useQuery(getOfficerQuery); const isViewerExecutive = viewer ? isExecutive(viewer) : false; @@ -182,6 +184,8 @@ export function ProfileView({ officerId, archived = false, editable = false }: P archived={archived} editable={editable} variant="inline" + hideSubmitButton + academicFormRef={academicFormRef} /> @@ -198,6 +202,7 @@ export function ProfileView({ officerId, archived = false, editable = false }: P isEditing onCancelEdit={() => setIsEditing(false)} onFinishEdit={() => setIsEditing(false)} + onBeforeSave={() => academicFormRef.current?.submit()} />
diff --git a/src/components/Profile/Socials/EditSocials.tsx b/src/components/Profile/Socials/EditSocials.tsx index b0487c4..fab6d37 100644 --- a/src/components/Profile/Socials/EditSocials.tsx +++ b/src/components/Profile/Socials/EditSocials.tsx @@ -23,6 +23,7 @@ type EditSocialsProps = { links: SocialLinks; onCancel?: () => void; onSuccess?: () => void; + onBeforeSave?: () => Promise | void; }; const emailValidator = z.email(); @@ -74,7 +75,13 @@ const inputClassName = type SocialLinksFormValues = z.infer; -export function EditSocials({ officerId, links, onCancel, onSuccess }: EditSocialsProps) { +export function EditSocials({ + officerId, + links, + onCancel, + onSuccess, + onBeforeSave, +}: EditSocialsProps) { const { register, handleSubmit, @@ -104,6 +111,10 @@ export function EditSocials({ officerId, links, onCancel, onSuccess }: EditSocia }, [links.github, links.instagram, links.linkedin, links.personalEmail, reset]); const onSubmit = async (values: SocialLinksFormValues) => { + if (onBeforeSave) { + await onBeforeSave(); + } + const updated: SocialLinks = { ...links }; const applyField = ( diff --git a/src/components/Profile/UpdateAcademics.tsx b/src/components/Profile/UpdateAcademics.tsx index b49e07a..083c131 100644 --- a/src/components/Profile/UpdateAcademics.tsx +++ b/src/components/Profile/UpdateAcademics.tsx @@ -20,7 +20,7 @@ import { useForm, Controller } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { toast } from "sonner"; -import { useEffect } from "react"; +import { forwardRef, useEffect, useImperativeHandle } from "react"; import { CalendarDays, GraduationCap } from "lucide-react"; const UpdateAcademicsSchema = z.object({ @@ -30,7 +30,19 @@ const UpdateAcademicsSchema = z.object({ type UpdateAcademicsFormData = z.infer; -export default function UpdateAcademics({ officer }: { officer: Officer }) { +export type UpdateAcademicsHandle = { + submit: () => Promise; +}; + +type Props = { + officer: Officer; + showSubmitButton?: boolean; +}; + +const UpdateAcademics = forwardRef(function UpdateAcademics( + { officer, showSubmitButton = true }, + ref +) { const currentYear = new Date().getFullYear(); const startYear = 2020; // matches TermSchema minimum const years = Array.from({ length: currentYear + 6 - startYear + 1 }, (_, i) => startYear + i); @@ -74,8 +86,15 @@ export default function UpdateAcademics({ officer }: { officer: Officer }) { toast.success("Academic info updated successfully"); } catch (error) { toast.error("Failed to update academic info"); + throw error; } }; + + useImperativeHandle(ref, () => ({ + submit: async () => { + await handleSubmit(onSubmit)(); + }, + })); return ( @@ -170,15 +189,19 @@ export default function UpdateAcademics({ officer }: { officer: Officer }) {
-
- -
+ {showSubmitButton && ( +
+ +
+ )}
); -} +}); + +export default UpdateAcademics; diff --git a/src/components/Socials/ExternalLinks.tsx b/src/components/Socials/ExternalLinks.tsx index 04a216c..15aa977 100644 --- a/src/components/Socials/ExternalLinks.tsx +++ b/src/components/Socials/ExternalLinks.tsx @@ -17,6 +17,7 @@ type Props = { onEditRequest?: () => void; onCancelEdit?: () => void; onFinishEdit?: () => void; + onBeforeSave?: () => Promise | void; }; export function ExternalLinks({ @@ -28,6 +29,7 @@ export function ExternalLinks({ onEditRequest, onCancelEdit, onFinishEdit, + onBeforeSave, }: Props) { const hasLinks = useMemo( () => Boolean(links.linkedin || links.github || links.instagram || links.personalEmail), @@ -57,6 +59,7 @@ export function ExternalLinks({ links={links} onCancel={onCancelEdit} onSuccess={onFinishEdit} + onBeforeSave={onBeforeSave} /> ); } From 6d3f8de107a110e299b68b688215a90e6f8fad70 Mon Sep 17 00:00:00 2001 From: jeydinpham Date: Sun, 19 Apr 2026 23:24:28 -0500 Subject: [PATCH 6/8] style: Fix name edit modal text alignment and division tab spacing --- src/components/Profile/ProfileView.tsx | 2 +- src/components/Profile/UpdateName.tsx | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/components/Profile/ProfileView.tsx b/src/components/Profile/ProfileView.tsx index 294b2cc..23a1009 100644 --- a/src/components/Profile/ProfileView.tsx +++ b/src/components/Profile/ProfileView.tsx @@ -170,7 +170,7 @@ export function ProfileView({ officerId, archived = false, editable = false }: P )} -
+
diff --git a/src/components/Profile/UpdateName.tsx b/src/components/Profile/UpdateName.tsx index d2a2931..12f91bd 100644 --- a/src/components/Profile/UpdateName.tsx +++ b/src/components/Profile/UpdateName.tsx @@ -69,11 +69,11 @@ export function UpdateName({ officerId, firstName, lastName, editable = false }: return (
-
From a377649af79669bbf5626aa9eb093e946477669b Mon Sep 17 00:00:00 2001 From: jeydinpham Date: Sun, 19 Apr 2026 23:42:36 -0500 Subject: [PATCH 8/8] style: Remove AcademicInfo component from DirectoryProfileTabs --- src/components/Directory/DirectoryProfileTabs.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/Directory/DirectoryProfileTabs.tsx b/src/components/Directory/DirectoryProfileTabs.tsx index 73629a6..936807d 100644 --- a/src/components/Directory/DirectoryProfileTabs.tsx +++ b/src/components/Directory/DirectoryProfileTabs.tsx @@ -1,4 +1,3 @@ -import { AcademicInfo } from "../Profile/AcademicInfo"; import { InternshipList } from "../Profile/Internship/InternshipList"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs"; import { ResearchList } from "../Profile/Research/ResearchList"; @@ -48,7 +47,6 @@ export function DirectoryProfileTabs({ officerId, archived = false, editable = f - {editable && }