diff --git a/backend/app/models/User.py b/backend/app/models/User.py index 8ed191ee..b8a9733c 100644 --- a/backend/app/models/User.py +++ b/backend/app/models/User.py @@ -17,6 +17,7 @@ class User(Base): role_id = Column(Integer, ForeignKey("roles.id"), nullable=False) auth_id = Column(String, nullable=False) approved = Column(Boolean, default=False) + active = Column(Boolean, nullable=False, default=True) role = relationship("Role") diff --git a/backend/app/routes/user.py b/backend/app/routes/user.py index eb1c10ca..eec68444 100644 --- a/backend/app/routes/user.py +++ b/backend/app/routes/user.py @@ -103,3 +103,18 @@ async def delete_user( raise http_ex except Exception as e: raise HTTPException(status_code=500, detail=str(e)) + + +# soft delete user +@router.post("/{user_id}/deactivate") +async def deactivate_user( + user_id: str, + user_service: UserService = Depends(get_user_service), +): + try: + await user_service.soft_delete_user_by_id(user_id) + return {"message": "User deactivated successfully"} + except HTTPException as http_ex: + raise http_ex + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) diff --git a/backend/app/services/implementations/user_service.py b/backend/app/services/implementations/user_service.py index 3667b0b3..40c1930f 100644 --- a/backend/app/services/implementations/user_service.py +++ b/backend/app/services/implementations/user_service.py @@ -114,6 +114,23 @@ async def delete_user_by_id(self, user_id: str): self.logger.error(f"Error deleting user {user_id}: {str(e)}") raise HTTPException(status_code=500, detail=str(e)) + async def soft_delete_user_by_id(self, user_id: str): + try: + db_user = self.db.query(User).filter(User.id == UUID(user_id)).first() + if not db_user: + raise HTTPException(status_code=404, detail="User not found") + + db_user.active = False + self.db.commit() + except ValueError: + raise HTTPException(status_code=400, detail="Invalid user ID format") + except HTTPException: + raise + except Exception as e: + self.db.rollback() + self.logger.error(f"Error deactivating user {user_id}: {str(e)}") + raise HTTPException(status_code=500, detail=str(e)) + async def get_user_id_by_auth_id(self, auth_id: str) -> str: """Get user ID for a user by their Firebase auth_id""" user = self.db.query(User).filter(User.auth_id == auth_id).first() diff --git a/backend/migrations/versions/abcd1234_add_active_column_to_users.py b/backend/migrations/versions/abcd1234_add_active_column_to_users.py new file mode 100644 index 00000000..01d96ff2 --- /dev/null +++ b/backend/migrations/versions/abcd1234_add_active_column_to_users.py @@ -0,0 +1,22 @@ +"""add active column to users + +Revision ID: abcd1234active +Revises: df571b763807 +Create Date: 2025-07-08 +""" + +import sqlalchemy as sa +from alembic import op + +revision = "abcd1234active" +down_revision = "df571b763807" +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column("users", sa.Column("active", sa.Boolean(), nullable=False, server_default=sa.true())) + + +def downgrade(): + op.drop_column("users", "active") diff --git a/frontend/src/pages/faq.tsx b/frontend/src/pages/faq.tsx new file mode 100644 index 00000000..3c2a4974 --- /dev/null +++ b/frontend/src/pages/faq.tsx @@ -0,0 +1,154 @@ +import React, { useState } from 'react'; +import { FiChevronDown, FiChevronUp } from 'react-icons/fi'; +import { COLORS } from '@/constants/form'; +import { useRouter } from 'next/router'; + +interface FAQItem { + id: string; + question: string; + answer: string; + actionButton?: { + text: string; + action: () => void; + }; +} + +const ACCENT_COLOR = '#056067'; +const BORDER_COLOR_EXPANDED = '#5F989D'; +const SHADOW_COLOR = '#B3CED1'; + +export default function FAQPage() { + const [expandedFAQs, setExpandedFAQs] = useState([]); + const router = useRouter(); + + const faqData: FAQItem[] = [ + { + id: 'contact-staff', + question: 'How can I contact a staff member?', + answer: + 'Click the button below to fill out a short form, it only takes a minute! Once submitted, a staff member will follow up via email within 2 business days to support your needs from FirstConnections@lls.org.', + actionButton: { + text: 'Contact Us!', + action: () => true, + }, + }, + { + id: 'become-volunteer', + question: 'How can I apply to become a volunteer?', + answer: + "Complete the volunteer application to express your interest and confirm these details are correct. Once submitted, we'll follow up by email with next steps.", + actionButton: { + text: 'Become a volunteer!', + action: () => router.push('/volunteer/intake'), + }, + }, + { + id: 'opt-out', + question: 'How can I opt out of the First Connections program?', + answer: + 'Your experience is important to us. Our volunteers are the most important part of First Connection. Volunteers are encouraged to take the time they need away from the program when they need it. By opting out you are removing yourself from the matching algorithm and cannot be connected with a potential participant.\n\nWhen you are ready to volunteer with us again, please sign back in and click the Opt In. You do not need to re-register or create a new profile. If you would like to talk with a staff member about your time away or remove yourself completely from the program please reach out, we are here to help.', + actionButton: { + text: 'Opt out', + action: () => true, + }, + }, + ]; + + return ( +
+
+
+

+ Frequently asked questions +

+ +
+ {faqData.map((faq) => { + const isOpen = expandedFAQs.includes(faq.id); + return ( +
+ + {isOpen && ( +
+
+ {faq.answer} +
+ {faq.actionButton && ( + + )} +
+ )} +
+ ); + })} +
+
+
+
+ ); +}