Skip to content

Commit 8a86322

Browse files
committed
added loading/error states to admin directory, updated rejection screen
1 parent a006926 commit 8a86322

File tree

5 files changed

+397
-214
lines changed

5 files changed

+397
-214
lines changed

backend/migrations/versions/8d2cd99b9eb8_add_rejected_enum_value_to_user_model.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""add rejected enum value to User model
22
33
Revision ID: 8d2cd99b9eb8
4-
Revises: b56e0bf600a2
4+
Revises: 9f1a6d727929
55
Create Date: 2025-10-30 19:50:23.495788
66
77
"""
@@ -12,7 +12,7 @@
1212

1313
# revision identifiers, used by Alembic.
1414
revision: str = "8d2cd99b9eb8"
15-
down_revision: Union[str, None] = "b56e0bf600a2"
15+
down_revision: Union[str, None] = "9f1a6d727929"
1616
branch_labels: Union[str, Sequence[str], None] = None
1717
depends_on: Union[str, Sequence[str], None] = None
1818

frontend/src/components/admin/DirectoryDataProvider.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,21 @@ export function DirectoryDataProvider({ children }: DirectoryDataProviderProps)
1515
useEffect(() => {
1616
const fetchUsers = async () => {
1717
try {
18+
setLoading(true);
19+
setError(null);
1820
const response = await baseAPIClient.get('/users');
19-
setUsers(response.data.users || response.data);
21+
const usersData = response.data?.users || response.data || [];
22+
setUsers(Array.isArray(usersData) ? usersData : []);
2023
} catch (err) {
21-
setError(err as Error);
24+
const error =
25+
err instanceof Error
26+
? err
27+
: new Error(
28+
typeof err === 'object' && err !== null && 'message' in err
29+
? String(err.message)
30+
: 'Failed to fetch users. Please try again.',
31+
);
32+
setError(error);
2233
console.error('Failed to fetch users:', err);
2334
} finally {
2435
setLoading(false);

frontend/src/components/intake/rejection-screen.tsx

Lines changed: 95 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
import React from 'react';
2-
import { Box, Text, VStack } from '@chakra-ui/react';
1+
import React, { useEffect, useMemo, useState } from 'react';
2+
import { Box, Link, Text, VStack } from '@chakra-ui/react';
33
import { COLORS } from '@/constants/form';
4+
import type { AuthenticatedUser, UserRole } from '@/types/authTypes';
5+
import { UserRole as UserRoleEnum } from '@/types/authTypes';
6+
import { getCurrentUser, syncCurrentUser } from '@/APIClients/authAPIClient';
7+
import { roleIdToUserRole } from '@/utils/roleUtils';
48

59
// X mark icon component
610
const XMarkIcon: React.FC = () => (
@@ -28,6 +32,53 @@ const XMarkIcon: React.FC = () => (
2832
);
2933

3034
export function RejectionScreen() {
35+
const [userRole, setUserRole] = useState<UserRole | null>(null);
36+
37+
useEffect(() => {
38+
const resolveRole = (user: AuthenticatedUser): UserRole | null => {
39+
if (!user) return null;
40+
if ('role' in user && user.role) {
41+
return user.role as UserRole;
42+
}
43+
const roleId = user.user?.roleId ?? (user.roleId as unknown as number | undefined) ?? null;
44+
return roleIdToUserRole(roleId);
45+
};
46+
47+
const hydrateRole = async () => {
48+
const localUser = getCurrentUser();
49+
let resolved = resolveRole(localUser);
50+
if (!resolved) {
51+
const synced = await syncCurrentUser();
52+
resolved = resolveRole(synced);
53+
}
54+
setUserRole(resolved ?? UserRoleEnum.PARTICIPANT);
55+
};
56+
57+
void hydrateRole();
58+
}, []);
59+
60+
const content = useMemo(() => {
61+
if (userRole === UserRoleEnum.VOLUNTEER) {
62+
return {
63+
title: "You're unable to continue this application.",
64+
body: [
65+
'Thank you for your interest in becoming a peer support volunteer.',
66+
'You must meet all of the eligibility criteria to continue.',
67+
'Please reach out to [email protected] for more information about volunteering with LLSC.',
68+
],
69+
};
70+
}
71+
72+
return {
73+
title: "You're unable to continue this application.",
74+
body: [
75+
'Thank you for your interest in First Connections.',
76+
'To continue as a participant you must meet each of the eligibility requirements.',
77+
'Please reach out to [email protected] if you have questions or need help finding other support options.',
78+
],
79+
};
80+
}, [userRole]);
81+
3182
return (
3283
<Box minH="100vh" bg="white" display="flex" alignItems="center" justifyContent="center" py={12}>
3384
<VStack gap={6}>
@@ -40,23 +91,50 @@ export function RejectionScreen() {
4091
color={COLORS.veniceBlue}
4192
mb={2}
4293
>
43-
Your request has been declined.
94+
{content.title}
4495
</Text>
4596

46-
<Text
47-
fontFamily="system-ui, -apple-system, sans-serif"
48-
fontSize="18px"
49-
color={COLORS.fieldGray}
50-
lineHeight="1.6"
51-
maxW="600px"
52-
textAlign="center"
53-
>
54-
For any inquiries, please reach us at{' '}
55-
<Text as="span" color={COLORS.teal} fontWeight={500}>
56-
57-
</Text>
58-
.
59-
</Text>
97+
<VStack gap={1} maxW="640px">
98+
{content.body.map((sentence, index) => {
99+
const EMAIL_TOKEN = '[email protected]';
100+
const hasEmail = sentence.includes(EMAIL_TOKEN);
101+
let prefix = '';
102+
let suffix = '';
103+
if (hasEmail) {
104+
const parts = sentence.split(EMAIL_TOKEN);
105+
prefix = parts[0] ?? '';
106+
suffix = parts[1] ?? '';
107+
}
108+
109+
return (
110+
<Text
111+
key={`${sentence}-${index}`}
112+
fontFamily="system-ui, -apple-system, sans-serif"
113+
fontSize="18px"
114+
color={COLORS.fieldGray}
115+
lineHeight="1.6"
116+
textAlign="center"
117+
>
118+
{hasEmail ? (
119+
<>
120+
{prefix}
121+
<Link
122+
href="mailto:[email protected]"
123+
color={COLORS.teal}
124+
textDecoration="underline"
125+
fontWeight={600}
126+
>
127+
128+
</Link>
129+
{suffix}
130+
</>
131+
) : (
132+
sentence
133+
)}
134+
</Text>
135+
);
136+
})}
137+
</VStack>
60138
</VStack>
61139
</Box>
62140
);

frontend/src/constants/formStatusRoutes.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export const PARTICIPANT_STATUS_ROUTES: StatusRouteMap = {
1010
[FormStatus.SECONDARY_APPLICATION_TODO]: '/participant/ranking',
1111
[FormStatus.SECONDARY_APPLICATION_SUBMITTED]: '/participant/ranking/thank-you',
1212
[FormStatus.COMPLETED]: '/participant/dashboard',
13+
[FormStatus.REJECTED]: '/rejection',
1314
};
1415

1516
export const VOLUNTEER_STATUS_ROUTES: StatusRouteMap = {
@@ -20,6 +21,7 @@ export const VOLUNTEER_STATUS_ROUTES: StatusRouteMap = {
2021
[FormStatus.SECONDARY_APPLICATION_TODO]: '/volunteer/secondary-application',
2122
[FormStatus.SECONDARY_APPLICATION_SUBMITTED]: '/volunteer/secondary-application/thank-you',
2223
[FormStatus.COMPLETED]: '/volunteer/dashboard',
24+
[FormStatus.REJECTED]: '/rejection',
2325
};
2426

2527
export const ADMIN_STATUS_ROUTES: StatusRouteMap = {
@@ -30,6 +32,7 @@ export const ADMIN_STATUS_ROUTES: StatusRouteMap = {
3032
[FormStatus.SECONDARY_APPLICATION_TODO]: '/admin',
3133
[FormStatus.SECONDARY_APPLICATION_SUBMITTED]: '/admin',
3234
[FormStatus.COMPLETED]: '/admin',
35+
[FormStatus.REJECTED]: '/admin',
3336
};
3437

3538
export const ROLE_STATUS_ROUTES: Record<UserRole, StatusRouteMap> = {

0 commit comments

Comments
 (0)