From a356f8b8cb6d1304e0666112f11b9f9e4d965a6a Mon Sep 17 00:00:00 2001 From: matia Date: Mon, 7 Jul 2025 23:10:03 -0700 Subject: [PATCH 1/2] faq + soft delete --- backend/app/models/User.py | 1 + backend/app/routes/user.py | 15 ++ .../services/implementations/user_service.py | 17 ++ .../abcd1234_add_active_column_to_users.py | 21 +++ frontend/src/pages/faq.tsx | 154 ++++++++++++++++++ 5 files changed, 208 insertions(+) create mode 100644 backend/migrations/versions/abcd1234_add_active_column_to_users.py create mode 100644 frontend/src/pages/faq.tsx 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..b8af4c28 --- /dev/null +++ b/backend/migrations/versions/abcd1234_add_active_column_to_users.py @@ -0,0 +1,21 @@ +"""add active column to users + +Revision ID: abcd1234active +Revises: df571b763807 +Create Date: 2025-07-08 +""" + +from alembic import op +import sqlalchemy as sa + +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') \ No newline at end of file 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 && ( + + )} +
+ )} +
+ ); + })} +
+
+
+
+ ); +} From 6ba1e48e83b55e37be7e5ee8a0224b1bd515ae1d Mon Sep 17 00:00:00 2001 From: matia Date: Mon, 7 Jul 2025 23:10:45 -0700 Subject: [PATCH 2/2] formatting --- .../versions/abcd1234_add_active_column_to_users.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/backend/migrations/versions/abcd1234_add_active_column_to_users.py b/backend/migrations/versions/abcd1234_add_active_column_to_users.py index b8af4c28..01d96ff2 100644 --- a/backend/migrations/versions/abcd1234_add_active_column_to_users.py +++ b/backend/migrations/versions/abcd1234_add_active_column_to_users.py @@ -5,17 +5,18 @@ Create Date: 2025-07-08 """ -from alembic import op import sqlalchemy as sa +from alembic import op -revision = 'abcd1234active' -down_revision = 'df571b763807' +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())) + op.add_column("users", sa.Column("active", sa.Boolean(), nullable=False, server_default=sa.true())) def downgrade(): - op.drop_column('users', 'active') \ No newline at end of file + op.drop_column("users", "active")