Skip to content

Commit 1ce0991

Browse files
committed
implemented scheduling call, request new times, cancelling call, edit profile flows in participant dash. implemented contact form and sign out button for participant and volunteer dash
1 parent 84470d0 commit 1ce0991

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+4420
-1184
lines changed

backend/app/routes/auth.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,19 @@ async def logout(
6363
request: Request,
6464
credentials: HTTPAuthorizationCredentials = Depends(security),
6565
auth_service: AuthService = Depends(get_auth_service),
66+
user_service: UserService = Depends(get_user_service),
6667
):
6768
try:
68-
user_id = request.state.user_id
69-
if not user_id:
69+
auth_id = request.state.user_id # This is actually the Firebase auth_id
70+
if not auth_id:
7071
raise HTTPException(status_code=401, detail="Authentication required")
7172

73+
# Convert Firebase auth_id to database user_id (UUID)
74+
user_id = await user_service.get_user_id_by_auth_id(auth_id)
7275
auth_service.revoke_tokens(user_id)
7376
return {"message": "Successfully logged out"}
77+
except ValueError as e:
78+
raise HTTPException(status_code=404, detail=str(e))
7479
except Exception as e:
7580
raise HTTPException(status_code=500, detail=str(e))
7681

backend/app/routes/contact.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import logging
2+
3+
from fastapi import APIRouter, Depends, HTTPException, Request
4+
from sqlalchemy.orm import Session
5+
6+
from app.middleware.auth import has_roles
7+
from app.models import User
8+
from app.schemas.contact import ContactRequest, ContactResponse
9+
from app.schemas.user import UserRole
10+
from app.utilities.constants import LOGGER_NAME
11+
from app.utilities.db_utils import get_db
12+
13+
log = logging.getLogger(LOGGER_NAME("contact"))
14+
15+
router = APIRouter(
16+
prefix="/contact",
17+
tags=["contact"],
18+
)
19+
20+
21+
@router.post("/submit", response_model=ContactResponse)
22+
async def submit_contact_form(
23+
contact_data: ContactRequest,
24+
request: Request,
25+
db: Session = Depends(get_db),
26+
authorized: bool = has_roles([UserRole.PARTICIPANT, UserRole.VOLUNTEER, UserRole.ADMIN]),
27+
):
28+
"""
29+
Submit a contact form message from a user.
30+
31+
This endpoint receives contact form submissions from participants or volunteers
32+
and sends the message to the admin team.
33+
34+
Args:
35+
contact_data: The contact form data (name, email, message)
36+
request: The FastAPI request object (contains user_id from auth middleware)
37+
db: Database session
38+
39+
Returns:
40+
ContactResponse with success status and message
41+
"""
42+
try:
43+
# Get current user from auth middleware
44+
current_user_auth_id = request.state.user_id
45+
current_user = db.query(User).filter(User.auth_id == current_user_auth_id).first()
46+
47+
if not current_user:
48+
raise HTTPException(status_code=401, detail="User not found")
49+
50+
# Log the contact form submission
51+
log.info(
52+
f"Contact form submission from user {current_user.id} "
53+
f"(name: {contact_data.name}, email: {contact_data.email})"
54+
)
55+
log.info(f"Message: {contact_data.message}")
56+
57+
# TODO: Send email to admin team
58+
# This will be implemented in a future update
59+
# For now, we just log the message and return success
60+
61+
return ContactResponse(
62+
success=True,
63+
message="Your message has been sent successfully. A staff member will get back to you as soon as possible.",
64+
)
65+
66+
except HTTPException:
67+
raise
68+
except Exception as e:
69+
log.error(f"Error submitting contact form: {str(e)}")
70+
raise HTTPException(status_code=500, detail="Failed to submit contact form")

backend/app/routes/user_data.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ class UserDataResponse(BaseModel):
6262
has_kids: Optional[str] = None
6363
other_ethnic_group: Optional[str] = None
6464
gender_identity_custom: Optional[str] = None
65+
timezone: Optional[str] = None
6566

6667
# Cancer Experience
6768
diagnosis: Optional[str] = None
@@ -147,6 +148,7 @@ async def get_my_user_data(
147148
has_kids=user_data.has_kids,
148149
other_ethnic_group=user_data.other_ethnic_group,
149150
gender_identity_custom=user_data.gender_identity_custom,
151+
timezone=user_data.timezone,
150152
# Cancer Experience
151153
diagnosis=user_data.diagnosis,
152154
date_of_diagnosis=user_data.date_of_diagnosis.isoformat() if user_data.date_of_diagnosis else None,
@@ -300,6 +302,21 @@ async def update_my_user_data(
300302
if experience:
301303
user_data.loved_one_experiences.append(experience)
302304

305+
# Update user language (stored on User model, not UserData)
306+
if "language" in update_data:
307+
from ..models.User import Language
308+
309+
try:
310+
language_value = update_data["language"]
311+
if language_value in ["en", "fr"]:
312+
current_user.language = Language(language_value)
313+
except (ValueError, AttributeError):
314+
pass # Invalid language value, skip
315+
316+
# Update user timezone (stored on UserData model)
317+
if "timezone" in update_data:
318+
user_data.timezone = update_data["timezone"]
319+
303320
db.commit()
304321
db.refresh(user_data)
305322

@@ -330,6 +347,7 @@ async def update_my_user_data(
330347
has_kids=user_data.has_kids,
331348
other_ethnic_group=user_data.other_ethnic_group,
332349
gender_identity_custom=user_data.gender_identity_custom,
350+
timezone=user_data.timezone,
333351
diagnosis=user_data.diagnosis,
334352
date_of_diagnosis=user_data.date_of_diagnosis.isoformat() if user_data.date_of_diagnosis else None,
335353
treatments=[treatment.name for treatment in user_data.treatments],

backend/app/schemas/contact.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from pydantic import BaseModel
2+
3+
4+
class ContactRequest(BaseModel):
5+
"""Schema for contact form submission"""
6+
7+
name: str
8+
email: str
9+
message: str
10+
11+
12+
class ContactResponse(BaseModel):
13+
"""Schema for contact form response"""
14+
15+
success: bool
16+
message: str

backend/app/schemas/match.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ class MatchVolunteerSummary(BaseModel):
4747
first_name: Optional[str] = None
4848
last_name: Optional[str] = None
4949
email: str
50+
phone: Optional[str] = None
5051
pronouns: Optional[List[str]] = None
5152
diagnosis: Optional[str] = None
5253
age: Optional[int] = None

backend/app/seeds/users.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Seed users data for testing matching functionality."""
22

33
import uuid
4-
from datetime import date
4+
from datetime import date, time
55

66
from sqlalchemy import delete
77
from sqlalchemy.orm import Session
@@ -131,6 +131,7 @@ def seed_users(session: Session) -> None:
131131
"date_of_diagnosis": date(2018, 4, 20), # Survivor
132132
"has_blood_cancer": "yes",
133133
"caring_for_someone": "no",
134+
"timezone": "EST",
134135
},
135136
"treatments": [
136137
TreatmentId.CHEMOTHERAPY,
@@ -143,6 +144,12 @@ def seed_users(session: Session) -> None:
143144
ExperienceId.FATIGUE,
144145
ExperienceId.RETURNING_TO_WORK,
145146
],
147+
"volunteer_experience": "My journey with blood cancer started when I was about twelve years old and getting treatment for the first time was extremely stress-inducing. My journey with blood cancer started when I was about twelve years old and getting treatment for the first time was extremely stress-inducing.",
148+
"availability_templates": [
149+
{"day_of_week": 1, "start_time": time(14, 0), "end_time": time(16, 0)}, # Tuesday 2-4pm
150+
{"day_of_week": 3, "start_time": time(14, 0), "end_time": time(17, 0)}, # Thursday 2-5pm
151+
{"day_of_week": 4, "start_time": time(10, 0), "end_time": time(12, 0)}, # Friday 10am-12pm
152+
],
146153
},
147154
{
148155
"role": "volunteer",
@@ -165,9 +172,16 @@ def seed_users(session: Session) -> None:
165172
"date_of_diagnosis": date(2020, 8, 15), # Survivor
166173
"has_blood_cancer": "yes",
167174
"caring_for_someone": "no",
175+
"timezone": "PST",
168176
},
169177
"treatments": [3, 6], # Chemotherapy, Radiation (matching Sarah's preferences)
170178
"experiences": [1, 3, 4], # Brain Fog, Feeling Overwhelmed, Fatigue (same as Sarah!)
179+
"volunteer_experience": "I was diagnosed with ALL at 34 and underwent intensive chemotherapy and radiation. The hardest part was balancing treatment with being a mother. I want to help others navigate this difficult journey and share what I learned.",
180+
"availability_templates": [
181+
{"day_of_week": 0, "start_time": time(16, 0), "end_time": time(18, 0)}, # Monday 4-6pm
182+
{"day_of_week": 2, "start_time": time(11, 0), "end_time": time(13, 0)}, # Wednesday 11am-1pm
183+
{"day_of_week": 4, "start_time": time(13, 0), "end_time": time(15, 0)}, # Friday 1-3pm
184+
],
171185
},
172186
{
173187
"role": "volunteer",
@@ -190,9 +204,16 @@ def seed_users(session: Session) -> None:
190204
"date_of_diagnosis": date(2020, 2, 14),
191205
"has_blood_cancer": "yes",
192206
"caring_for_someone": "no",
207+
"timezone": "EST",
193208
},
194209
"treatments": [3, 6], # Chemotherapy, Radiation
195210
"experiences": [10, 11, 7], # Anxiety/Depression, PTSD, Returning to work
211+
"volunteer_experience": "Fighting Hodgkin Lymphoma taught me resilience I never knew I had. The mental health challenges were just as tough as the physical ones. I'm here to listen and support anyone going through similar struggles.",
212+
"availability_templates": [
213+
{"day_of_week": 1, "start_time": time(9, 0), "end_time": time(11, 0)}, # Tuesday 9-11am
214+
{"day_of_week": 3, "start_time": time(14, 0), "end_time": time(16, 0)}, # Thursday 2-4pm
215+
{"day_of_week": 5, "start_time": time(10, 0), "end_time": time(12, 0)}, # Saturday 10am-12pm
216+
],
196217
},
197218
# High-matching volunteers for Sarah Johnson
198219
{
@@ -216,9 +237,15 @@ def seed_users(session: Session) -> None:
216237
"date_of_diagnosis": date(2019, 5, 10), # Survivor
217238
"has_blood_cancer": "yes",
218239
"caring_for_someone": "no",
240+
"timezone": "EST",
219241
},
220242
"treatments": [3, 6], # Chemotherapy, Radiation (matching Sarah's preferences)
221243
"experiences": [1, 3, 4], # Brain Fog, Feeling Overwhelmed, Fatigue (same as Sarah!)
244+
"volunteer_experience": "As a working mother of two, being diagnosed with leukemia turned my world upside down. Brain fog and fatigue made everyday tasks feel impossible. Now in remission, I'm passionate about helping others find their strength during treatment.",
245+
"availability_templates": [
246+
{"day_of_week": 2, "start_time": time(14, 0), "end_time": time(17, 0)}, # Wednesday 2-5pm
247+
{"day_of_week": 3, "start_time": time(16, 0), "end_time": time(18, 0)}, # Thursday 4-6pm
248+
],
222249
},
223250
{
224251
"role": "volunteer",
@@ -241,9 +268,15 @@ def seed_users(session: Session) -> None:
241268
"date_of_diagnosis": date(2021, 3, 18), # Survivor
242269
"has_blood_cancer": "yes",
243270
"caring_for_someone": "no",
271+
"timezone": "MST",
244272
},
245273
"treatments": [3, 6], # Chemotherapy, Radiation (matching Sarah's preferences)
246274
"experiences": [1, 3, 4, 10], # Brain Fog, Feeling Overwhelmed, Fatigue, Anxiety/Depression
275+
"volunteer_experience": "My cancer journey came with intense anxiety and depression alongside the physical symptoms. Through therapy and support groups, I learned to cope with the overwhelming emotions. I want to be that supportive voice for others facing the same battles.",
276+
"availability_templates": [
277+
{"day_of_week": 0, "start_time": time(10, 0), "end_time": time(12, 0)}, # Monday 10am-12pm
278+
{"day_of_week": 4, "start_time": time(14, 30), "end_time": time(16, 30)}, # Friday 2:30-4:30pm
279+
],
247280
},
248281
{
249282
"role": "volunteer",
@@ -266,9 +299,16 @@ def seed_users(session: Session) -> None:
266299
"date_of_diagnosis": date(2018, 9, 25), # Survivor
267300
"has_blood_cancer": "yes",
268301
"caring_for_someone": "no",
302+
"timezone": "CST",
269303
},
270304
"treatments": [3, 6, 7], # Chemotherapy, Radiation, Maintenance Chemo
271305
"experiences": [1, 4, 5], # Brain Fog, Feeling Overwhelmed, Fatigue (same as Sarah!)
306+
"volunteer_experience": "Seven years ago, ALL changed everything. The fatigue was relentless, and brain fog made me feel like a stranger in my own mind. But I made it through, and now I want to offer hope and practical advice to anyone starting this journey.",
307+
"availability_templates": [
308+
{"day_of_week": 1, "start_time": time(15, 0), "end_time": time(17, 0)}, # Tuesday 3-5pm
309+
{"day_of_week": 3, "start_time": time(10, 0), "end_time": time(12, 0)}, # Thursday 10am-12pm
310+
{"day_of_week": 5, "start_time": time(14, 0), "end_time": time(16, 0)}, # Saturday 2-4pm
311+
],
272312
},
273313
# Test Case 3: Participant who is a caregiver wanting caregiver volunteers
274314
{
@@ -434,6 +474,22 @@ def seed_users(session: Session) -> None:
434474
)
435475
session.add(volunteer_data)
436476

477+
# Add availability templates for volunteers (if specified)
478+
# Skip [email protected] so they can set their own availability
479+
if (
480+
user_info.get("availability_templates")
481+
and user_info["user_data"]["email"] != "[email protected]"
482+
):
483+
for template_info in user_info["availability_templates"]:
484+
availability_template = AvailabilityTemplate(
485+
user_id=user.id,
486+
day_of_week=template_info["day_of_week"],
487+
start_time=template_info["start_time"],
488+
end_time=template_info["end_time"],
489+
is_active=True,
490+
)
491+
session.add(availability_template)
492+
437493
created_users.append((user, user_info["role"]))
438494
print(f"Added {user_info['role']}: {user.first_name} {user.last_name}")
439495

backend/app/server.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from .routes import (
1212
auth,
1313
availability,
14+
contact,
1415
intake,
1516
match,
1617
matching,
@@ -90,6 +91,7 @@ async def lifespan(_: FastAPI):
9091
app.include_router(send_email.router)
9192
app.include_router(task.router)
9293
app.include_router(test.router)
94+
app.include_router(contact.router)
9395

9496

9597
@app.get("/")

backend/app/services/email/amazon_ses_provider.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,5 @@ def get_email_service_provider() -> IEmailServiceProvider:
8282
aws_secret_key=os.getenv("AWS_SECRET_KEY"),
8383
region=os.getenv("AWS_REGION"),
8484
source_email=os.getenv("SES_SOURCE_EMAIL"),
85+
is_sandbox=os.getenv("IS_SANDBOX", "True").lower() == "true",
8586
)

backend/app/services/implementations/match_service.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -345,8 +345,8 @@ async def cancel_match_by_participant(
345345
if acting_participant_id and match.participant_id != acting_participant_id:
346346
raise HTTPException(status_code=403, detail="Cannot modify another participant's match")
347347

348-
self._clear_confirmed_time(match)
349-
self._set_match_status(match, "cancelled_by_participant")
348+
# Soft-delete the match when cancelled (cleans up time blocks and sets deleted_at)
349+
self._delete_match(match)
350350

351351
self.db.flush()
352352
self.db.commit()
@@ -665,6 +665,7 @@ def _build_match_detail(self, match: Match) -> MatchDetailResponse:
665665
diagnosis = None
666666
age: Optional[int] = None
667667
timezone: Optional[str] = None
668+
phone: Optional[str] = None
668669
treatments: List[str] = []
669670
experiences: List[str] = []
670671
overview: Optional[str] = None
@@ -674,6 +675,7 @@ def _build_match_detail(self, match: Match) -> MatchDetailResponse:
674675
pronouns = volunteer_data.pronouns
675676
diagnosis = volunteer_data.diagnosis
676677
timezone = volunteer_data.timezone
678+
phone = volunteer_data.phone
677679

678680
if volunteer_data.date_of_birth:
679681
age = self._calculate_age(volunteer_data.date_of_birth)
@@ -693,6 +695,7 @@ def _build_match_detail(self, match: Match) -> MatchDetailResponse:
693695
first_name=volunteer.first_name,
694696
last_name=volunteer.last_name,
695697
email=volunteer.email,
698+
phone=phone,
696699
pronouns=pronouns,
697700
diagnosis=diagnosis,
698701
age=age,

frontend/src/APIClients/authAPIClient.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { signOut as firebaseSignOut } from 'firebase/auth';
12
import {
23
AuthenticatedUser,
34
UserCreateResponse,
@@ -149,12 +150,20 @@ export const logout = async (): Promise<boolean> => {
149150
const bearerToken = `Bearer ${getLocalStorageObjProperty(AUTHENTICATED_USER_KEY, 'accessToken')}`;
150151

151152
try {
153+
// Call backend to revoke tokens
152154
await baseAPIClient.post('/auth/logout', {}, { headers: { Authorization: bearerToken } });
153-
localStorage.removeItem(AUTHENTICATED_USER_KEY);
154155
return true;
155156
} catch (error) {
156-
console.error('Logout error:', error);
157+
console.error('Backend logout error:', error);
157158
return false;
159+
} finally {
160+
// Always clear localStorage and sign out from Firebase regardless of backend API success/failure
161+
localStorage.removeItem(AUTHENTICATED_USER_KEY);
162+
try {
163+
await firebaseSignOut(auth);
164+
} catch (firebaseError) {
165+
console.error('Firebase sign out error:', firebaseError);
166+
}
158167
}
159168
};
160169

0 commit comments

Comments
 (0)