From 335fdb38f383261acaadaa8bbff91b49970dbab0 Mon Sep 17 00:00:00 2001 From: ayush00git Date: Sat, 15 Aug 2026 01:44:47 +0530 Subject: [PATCH 1/9] feat(profile): add edit profile apis for all users --- app/src/pages/profile/Profile.tsx | 107 +++++++++++++++++++++++++++++- handlers/centrehead_auth.go | 33 +++++++++ routes/auth.go | 1 + 3 files changed, 139 insertions(+), 2 deletions(-) diff --git a/app/src/pages/profile/Profile.tsx b/app/src/pages/profile/Profile.tsx index e742043..466200a 100644 --- a/app/src/pages/profile/Profile.tsx +++ b/app/src/pages/profile/Profile.tsx @@ -2,12 +2,13 @@ import { useCallback, useEffect, useState } from 'react'; import { useNavigate, Link } from 'react-router-dom'; import { ShieldCheck, LogOut, PlusCircle, AlertCircle, Pencil, - Inbox, ServerCrash, Info, + Inbox, ServerCrash, Info, X, } from 'lucide-react'; import { MainLayout } from '../../components/layout/MainLayout'; import { ComplaintCard } from '../../components/ComplaintCard'; import type { ComplaintPost, EditForm, Role } from '../../components/ComplaintCard'; import { Loader } from '../../components/Loader'; +import { BUILDINGS } from '../../constants/models'; interface ProfileData { name?: string; @@ -38,6 +39,11 @@ export function Profile() { }); const [actionLoading, setActionLoading] = useState(null); + const [isEditingProfile, setIsEditingProfile] = useState(false); + const [profileForm, setProfileForm] = useState({ building: '', phone_number: '' }); + const [profileSaving, setProfileSaving] = useState(false); + const [profileError, setProfileError] = useState(null); + useEffect(() => { fetch('/api/profile', { credentials: 'include' }) .then((res) => { @@ -183,6 +189,38 @@ export function Profile() { } } + function openProfileEdit() { + setProfileForm({ + building: profile.building ?? '', + phone_number: profile.phone_number ?? '', + }); + setProfileError(null); + setIsEditingProfile(true); + } + + async function handleSaveProfile() { + setProfileSaving(true); + setProfileError(null); + try { + const res = await fetch('/api/centrehead/profile/edit', { + method: 'PATCH', + credentials: 'include', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(profileForm), + }); + if (!res.ok) { + const b = await res.json().catch(() => ({})); + throw new Error(b.error ?? `Failed to update profile (${res.status})`); + } + setProfile((prev) => prev ? { ...prev, ...profileForm } : null); + setIsEditingProfile(false); + } catch (err) { + setProfileError((err as Error).message); + } finally { + setProfileSaving(false); + } + } + return (
@@ -203,7 +241,7 @@ export function Profile() { Register Complaint
+ + {isCentreHead && isEditingProfile && ( +
+
+
+

Edit Profile

+ +
+ +
+
+ + setProfileForm((f) => ({ ...f, phone_number: e.target.value }))} + className="w-full px-3.5 py-2.5 border border-[#CCCCCC] rounded-lg focus:outline-none focus:border-[#111111] text-sm text-[#111111] bg-white transition-colors" + placeholder="10-digit phone number" + /> +
+ +
+ + +
+ + {profileError && ( +

{profileError}

+ )} +
+ +
+ + +
+
+
+ )}
); } diff --git a/handlers/centrehead_auth.go b/handlers/centrehead_auth.go index b4b7f7e..0cc51c9 100644 --- a/handlers/centrehead_auth.go +++ b/handlers/centrehead_auth.go @@ -5,6 +5,7 @@ import ( "time" "github.com/ayush00git/cms-web/helpers" + "github.com/ayush00git/cms-web/middleware" "github.com/ayush00git/cms-web/models" "github.com/ayush00git/cms-web/services" @@ -13,6 +14,11 @@ import ( "gorm.io/gorm" ) +type CentreheadProfileEditType struct { + Building string `json:"building"` + PhoneNumber string `json:"phone_number"` +} + // CentreheadSignup registers the head of adminstrations. // On success, sends a verification email with a JWT token link. @@ -198,3 +204,30 @@ func (h *AuthHandler) CentreheadResetPassword(c *gin.Context) { } c.JSON(200, gin.H{"success": "password changed successfully", "role": "centrehead"}) } + +//CentreheadProfileEdit edits the profile of user type centrehead +func (h *AuthHandler) CentreheadProfileEdit(c *gin.Context) { + email, ok := c.Get(middleware.EmailKey) + if !ok { + c.JSON(401, gin.H{"error": "unauthenticated access!"}) + return + } + + var head models.Centrehead + if err := h.DB.Where("email = ?", email).Take(&head).Error; err != nil { + c.JSON(500, gin.H{"error": "internal server error"}) + return + } + + var updatedProfile CentreheadProfileEditType + if err := c.ShouldBindJSON(&updatedProfile); err != nil { + c.JSON(400, gin.H{"error": "invalid request body"}) + return + } + + if err := h.DB.Model(&head).Updates(updatedProfile).Error; err != nil { + c.JSON(500, gin.H{"error": "failed to update the profile"}) + return + } + c.JSON(200, gin.H{"success": "profile updated successfully!"}) +} diff --git a/routes/auth.go b/routes/auth.go index de9f361..8dbe957 100644 --- a/routes/auth.go +++ b/routes/auth.go @@ -35,4 +35,5 @@ func AuthRoute (e *gin.Engine, h *handlers.AuthHandler) { // for returning the user's profile e.GET("/api/profile", middleware.IsAuthenticated(), h.UserProfile) + e.PATCH("/api/centrehead/profile/edit", middleware.IsAuthenticated(), h.CentreheadProfileEdit) } From a1adcf6b0c8f0368315a87d5bfccb357e815cb89 Mon Sep 17 00:00:00 2001 From: ayush00git Date: Sat, 15 Aug 2026 13:02:11 +0530 Subject: [PATCH 2/9] fix: match result Error syntax --- handlers/centrehead_auth.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/handlers/centrehead_auth.go b/handlers/centrehead_auth.go index 0cc51c9..6274018 100644 --- a/handlers/centrehead_auth.go +++ b/handlers/centrehead_auth.go @@ -205,7 +205,7 @@ func (h *AuthHandler) CentreheadResetPassword(c *gin.Context) { c.JSON(200, gin.H{"success": "password changed successfully", "role": "centrehead"}) } -//CentreheadProfileEdit edits the profile of user type centrehead +//CentreheadProfileEdit edits the profile of user type centrehead. func (h *AuthHandler) CentreheadProfileEdit(c *gin.Context) { email, ok := c.Get(middleware.EmailKey) if !ok { @@ -214,8 +214,12 @@ func (h *AuthHandler) CentreheadProfileEdit(c *gin.Context) { } var head models.Centrehead - if err := h.DB.Where("email = ?", email).Take(&head).Error; err != nil { - c.JSON(500, gin.H{"error": "internal server error"}) + result := h.DB.Where("email = ?", email).Take(&head) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + c.JSON(404, gin.H{"error": "user not found"}) + } + c.JSON(500, gin.H{"error": "failed to fetch profile"}) return } @@ -225,7 +229,8 @@ func (h *AuthHandler) CentreheadProfileEdit(c *gin.Context) { return } - if err := h.DB.Model(&head).Updates(updatedProfile).Error; err != nil { + result = h.DB.Model(&head).Updates(updatedProfile) + if result.Error != nil { c.JSON(500, gin.H{"error": "failed to update the profile"}) return } From f04729d1fef3f2aaca44a6a247db745db211fb91 Mon Sep 17 00:00:00 2001 From: ayush00git Date: Sat, 15 Aug 2026 13:02:38 +0530 Subject: [PATCH 3/9] feat: defined a new function - FacultyProfileEdit --- handlers/faculty_auth.go | 47 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/handlers/faculty_auth.go b/handlers/faculty_auth.go index 282d05c..04675d8 100644 --- a/handlers/faculty_auth.go +++ b/handlers/faculty_auth.go @@ -5,6 +5,7 @@ import ( "time" "github.com/ayush00git/cms-web/helpers" + "github.com/ayush00git/cms-web/middleware" "github.com/ayush00git/cms-web/models" "github.com/ayush00git/cms-web/services" @@ -25,10 +26,18 @@ type ForgetPassword struct { Email string `json:"email" binding:"required"` } +type FacultyProfileEditType struct { + Name string `json:"name"` + Department string `json:"department"` + HouseNumber string `json:"house_number"` + Block string `json:"block"` + Type string `json:"type"` + PhoneNumber string `json:"phone_number"` +} // FacultySignup registers a new faculty member. // On success, sends a verification email with a JWT token link. -func (h *AuthHandler) FacultySignup (c *gin.Context) { +func (h *AuthHandler) FacultySignup(c *gin.Context) { var inputs models.FacultySignup // bind the request body in a json format @@ -94,7 +103,7 @@ func (h *AuthHandler) FacultySignup (c *gin.Context) { // FacultyLogin authenticates a faculty member using email and password. // On success, signs a JWT and stores it in an httpOnly cookie. -func (h *AuthHandler) FacultyLogin (c *gin.Context) { +func (h *AuthHandler) FacultyLogin(c *gin.Context) { var inputs models.FacultyLogin if err := c.ShouldBindJSON(&inputs); err != nil { @@ -220,3 +229,37 @@ func (h *AuthHandler) FacultyResetPassword(c *gin.Context) { } c.JSON(200, gin.H{"success": "password changed successfully", "role": "faculty"}) } + +// FacultyProfileEdit edits the profile of user type faculty. +func (h *AuthHandler) FacultyProfileEdit(c *gin.Context) { + email, ok := c.Get(middleware.EmailKey) + if !ok { + c.JSON(401, gin.H{"error": "unauthenticated access!"}) + return + } + + var profile models.Faculty + result := h.DB.Where("email = ?", email).Take(&profile) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + c.JSON(404, gin.H{"error": "profile not found"}) + return + } + c.JSON(500, gin.H{"error": "failed to fetch profile"}) + return + } + + var updatedProfile FacultyProfileEditType + if err := c.ShouldBindJSON(&updatedProfile); err != nil { + c.JSON(400, gin.H{"error": "invalid request body"}) + return + } + + result = h.DB.Model(profile).Updates(updatedProfile) + if result.Error != nil { + c.JSON(500, gin.H{"error": "failed to update profile"}) + return + } + + c.JSON(200, gin.H{"success": "profile updated successfully!"}) +} From c56879c63004d0a4940e99ff89cb1e58da58692b Mon Sep 17 00:00:00 2001 From: ayush00git Date: Sat, 15 Aug 2026 13:02:58 +0530 Subject: [PATCH 4/9] feat: register a new api route --- routes/auth.go | 1 + 1 file changed, 1 insertion(+) diff --git a/routes/auth.go b/routes/auth.go index 8dbe957..a9c3827 100644 --- a/routes/auth.go +++ b/routes/auth.go @@ -35,5 +35,6 @@ func AuthRoute (e *gin.Engine, h *handlers.AuthHandler) { // for returning the user's profile e.GET("/api/profile", middleware.IsAuthenticated(), h.UserProfile) + e.PATCH("/api/faculty/profile/edit", middleware.IsAuthenticated(), h.FacultyProfileEdit) e.PATCH("/api/centrehead/profile/edit", middleware.IsAuthenticated(), h.CentreheadProfileEdit) } From ea533e30332c5f0f1d0552eb93dbfd9592640a66 Mon Sep 17 00:00:00 2001 From: ayush00git Date: Sat, 15 Aug 2026 14:37:06 +0530 Subject: [PATCH 5/9] feat: updated a new api- WardenProfileEdit --- handlers/centrehead_auth.go | 2 +- handlers/faculty_auth.go | 2 +- handlers/warden_auth.go | 43 ++++++++++++++++++++++++++++++++++++- 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/handlers/centrehead_auth.go b/handlers/centrehead_auth.go index 6274018..4932dbd 100644 --- a/handlers/centrehead_auth.go +++ b/handlers/centrehead_auth.go @@ -209,7 +209,7 @@ func (h *AuthHandler) CentreheadResetPassword(c *gin.Context) { func (h *AuthHandler) CentreheadProfileEdit(c *gin.Context) { email, ok := c.Get(middleware.EmailKey) if !ok { - c.JSON(401, gin.H{"error": "unauthenticated access!"}) + c.JSON(403, gin.H{"error": "unauthorized access!"}) return } diff --git a/handlers/faculty_auth.go b/handlers/faculty_auth.go index 04675d8..121e5f2 100644 --- a/handlers/faculty_auth.go +++ b/handlers/faculty_auth.go @@ -234,7 +234,7 @@ func (h *AuthHandler) FacultyResetPassword(c *gin.Context) { func (h *AuthHandler) FacultyProfileEdit(c *gin.Context) { email, ok := c.Get(middleware.EmailKey) if !ok { - c.JSON(401, gin.H{"error": "unauthenticated access!"}) + c.JSON(403, gin.H{"error": "unauthorized access!"}) return } diff --git a/handlers/warden_auth.go b/handlers/warden_auth.go index 9875c7e..5401b01 100644 --- a/handlers/warden_auth.go +++ b/handlers/warden_auth.go @@ -5,6 +5,7 @@ import ( "time" "github.com/ayush00git/cms-web/helpers" + "github.com/ayush00git/cms-web/middleware" "github.com/ayush00git/cms-web/models" "github.com/ayush00git/cms-web/services" @@ -13,6 +14,13 @@ import ( "gorm.io/gorm" ) +type WardenProfileEditType struct { + Name string `json:"name"` + Hostel string `json:"hostel"` + PhoneNumber string `json:"phone_number"` +} + + // WardenSignup registers a warden. // On success, sends a verification email with a JWT token link. func (h *AuthHandler) WardenSignup (c *gin.Context) { @@ -124,7 +132,6 @@ func (h *AuthHandler) WardenLogin (c *gin.Context) { } - // WardenForgetPassword sends an password reset email to the user func (h* AuthHandler) WardenForgetPassword(c *gin.Context) { var input ForgetPassword @@ -199,3 +206,37 @@ func (h *AuthHandler) WardenResetPassword(c *gin.Context) { } c.JSON(200, gin.H{"success": "password changed successfully", "role": "warden"}) } + + +// WardenProfileEdit dits the profile of user type warden. +func (h *AuthHandler) WardenProfileEdit(c *gin.Context) { + email, ok := c.Get(middleware.EmailKey) + if !ok { + c.JSON(403, gin.H{"error": "unauthorized access!"}) + return + } + + var profile models.Warden + result := h.DB.Where("email = ?", email).Take(&profile) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + c.JSON(404, gin.H{"error": "user not found"}) + return + } + c.JSON(500, gin.H{"error": "failed to fetch profile"}) + return + } + + var updatedProfile WardenProfileEditType + if err := c.ShouldBindJSON(&updatedProfile); err != nil { + c.JSON(400, gin.H{"error": "invalid request body"}) + return + } + + result = h.DB.Model(profile).Updates(updatedProfile) + if result.Error != nil { + c.JSON(500, gin.H{"error": "failed updating profile"}) + return + } + c.JSON(200, gin.H{"success": "profile updated successfully!"}) +} From db2d14fc8dc185ab481832d5b965ff922a74eb00 Mon Sep 17 00:00:00 2001 From: ayush00git Date: Sat, 15 Aug 2026 14:37:32 +0530 Subject: [PATCH 6/9] feat: registered WardenProfileEdit api to routes --- routes/auth.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/routes/auth.go b/routes/auth.go index a9c3827..a5712ac 100644 --- a/routes/auth.go +++ b/routes/auth.go @@ -30,11 +30,14 @@ func AuthRoute (e *gin.Engine, h *handlers.AuthHandler) { } e.POST("/api/auth/logout", h.Logout) - // for account verifications + // for account verifications. e.GET("/api/auth/verify", h.VerifyAccount) - // for returning the user's profile + // for returning the user's profile. e.GET("/api/profile", middleware.IsAuthenticated(), h.UserProfile) + + // for editing user's profile. e.PATCH("/api/faculty/profile/edit", middleware.IsAuthenticated(), h.FacultyProfileEdit) + e.PATCH("/api/warden/profile/edit", middleware.IsAuthenticated(), h.WardenProfileEdit) e.PATCH("/api/centrehead/profile/edit", middleware.IsAuthenticated(), h.CentreheadProfileEdit) } From 8474f687fe60518753d87dad2fcfcd6d1a853ea8 Mon Sep 17 00:00:00 2001 From: ayush00git Date: Sat, 15 Aug 2026 14:47:33 +0530 Subject: [PATCH 7/9] feat: bind up apis to frontend --- app/src/pages/profile/Profile.tsx | 166 +++++++++++++++++++++++++----- 1 file changed, 143 insertions(+), 23 deletions(-) diff --git a/app/src/pages/profile/Profile.tsx b/app/src/pages/profile/Profile.tsx index 466200a..993a1f6 100644 --- a/app/src/pages/profile/Profile.tsx +++ b/app/src/pages/profile/Profile.tsx @@ -8,7 +8,7 @@ import { MainLayout } from '../../components/layout/MainLayout'; import { ComplaintCard } from '../../components/ComplaintCard'; import type { ComplaintPost, EditForm, Role } from '../../components/ComplaintCard'; import { Loader } from '../../components/Loader'; -import { BUILDINGS } from '../../constants/models'; +import { BUILDINGS, HOSTELS, DEPARTMENTS, BLOCK_LABELS, BLOCK_TYPES } from '../../constants/models'; interface ProfileData { name?: string; @@ -40,7 +40,10 @@ export function Profile() { const [actionLoading, setActionLoading] = useState(null); const [isEditingProfile, setIsEditingProfile] = useState(false); - const [profileForm, setProfileForm] = useState({ building: '', phone_number: '' }); + const [profileForm, setProfileForm] = useState({ + name: '', phone_number: '', building: '', hostel: '', + department: '', house_number: '', block: '', type: '', + }); const [profileSaving, setProfileSaving] = useState(false); const [profileError, setProfileError] = useState(null); @@ -189,10 +192,22 @@ export function Profile() { } } + const profileEditEndpoint = isFaculty + ? '/api/faculty/profile/edit' + : isWarden + ? '/api/warden/profile/edit' + : '/api/centrehead/profile/edit'; + function openProfileEdit() { setProfileForm({ - building: profile.building ?? '', + name: profile.name ?? '', phone_number: profile.phone_number ?? '', + building: profile.building ?? '', + hostel: profile.hostel ?? '', + department: profile.department ?? '', + house_number: profile.house_number ?? '', + block: profile.block ?? '', + type: profile.type ?? '', }); setProfileError(null); setIsEditingProfile(true); @@ -201,18 +216,29 @@ export function Profile() { async function handleSaveProfile() { setProfileSaving(true); setProfileError(null); + + const body = isFaculty + ? { + name: profileForm.name, phone_number: profileForm.phone_number, + department: profileForm.department, house_number: profileForm.house_number, + block: profileForm.block, type: profileForm.type, + } + : isWarden + ? { name: profileForm.name, phone_number: profileForm.phone_number, hostel: profileForm.hostel } + : { phone_number: profileForm.phone_number, building: profileForm.building }; + try { - const res = await fetch('/api/centrehead/profile/edit', { + const res = await fetch(profileEditEndpoint, { method: 'PATCH', credentials: 'include', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(profileForm), + body: JSON.stringify(body), }); if (!res.ok) { const b = await res.json().catch(() => ({})); throw new Error(b.error ?? `Failed to update profile (${res.status})`); } - setProfile((prev) => prev ? { ...prev, ...profileForm } : null); + setProfile((prev) => prev ? { ...prev, ...body } : null); setIsEditingProfile(false); } catch (err) { setProfileError((err as Error).message); @@ -241,7 +267,7 @@ export function Profile() { Register Complaint