Skip to content

Commit f07d2a1

Browse files
chore: Clean-up contexts, hooks, & stores (#114)
* chore: rm unneeded helper functions * chore: auth context for profile * chore: streamline survey store * chore: pare down JWT payload * fix: rm obselete userObjectId from survey store * refactor: mc --------- Co-authored-by: Anant Mittal <anmittal@uw.edu>
1 parent 4680cf1 commit f07d2a1

File tree

10 files changed

+116
-124
lines changed

10 files changed

+116
-124
lines changed

README.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ The RDS App is a secure, accessible, and open-source web application that stream
1919
| Hosting | Azure Web Service |
2020
| QR Scanning | Html5QrcodeScanner, QRCodeCanvas |
2121

22-
## Directory
22+
## Directory (old)
2323

2424
```plaintext
2525
client/ # Client-facing React application
@@ -196,24 +196,24 @@ The items listed below are features our team has identified out of scope for the
196196

197197
**App Features**
198198

199-
- Auto-populate location using GPS location coordinates
200-
- Widget for staff to comment on survey responses
201-
- Integration with Homeless Management Information System (HMIS) database system
202-
- Volunteer scheduling dashboard for administrators
203-
- Automated SMS gift card distribution
204-
- Resume unfinished survey feature
205-
- Admin ability to edit survey questions
206-
- Volunteer ability to edit survey responses
207-
- Survey analytics dashboard
199+
- Auto-populate location using GPS location coordinates
200+
- Widget for staff to comment on survey responses
201+
- Integration with Homeless Management Information System (HMIS) database system
202+
- Volunteer scheduling dashboard for administrators
203+
- Automated SMS gift card distribution
204+
- Resume unfinished survey feature
205+
- Admin ability to edit survey questions
206+
- Volunteer ability to edit survey responses
207+
- Survey analytics dashboard
208208

209209
**Testing**
210210

211-
- Dynamic Application Security Testing (DAST)
211+
- Dynamic Application Security Testing (DAST)
212212

213213
**User Experience**
214214
-Step-by-step user training guide
215215

216-
- Setup wizard
216+
- Setup wizard
217217

218218
## Contributors
219219

client/src/contexts/AuthContext.tsx

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,12 @@ import {
1616
interface AuthState {
1717
token: string;
1818
firstName: string;
19+
lastName: string;
1920
userObjectId: string;
2021
userRole: string;
22+
email: string;
23+
phone: string;
24+
locationObjectId: string;
2125
lastestLocationObjectId: string;
2226
permissions: {
2327
action: Action;
@@ -32,7 +36,11 @@ interface AuthContextValue extends AuthState {
3236
setToken: (token: string) => void;
3337
setUserObjectId: (userObjectId: string) => void;
3438
setFirstName: (firstName: string) => void;
39+
setLastName: (lastName: string) => void;
3540
setUserRole: (userRole: string) => void;
41+
setEmail: (email: string) => void;
42+
setPhone: (phone: string) => void;
43+
setLocationObjectId: (locationObjectId: string) => void;
3644
setLastestLocationObjectId: (lastestLocationObjectId: string) => void;
3745
setPermissions: (
3846
permissions: {
@@ -51,8 +59,12 @@ function getDefaultAuthState(): AuthState {
5159
return {
5260
token: '',
5361
firstName: '',
62+
lastName: '',
5463
userObjectId: '',
5564
userRole: '',
65+
email: '',
66+
phone: '',
67+
locationObjectId: '',
5668
lastestLocationObjectId: '',
5769
permissions: [],
5870
isLoggedIn: false,
@@ -70,10 +82,14 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
7082

7183
return {
7284
token,
73-
firstName: decoded.firstName,
74-
userRole: decoded.role,
85+
firstName: '',
86+
lastName: '',
87+
email: '',
88+
phone: '',
89+
userRole: '',
7590
userObjectId: decoded.userObjectId,
7691
permissions: [],
92+
locationObjectId: '',
7793
lastestLocationObjectId: '',
7894
isLoggedIn: true,
7995
// TODO: Verification needed for consistency.
@@ -129,7 +145,11 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
129145
...prev,
130146
userObjectId: user.data._id,
131147
firstName: user.data.firstName,
148+
lastName: user.data.lastName,
132149
userRole: user.data.role,
150+
email: user.data.email,
151+
phone: user.data.phone,
152+
locationObjectId: user.data.locationObjectId,
133153
lastestLocationObjectId: latestLocationObjectId,
134154
permissions: user.data.permissions,
135155
isLoggedIn: true,
@@ -153,10 +173,26 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
153173
setState(prev => ({ ...prev, firstName }));
154174
};
155175

176+
const setLastName = (lastName: string) => {
177+
setState(prev => ({ ...prev, lastName }));
178+
};
179+
156180
const setUserRole = (userRole: string) => {
157181
setState(prev => ({ ...prev, userRole }));
158182
};
159183

184+
const setEmail = (email: string) => {
185+
setState(prev => ({ ...prev, email }));
186+
};
187+
188+
const setPhone = (phone: string) => {
189+
setState(prev => ({ ...prev, phone }));
190+
};
191+
192+
const setLocationObjectId = (locationObjectId: string) => {
193+
setState(prev => ({ ...prev, locationObjectId }));
194+
};
195+
160196
const setLastestLocationObjectId = (lastestLocationObjectId: string) => {
161197
setState(prev => ({ ...prev, lastestLocationObjectId }));
162198
};
@@ -176,7 +212,11 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
176212
setToken,
177213
setUserObjectId,
178214
setFirstName,
215+
setLastName,
179216
setUserRole,
217+
setEmail,
218+
setPhone,
219+
setLocationObjectId,
180220
setLastestLocationObjectId,
181221
setPermissions,
182222
clearSession,

client/src/hooks/useAuth.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { useAuthContext } from '@/contexts';
33
import { useSurveyStore } from '@/stores/useSurveyStore';
44
import {
55
deleteAuthToken,
6-
getObjectId,
6+
getDecodedAuthToken,
77
saveAuthToken
88
} from '@/utils/authTokenHandler';
99

@@ -18,8 +18,10 @@ export const useAuth = () => {
1818

1919
const handleLogin = async (token: string) => {
2020
saveAuthToken(token);
21-
const userObjectId = getObjectId();
22-
await fetchUserContext(userObjectId);
21+
const userObjectId = getDecodedAuthToken()?.userObjectId;
22+
if (userObjectId) {
23+
await fetchUserContext(userObjectId);
24+
}
2325
};
2426

2527
const handleLogout = () => {

client/src/pages/Login/Login.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useEffect, useState } from 'react';
22

3-
import { initializeSurveyStore } from '@/utils/authTokenHandler';
3+
import { useSurveyStore } from '@/stores';
44
import { Box, Button, Paper, TextField, Typography } from '@mui/material';
55
import { useNavigate } from 'react-router-dom';
66

@@ -9,6 +9,7 @@ import { useAuth } from '@/hooks/useAuth';
99
export default function Login() {
1010
const { handleLogin } = useAuth();
1111
const navigate = useNavigate();
12+
const { clearSession } = useSurveyStore();
1213
// const [email, setEmail] = useState('');
1314
const [phone, setPhone] = useState('');
1415
const [otp, setOtp] = useState('');
@@ -83,7 +84,7 @@ export default function Login() {
8384
if (response.ok) {
8485
// Successful login and store user data
8586
await handleLogin(data.token);
86-
initializeSurveyStore();
87+
clearSession(); // Clear any previous survey data from storage
8788
navigate(data.redirectTo);
8889
} else {
8990
setErrorMessage(data.message);

client/src/pages/Profile/Profile.tsx

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useState } from 'react';
22

3+
import { useAuthContext } from '@/contexts';
34
import { useAbility, useApi } from '@/hooks';
45
import { ACTIONS, SUBJECTS } from '@/permissions/constants';
56
import { subject } from '@casl/ability';
@@ -13,36 +14,43 @@ import {
1314
RoleSelect
1415
} from '@/components/forms';
1516

16-
export default function AdminEditProfile() {
17+
export default function Profile() {
1718
const ability = useAbility();
1819
const { userService } = useApi();
1920
const { id } = useParams();
2021
const [message, setMessage] = useState('');
2122
const [error, _setError] = useState('');
22-
23-
// REVIEW: Let's use auth context, such that you first refresh the auth context for this user, and then fetch the user data from the context.
24-
const { data: user, mutate } = userService.useUser(id) ?? {};
23+
const {
24+
locationObjectId,
25+
firstName,
26+
lastName,
27+
userObjectId,
28+
userRole,
29+
email,
30+
phone,
31+
setEmail,
32+
setPhone,
33+
setLocationObjectId,
34+
setUserRole
35+
} = useAuthContext();
2536

2637
const canEditField = (field: string) => {
27-
if (!user) return false; // wait until user is fetched
2838
return ability.can(
2939
ACTIONS.CASL.UPDATE,
30-
subject(SUBJECTS.USER, user),
40+
subject(SUBJECTS.USER, { _id: userObjectId }),
3141
field
3242
);
3343
};
3444

3545
const handleSave = async () => {
36-
if (!user) return;
37-
3846
try {
3947
const updatePayload: Record<string, any> = {};
4048

41-
if (canEditField('role')) updatePayload.role = user.role;
42-
if (canEditField('email')) updatePayload.email = user.email;
43-
if (canEditField('phone')) updatePayload.phone = user.phone;
49+
if (canEditField('role')) updatePayload.role = userRole;
50+
if (canEditField('email')) updatePayload.email = email;
51+
if (canEditField('phone')) updatePayload.phone = phone;
4452
if (canEditField('locationObjectId'))
45-
updatePayload.locationObjectId = user.locationObjectId;
53+
updatePayload.locationObjectId = locationObjectId;
4654

4755
const updatedUser = await userService.updateUser(
4856
id!,
@@ -53,7 +61,6 @@ export default function AdminEditProfile() {
5361
setMessage(
5462
updatedUser.message || 'Profile updated successfully!'
5563
);
56-
mutate?.(); // refetch the user
5764
} else {
5865
setMessage(updatedUser.message || 'Failed to update profile.');
5966
}
@@ -70,7 +77,20 @@ export default function AdminEditProfile() {
7077
}
7178
}
7279

73-
mutate?.({ ...user, [field]: value }, false);
80+
switch (field) {
81+
case 'email':
82+
setEmail(value);
83+
break;
84+
case 'phone':
85+
setPhone(value);
86+
break;
87+
case 'locationObjectId':
88+
setLocationObjectId(value);
89+
break;
90+
case 'role':
91+
setUserRole(value);
92+
break;
93+
}
7494
};
7595

7696
return (
@@ -86,8 +106,7 @@ export default function AdminEditProfile() {
86106
}}
87107
>
88108
<Typography variant="h5" component="h2" gutterBottom sx={{ mb: 3 }}>
89-
Profile for{' '}
90-
{`${user?.firstName || 'User'} ${user?.lastName || ''}`}
109+
Profile for {`${firstName || 'User'} ${lastName || ''}`}
91110
</Typography>
92111

93112
{error ? (
@@ -98,7 +117,7 @@ export default function AdminEditProfile() {
98117
<FormInput
99118
label="Email"
100119
type="email"
101-
value={user?.email || ''}
120+
value={email || ''}
102121
onChange={e =>
103122
handleChange('email', e.target.value)
104123
}
@@ -108,7 +127,7 @@ export default function AdminEditProfile() {
108127

109128
<PhoneInput
110129
label="Phone Number"
111-
value={user?.phone || ''}
130+
value={phone || ''}
112131
onChange={e =>
113132
handleChange('phone', e.target.value)
114133
}
@@ -117,7 +136,7 @@ export default function AdminEditProfile() {
117136
/>
118137

119138
<RoleSelect
120-
value={user?.role || ''}
139+
value={userRole || ''}
121140
onChange={e =>
122141
handleChange('role', e.target.value as string)
123142
}
@@ -126,7 +145,7 @@ export default function AdminEditProfile() {
126145
/>
127146

128147
<LocationSelect
129-
value={user?.locationObjectId || ''}
148+
value={locationObjectId || ''}
130149
onChange={e =>
131150
handleChange(
132151
'locationObjectId',

0 commit comments

Comments
 (0)