Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ The RDS App is a secure, accessible, and open-source web application that stream
| Hosting | Azure Web Service |
| QR Scanning | Html5QrcodeScanner, QRCodeCanvas |

## Directory
## Directory (old)

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

**App Features**

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

**Testing**

- Dynamic Application Security Testing (DAST)
- Dynamic Application Security Testing (DAST)

**User Experience**
-Step-by-step user training guide

- Setup wizard
- Setup wizard

## Contributors

Expand Down
44 changes: 42 additions & 2 deletions client/src/contexts/AuthContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@
interface AuthState {
token: string;
firstName: string;
lastName: string;
userObjectId: string;
userRole: string;
email: string;
phone: string;
locationObjectId: string;
lastestLocationObjectId: string;
permissions: {
action: Action;
Expand All @@ -32,7 +36,11 @@
setToken: (token: string) => void;
setUserObjectId: (userObjectId: string) => void;
setFirstName: (firstName: string) => void;
setLastName: (lastName: string) => void;
setUserRole: (userRole: string) => void;
setEmail: (email: string) => void;
setPhone: (phone: string) => void;
setLocationObjectId: (locationObjectId: string) => void;
setLastestLocationObjectId: (lastestLocationObjectId: string) => void;
setPermissions: (
permissions: {
Expand All @@ -51,8 +59,12 @@
return {
token: '',
firstName: '',
lastName: '',
userObjectId: '',
userRole: '',
email: '',
phone: '',
locationObjectId: '',
lastestLocationObjectId: '',
permissions: [],
isLoggedIn: false,
Expand All @@ -70,10 +82,14 @@

return {
token,
firstName: decoded.firstName,
userRole: decoded.role,
firstName: '',
lastName: '',
email: '',
phone: '',
userRole: '',
userObjectId: decoded.userObjectId,
permissions: [],
locationObjectId: '',
lastestLocationObjectId: '',
isLoggedIn: true,
// TODO: Verification needed for consistency.
Expand All @@ -98,7 +114,7 @@
} else {
setState(prev => ({ ...prev, isLoading: false }));
}
}, []);

Check warning on line 117 in client/src/contexts/AuthContext.tsx

View workflow job for this annotation

GitHub Actions / lint-and-type-check (client)

React Hook useEffect has missing dependencies: 'state.token' and 'state.userObjectId'. Either include them or remove the dependency array

const fetchUserContext = async (userObjectId: string) => {
try {
Expand Down Expand Up @@ -129,14 +145,18 @@
...prev,
userObjectId: user.data._id,
firstName: user.data.firstName,
lastName: user.data.lastName,
userRole: user.data.role,
email: user.data.email,
phone: user.data.phone,
locationObjectId: user.data.locationObjectId,
lastestLocationObjectId: latestLocationObjectId,
permissions: user.data.permissions,
isLoggedIn: true,
isLoading: false
}));
} catch (error) {
console.error('Error fetching user context:', error);

Check warning on line 159 in client/src/contexts/AuthContext.tsx

View workflow job for this annotation

GitHub Actions / lint-and-type-check (client)

Unexpected console statement
setState(prev => ({ ...prev, isLoading: false }));
}
};
Expand All @@ -153,10 +173,26 @@
setState(prev => ({ ...prev, firstName }));
};

const setLastName = (lastName: string) => {
setState(prev => ({ ...prev, lastName }));
};

const setUserRole = (userRole: string) => {
setState(prev => ({ ...prev, userRole }));
};

const setEmail = (email: string) => {
setState(prev => ({ ...prev, email }));
};

const setPhone = (phone: string) => {
setState(prev => ({ ...prev, phone }));
};

const setLocationObjectId = (locationObjectId: string) => {
setState(prev => ({ ...prev, locationObjectId }));
};

const setLastestLocationObjectId = (lastestLocationObjectId: string) => {
setState(prev => ({ ...prev, lastestLocationObjectId }));
};
Expand All @@ -176,7 +212,11 @@
setToken,
setUserObjectId,
setFirstName,
setLastName,
setUserRole,
setEmail,
setPhone,
setLocationObjectId,
setLastestLocationObjectId,
setPermissions,
clearSession,
Expand All @@ -188,7 +228,7 @@
);
};

export const useAuthContext = () => {

Check warning on line 231 in client/src/contexts/AuthContext.tsx

View workflow job for this annotation

GitHub Actions / lint-and-type-check (client)

Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuthContext must be used within AuthProvider');
Expand Down
8 changes: 5 additions & 3 deletions client/src/hooks/useAuth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useAuthContext } from '@/contexts';
import { useSurveyStore } from '@/stores/useSurveyStore';
import {
deleteAuthToken,
getObjectId,
getDecodedAuthToken,
saveAuthToken
} from '@/utils/authTokenHandler';

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

const handleLogin = async (token: string) => {
saveAuthToken(token);
const userObjectId = getObjectId();
await fetchUserContext(userObjectId);
const userObjectId = getDecodedAuthToken()?.userObjectId;
if (userObjectId) {
await fetchUserContext(userObjectId);
}
};

const handleLogout = () => {
Expand Down
5 changes: 3 additions & 2 deletions client/src/pages/Login/Login.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEffect, useState } from 'react';

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

Expand All @@ -9,6 +9,7 @@ import { useAuth } from '@/hooks/useAuth';
export default function Login() {
const { handleLogin } = useAuth();
const navigate = useNavigate();
const { clearSession } = useSurveyStore();
// const [email, setEmail] = useState('');
const [phone, setPhone] = useState('');
const [otp, setOtp] = useState('');
Expand Down Expand Up @@ -83,7 +84,7 @@ export default function Login() {
if (response.ok) {
// Successful login and store user data
await handleLogin(data.token);
initializeSurveyStore();
clearSession(); // Clear any previous survey data from storage
navigate(data.redirectTo);
} else {
setErrorMessage(data.message);
Expand Down
59 changes: 39 additions & 20 deletions client/src/pages/Profile/Profile.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useState } from 'react';

import { useAuthContext } from '@/contexts';
import { useAbility, useApi } from '@/hooks';
import { ACTIONS, SUBJECTS } from '@/permissions/constants';
import { subject } from '@casl/ability';
Expand All @@ -13,36 +14,43 @@ import {
RoleSelect
} from '@/components/forms';

export default function AdminEditProfile() {
export default function Profile() {
const ability = useAbility();
const { userService } = useApi();
const { id } = useParams();
const [message, setMessage] = useState('');
const [error, _setError] = useState('');

// 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.
const { data: user, mutate } = userService.useUser(id) ?? {};
const {
locationObjectId,
firstName,
lastName,
userObjectId,
userRole,
email,
phone,
setEmail,
setPhone,
setLocationObjectId,
setUserRole
} = useAuthContext();

const canEditField = (field: string) => {
if (!user) return false; // wait until user is fetched
return ability.can(
ACTIONS.CASL.UPDATE,
subject(SUBJECTS.USER, user),
subject(SUBJECTS.USER, { _id: userObjectId }),
field
);
};

const handleSave = async () => {
if (!user) return;

try {
const updatePayload: Record<string, any> = {};

if (canEditField('role')) updatePayload.role = user.role;
if (canEditField('email')) updatePayload.email = user.email;
if (canEditField('phone')) updatePayload.phone = user.phone;
if (canEditField('role')) updatePayload.role = userRole;
if (canEditField('email')) updatePayload.email = email;
if (canEditField('phone')) updatePayload.phone = phone;
if (canEditField('locationObjectId'))
updatePayload.locationObjectId = user.locationObjectId;
updatePayload.locationObjectId = locationObjectId;

const updatedUser = await userService.updateUser(
id!,
Expand All @@ -53,7 +61,6 @@ export default function AdminEditProfile() {
setMessage(
updatedUser.message || 'Profile updated successfully!'
);
mutate?.(); // refetch the user
} else {
setMessage(updatedUser.message || 'Failed to update profile.');
}
Expand All @@ -70,7 +77,20 @@ export default function AdminEditProfile() {
}
}

mutate?.({ ...user, [field]: value }, false);
switch (field) {
case 'email':
setEmail(value);
break;
case 'phone':
setPhone(value);
break;
case 'locationObjectId':
setLocationObjectId(value);
break;
case 'role':
setUserRole(value);
break;
}
};

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

{error ? (
Expand All @@ -98,7 +117,7 @@ export default function AdminEditProfile() {
<FormInput
label="Email"
type="email"
value={user?.email || ''}
value={email || ''}
onChange={e =>
handleChange('email', e.target.value)
}
Expand All @@ -108,7 +127,7 @@ export default function AdminEditProfile() {

<PhoneInput
label="Phone Number"
value={user?.phone || ''}
value={phone || ''}
onChange={e =>
handleChange('phone', e.target.value)
}
Expand All @@ -117,7 +136,7 @@ export default function AdminEditProfile() {
/>

<RoleSelect
value={user?.role || ''}
value={userRole || ''}
onChange={e =>
handleChange('role', e.target.value as string)
}
Expand All @@ -126,7 +145,7 @@ export default function AdminEditProfile() {
/>

<LocationSelect
value={user?.locationObjectId || ''}
value={locationObjectId || ''}
onChange={e =>
handleChange(
'locationObjectId',
Expand Down
Loading
Loading