diff --git a/backend/prisma/seeds/pageContentSeed.ts b/backend/prisma/seeds/pageContentSeed.ts index 5721de4..eebc62a 100644 --- a/backend/prisma/seeds/pageContentSeed.ts +++ b/backend/prisma/seeds/pageContentSeed.ts @@ -289,6 +289,93 @@ async function main() { contentValue: 'Important alerts and updates', contentType: 'text', }, + { + page: 'advocacy', + section: 'header', + contentKey: 'title', + contentValue: 'Advocacy', + contentType: 'text', + }, + { + page: 'advocacy', + section: 'header', + contentKey: 'description', + contentValue: + 'We champion policies that strengthen aging services, support family caregivers, and protect the rights and dignity of older Tennesseans.', + contentType: 'richtext', + }, + { + page: 'advocacy', + section: 'header', + contentKey: 'image', + contentValue: '', + contentType: 'image', + }, + { + page: 'advocacy', + section: 'focus', + contentKey: 'title', + contentValue: 'Our Advocacy Focus', + contentType: 'text', + }, + { + page: 'advocacy', + section: 'focus', + contentKey: 'description', + contentValue: + 'TCBA works with coalition partners and state leaders to advance policy priorities that expand access to home and community-based services, invest in the direct care workforce, and improve systems for older adults and people with disabilities.', + contentType: 'richtext', + }, + { + page: 'advocacy', + section: 'focus', + contentKey: 'image', + contentValue: '', + contentType: 'image', + }, + { + page: 'advocacy', + section: 'cards', + contentKey: 'policy_title', + contentValue: 'Policy Development', + contentType: 'text', + }, + { + page: 'advocacy', + section: 'cards', + contentKey: 'coalition_title', + contentValue: 'Coalition Mobilization', + contentType: 'text', + }, + { + page: 'advocacy', + section: 'cards', + contentKey: 'public_title', + contentValue: 'Public Awareness', + contentType: 'text', + }, + { + page: 'advocacy', + section: 'cta', + contentKey: 'title', + contentValue: 'Join Our Advocacy Efforts', + contentType: 'text', + }, + { + page: 'advocacy', + section: 'cta', + contentKey: 'description', + contentValue: + 'Partner with TCBA to help shape policies and programs that improve the lives of older Tennesseans and their families.', + contentType: 'richtext', + }, + { + page: 'advocacy', + section: 'cta', + contentKey: 'button_text', + contentValue: 'Get Involved', + contentType: 'text', + }, { page: 'announcements', section: 'hero', diff --git a/frontend/src/components/AdminSidebar.tsx b/frontend/src/components/AdminSidebar.tsx index d6636af..36a8da2 100644 --- a/frontend/src/components/AdminSidebar.tsx +++ b/frontend/src/components/AdminSidebar.tsx @@ -144,6 +144,7 @@ const AdminSidebar = () => { const pageEditItems = [ { name: 'Home', path: '/admin/page-edit/home' }, { name: 'About', path: '/admin/page-edit/about' }, + { name: 'Advocacy', path: '/admin/page-edit/advocacy' }, { name: 'Announcements', path: '/admin/page-edit/announcements' }, { name: 'Blogs', path: '/admin/page-edit/blogs' }, { name: 'Get Involved', path: '/admin/page-edit/register' }, diff --git a/frontend/src/components/Navbar.tsx b/frontend/src/components/Navbar.tsx index 0cdb3f7..9a3a03e 100644 --- a/frontend/src/components/Navbar.tsx +++ b/frontend/src/components/Navbar.tsx @@ -38,6 +38,12 @@ const Navbar = () => { > About Us + + Advocacy + { > About Us + + Advocacy + { + const { getToken } = useAuth(); + const [content, setContent] = useState({}); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); + const [toast, setToast] = useState<{ + message: string; + type: 'success' | 'error' | 'info'; + } | null>(null); + const [showPreview, setShowPreview] = useState(false); + + useEffect(() => { + fetchContent(); + }, []); + + const fetchContent = async () => { + try { + setLoading(true); + const response = await fetch(`${API_BASE_URL}/api/page-content/advocacy`); + + if (!response.ok) { + throw new Error('Failed to fetch content'); + } + + const data = await response.json(); + setContent(data); + } catch (err: any) { + console.error('Error fetching content:', err); + setToast({ message: err.message || 'Failed to load content', type: 'error' }); + } finally { + setLoading(false); + } + }; + + const handleContentChange = (key: string, value: string, type?: string) => { + setContent({ + ...content, + [key]: { + ...content[key], + value, + type: type || content[key]?.type || 'text', + }, + }); + }; + + const handleSave = async () => { + try { + setSaving(true); + + const token = await getToken(); + const existingItems: any[] = []; + const newItems: any[] = []; + + Object.entries(content).forEach(([key, item]) => { + if (item.id) { + existingItems.push({ + id: item.id, + contentValue: item.value, + }); + } else if (item.value) { + const parts = key.split('_'); + const section = parts[0]; + const contentKey = parts.slice(1).join('_'); + + newItems.push({ + page: 'advocacy', + section, + contentKey, + contentValue: item.value, + contentType: item.type || 'text', + }); + } + }); + + for (const newItem of newItems) { + const createResponse = await fetch(`${API_BASE_URL}/api/page-content`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify(newItem), + }); + + if (!createResponse.ok) { + throw new Error(`Failed to create ${newItem.contentKey}`); + } + } + + if (existingItems.length > 0) { + const response = await fetch(`${API_BASE_URL}/api/page-content/bulk`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ updates: existingItems }), + }); + + if (!response.ok) { + throw new Error('Failed to save changes'); + } + } + + await fetchContent(); + setToast({ message: 'Changes saved successfully!', type: 'success' }); + } catch (err: any) { + console.error('Error saving:', err); + setToast({ message: err.message || 'Failed to save changes', type: 'error' }); + } finally { + setSaving(false); + } + }; + + const handleReset = () => { + fetchContent(); + }; + + if (loading) { + return ( +
+ +
+ +
+
+ ); + } + + return ( +
+ +
+
+
+

Edit Advocacy Page

+

Manage public-facing advocacy text and images

+
+ +
+

Header Section

+ handleContentChange('header_title', val, 'text')} + type='text' + placeholder='Advocacy' + /> + handleContentChange('header_description', val, 'richtext')} + type='richtext' + /> + handleContentChange('header_image', val, 'image')} + folder='pages/advocacy' + /> +
+ +
+

Advocacy Focus Section

+ handleContentChange('focus_title', val, 'text')} + type='text' + /> + handleContentChange('focus_description', val, 'richtext')} + type='richtext' + /> + handleContentChange('focus_image', val, 'image')} + folder='pages/advocacy' + /> +
+ +
+

Feature Cards

+ handleContentChange('cards_policy_title', val, 'text')} + type='text' + /> + handleContentChange('cards_coalition_title', val, 'text')} + type='text' + /> + handleContentChange('cards_public_title', val, 'text')} + type='text' + /> +
+ +
+

Call To Action

+ handleContentChange('cta_title', val, 'text')} + type='text' + /> + handleContentChange('cta_description', val, 'richtext')} + type='richtext' + /> + handleContentChange('cta_button_text', val, 'text')} + type='text' + /> +
+ +
+ + + +
+ + {showPreview && ( +
+
+
+

Preview: Advocacy Page

+ +
+
+ +
+
+
+ )} +
+
+ {toast && setToast(null)} />} +
+ ); +}; + +export default AdvocacyPageEdit; diff --git a/frontend/src/pages/AdvocacyPage/Advocacy.tsx b/frontend/src/pages/AdvocacyPage/Advocacy.tsx index 5d1d8d3..d15835c 100644 --- a/frontend/src/pages/AdvocacyPage/Advocacy.tsx +++ b/frontend/src/pages/AdvocacyPage/Advocacy.tsx @@ -1,5 +1,130 @@ -const AdvocacyPage = () => { - return
Advocacy page
; +import { useEffect } from 'react'; +import { Link } from 'react-router-dom'; +import { FaBullhorn, FaGavel, FaUsers } from 'react-icons/fa'; +import S3Image from '../../components/S3Image'; +import { usePageContent } from '../../hooks/queries/usePageContent'; + +interface PageContent { + [key: string]: { id: string; value: string; type: string }; +} + +interface AdvocacyPageProps { + previewContent?: PageContent; +} + +const AdvocacyPage = ({ previewContent }: AdvocacyPageProps = {}) => { + const { data: pageContent } = usePageContent('advocacy'); + const content = previewContent || pageContent || {}; + + useEffect(() => { + document.title = 'Advocacy - Tennessee Coalition For Better Aging'; + }, []); + + const headerImageSrc = content['header_image']?.value || ''; + const focusImageSrc = content['focus_image']?.value || ''; + + return ( +
+
+
+
+
+

+ {content['header_title']?.value || 'Advocacy'} +

+
+
+
+
+ {headerImageSrc && ( + + )} +
+
+
+ +
+
+
+

+ {content['focus_title']?.value || 'Our Advocacy Focus'} +

+
+
+
+ {focusImageSrc && ( + + )} +
+
+ +
+
+ +

+ {content['cards_policy_title']?.value || 'Policy Development'} +

+
+
+ +

+ {content['cards_coalition_title']?.value || 'Coalition Mobilization'} +

+
+
+ +

+ {content['cards_public_title']?.value || 'Public Awareness'} +

+
+
+
+ +
+
+

+ {content['cta_title']?.value || 'Join Our Advocacy Efforts'} +

+
+ + {content['cta_button_text']?.value || 'Get Involved'} + +
+
+
+ ); }; export default AdvocacyPage; diff --git a/frontend/src/routes/AppRoutes.tsx b/frontend/src/routes/AppRoutes.tsx index afa040f..b8df2a2 100644 --- a/frontend/src/routes/AppRoutes.tsx +++ b/frontend/src/routes/AppRoutes.tsx @@ -58,6 +58,9 @@ const AnnouncementsPageEdit = lazy( () => import('../pages/Admin/PageEditPages/AnnouncementsPage/AnnouncementsPageEdit') ); const BlogsPageEdit = lazy(() => import('../pages/Admin/PageEditPages/BlogsPage/BlogsPageEdit')); +const AdvocacyPageEdit = lazy( + () => import('../pages/Admin/PageEditPages/AdvocacyPage/AdvocacyPageEdit') +); const CustomEmail = lazy(() => import('../pages/Admin/CustomEmailPage/CustomEmail')); const AdminMessages = lazy(() => import('../pages/Admin/MessagesPage/Messages')); const Tags = lazy(() => import('../pages/Admin/TagsPage/Tags')); @@ -444,6 +447,16 @@ const AppRoutes = () => { } /> + + }> + + + + } + />