diff --git a/src/website2/next.config.mjs b/src/website2/next.config.mjs index 60418cc7cc..8a23ebaa96 100644 --- a/src/website2/next.config.mjs +++ b/src/website2/next.config.mjs @@ -46,6 +46,21 @@ const nextConfig = { destination: '/clean-air-forum/about', permanent: true, }, + { + source: '/clean-air-network', + destination: '/clean-air-network/about', + permanent: true, + }, + { + source: '/clean-air/about', + destination: '/clean-air-network/about', + permanent: true, + }, + { + source: '/clean-air', + destination: '/clean-air-network/about', + permanent: true, + }, ]; }, }; diff --git a/src/website2/public/Logo.png b/src/website2/public/Logo.png new file mode 100644 index 0000000000..bf64b5cbd5 Binary files /dev/null and b/src/website2/public/Logo.png differ diff --git a/src/website2/public/apple-icon.png b/src/website2/public/apple-icon.png new file mode 100644 index 0000000000..6af6fa1c8e Binary files /dev/null and b/src/website2/public/apple-icon.png differ diff --git a/src/website2/public/assets/icons/Icon1.tsx b/src/website2/public/assets/icons/Icon1.tsx index 76615bec02..be291f7323 100644 --- a/src/website2/public/assets/icons/Icon1.tsx +++ b/src/website2/public/assets/icons/Icon1.tsx @@ -1,4 +1,4 @@ -// components/icons/Icon1.tsx + import React from 'react'; interface IconProps { diff --git a/src/website2/public/assets/icons/Icon2.tsx b/src/website2/public/assets/icons/Icon2.tsx index dbd81a690d..3e6d88d384 100644 --- a/src/website2/public/assets/icons/Icon2.tsx +++ b/src/website2/public/assets/icons/Icon2.tsx @@ -1,4 +1,4 @@ -// components/icons/Icon2.tsx + import React from 'react'; interface IconProps { diff --git a/src/website2/public/assets/icons/Icon3.tsx b/src/website2/public/assets/icons/Icon3.tsx index 5812824619..71fa508107 100644 --- a/src/website2/public/assets/icons/Icon3.tsx +++ b/src/website2/public/assets/icons/Icon3.tsx @@ -1,4 +1,4 @@ -// components/icons/Icon3.tsx + import React from 'react'; interface IconProps { diff --git a/src/website2/public/assets/icons/Icon4.tsx b/src/website2/public/assets/icons/Icon4.tsx index 40c3fa8ba1..46f132ef6b 100644 --- a/src/website2/public/assets/icons/Icon4.tsx +++ b/src/website2/public/assets/icons/Icon4.tsx @@ -1,4 +1,4 @@ -// components/icons/Icon4.tsx + import React from 'react'; interface IconProps { diff --git a/src/website2/public/assets/icons/Icon5.tsx b/src/website2/public/assets/icons/Icon5.tsx index 7da0d0487b..5483e5940f 100644 --- a/src/website2/public/assets/icons/Icon5.tsx +++ b/src/website2/public/assets/icons/Icon5.tsx @@ -1,4 +1,4 @@ -// components/icons/Icon5.tsx + import React from 'react'; interface IconProps { diff --git a/src/website2/public/assets/icons/Icon6.tsx b/src/website2/public/assets/icons/Icon6.tsx index 3b62e3f341..26911b5ca2 100644 --- a/src/website2/public/assets/icons/Icon6.tsx +++ b/src/website2/public/assets/icons/Icon6.tsx @@ -1,4 +1,4 @@ -// components/icons/Icon6.tsx + import React from 'react'; interface IconProps { diff --git a/src/website2/public/assets/icons/Icon7.tsx b/src/website2/public/assets/icons/Icon7.tsx index 38af9fcba9..a8ebbde45c 100644 --- a/src/website2/public/assets/icons/Icon7.tsx +++ b/src/website2/public/assets/icons/Icon7.tsx @@ -1,4 +1,4 @@ -// components/icons/Icon6.tsx + import React from 'react'; interface IconProps { diff --git a/src/website2/public/assets/icons/Icon8.tsx b/src/website2/public/assets/icons/Icon8.tsx index 272fbb43a8..e7016fc1e5 100644 --- a/src/website2/public/assets/icons/Icon8.tsx +++ b/src/website2/public/assets/icons/Icon8.tsx @@ -1,4 +1,4 @@ -// components/icons/Icon6.tsx + import React from 'react'; interface IconProps { diff --git a/src/website2/public/favicon.ico b/src/website2/public/favicon.ico new file mode 100644 index 0000000000..80f940cae3 Binary files /dev/null and b/src/website2/public/favicon.ico differ diff --git a/src/website2/public/icon.png b/src/website2/public/icon.png new file mode 100644 index 0000000000..fd9cd302d8 Binary files /dev/null and b/src/website2/public/icon.png differ diff --git a/src/website2/public/icon.svg b/src/website2/public/icon.svg new file mode 100644 index 0000000000..e925bedccd --- /dev/null +++ b/src/website2/public/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/website2/public/manifest.json b/src/website2/public/manifest.json new file mode 100644 index 0000000000..859342a095 --- /dev/null +++ b/src/website2/public/manifest.json @@ -0,0 +1,21 @@ +{ + "name": "AirQo Website", + "short_name": "AirQo", + "icons": [ + { + "src": "/web-app-manifest-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "/web-app-manifest-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} \ No newline at end of file diff --git a/src/website2/public/web-app-manifest-192x192.png b/src/website2/public/web-app-manifest-192x192.png new file mode 100644 index 0000000000..1f574466b5 Binary files /dev/null and b/src/website2/public/web-app-manifest-192x192.png differ diff --git a/src/website2/public/web-app-manifest-512x512.png b/src/website2/public/web-app-manifest-512x512.png new file mode 100644 index 0000000000..9a57e19709 Binary files /dev/null and b/src/website2/public/web-app-manifest-512x512.png differ diff --git a/src/website2/src/app/clean-air-forum/about/page.tsx b/src/website2/src/app/clean-air-forum/about/page.tsx index 20109a061c..28c7f03e7e 100644 --- a/src/website2/src/app/clean-air-forum/about/page.tsx +++ b/src/website2/src/app/clean-air-forum/about/page.tsx @@ -1,8 +1,14 @@ -import React from 'react'; +import { Metadata } from 'next'; -import AboutPage from '@/views/Forum/AboutPage'; +import AboutPage from '@/views/cleanAirForum/about/AboutPage'; -const page = () => { +export const metadata: Metadata = { + title: 'About Clean Air Forum | AirQo', + description: + 'Discover the Clean Air Forum – learn about our mission, vision, and how we foster collaboration to advance clean air solutions and improve air quality.', +}; + +const Page = () => { return (
@@ -10,4 +16,4 @@ const page = () => { ); }; -export default page; +export default Page; diff --git a/src/website2/src/app/clean-air-forum/glossary/page.tsx b/src/website2/src/app/clean-air-forum/glossary/page.tsx index d099dd930e..7e71c40c03 100644 --- a/src/website2/src/app/clean-air-forum/glossary/page.tsx +++ b/src/website2/src/app/clean-air-forum/glossary/page.tsx @@ -1,115 +1,15 @@ -'use client'; +import { Metadata } from 'next'; -import DOMPurify from 'dompurify'; -import Link from 'next/link'; -import React from 'react'; +import GlossaryPage from '@/views/cleanAirForum/glossary/GlossaryPage'; -import Loading from '@/components/loading'; -import { Divider } from '@/components/ui'; -import { NoData } from '@/components/ui'; -import { useForumData } from '@/context/ForumDataContext'; -import { ForumEvent } from '@/types/forum'; -import { isValidGlossaryContent } from '@/utils/glossaryValidator'; -import { renderContent } from '@/utils/quillUtils'; -import SectionDisplay from '@/views/Forum/SectionDisplay'; - -const GlossaryPage: React.FC = () => { - // Access data from the context. - const { selectedEvent, eventTitles } = useForumData(); - - // If either is not available, show a loading state. - if (!selectedEvent || !eventTitles) { - return ; - } - - // Extract the events list from eventTitles. - // If eventTitles is an array, use it directly; otherwise, assume it's a ForumTitlesResponse. - const eventsList: ForumEvent[] = Array.isArray(eventTitles) - ? eventTitles - : eventTitles.forum_events; - - if (eventsList.length === 0) { - return ; - } - - // Render the main glossary content using the selected event. - const glossaryHTML = renderContent(selectedEvent.glossary_details); - const showGlossaryMain = isValidGlossaryContent(glossaryHTML); - - const glossarySections = selectedEvent.sections?.filter((section: any) => { - if (!section.pages.includes('glossary')) return false; - const html = renderContent(section.content); - return html.trim().length > 0; - }); - - return ( -
- - - {/* Clean Air Forum Events Section (Sidebar) */} -
- {/* Left column: Heading */} -
-

- Clean Air Forum Events -

-
- {/* Right column: List of event links */} -
-
    - {eventsList.map((event) => { - // Use the unique_title directly in the link. - const href = `/clean-air-forum/about?slug=${encodeURIComponent( - event.unique_title, - )}`; - return ( -
  • - - {event.title} - -
  • - ); - })} -
-
-
- - {/* Clean Air Glossary Section */} - {showGlossaryMain && ( - <> - -
- {/* Left column: Heading */} -
-

- Clean Air Glossary -

-
- {/* Right column: Glossary content */} -
-
- - )} +export const metadata: Metadata = { + title: 'Glossary | Clean Air Forum', + description: + 'Explore our glossary of key terms and pollutant types used at Clean Air Forum, providing clear definitions to help you better understand air quality management.', +}; - {/* Additional Glossary Sections (if any) */} - {glossarySections && glossarySections.length > 0 && ( - <> - {glossarySections.map((section: any) => ( - - ))} - - )} -
- ); +const Page = () => { + return ; }; -export default GlossaryPage; +export default Page; diff --git a/src/website2/src/app/clean-air-forum/layout.tsx b/src/website2/src/app/clean-air-forum/layout.tsx index 260174194f..d727cb6bc7 100644 --- a/src/website2/src/app/clean-air-forum/layout.tsx +++ b/src/website2/src/app/clean-air-forum/layout.tsx @@ -1,4 +1,3 @@ -// components/layouts/CleanAirLayout.tsx 'use client'; import { useSearchParams } from 'next/navigation'; @@ -12,7 +11,7 @@ import { NoData } from '@/components/ui'; import mainConfig from '@/configs/mainConfigs'; import { ForumDataProvider } from '@/context/ForumDataContext'; import { useForumEventDetails, useForumEventTitles } from '@/hooks/useApiHooks'; -import BannerSection from '@/views/Forum/BannerSection'; +import BannerSection from '@/views/cleanAirForum/BannerSection'; type CleanAirLayoutProps = { children: ReactNode; diff --git a/src/website2/src/app/clean-air-forum/logistics/page.tsx b/src/website2/src/app/clean-air-forum/logistics/page.tsx index abbadd07df..21bc43015b 100644 --- a/src/website2/src/app/clean-air-forum/logistics/page.tsx +++ b/src/website2/src/app/clean-air-forum/logistics/page.tsx @@ -1,96 +1,15 @@ -'use client'; +import { Metadata } from 'next'; -import DOMPurify from 'dompurify'; -import React from 'react'; +import LogisticsPage from '@/views/cleanAirForum/logistics/LogisticsPage'; -import Loading from '@/components/loading'; -import { Divider } from '@/components/ui'; -import { useForumData } from '@/context/ForumDataContext'; -import { isValidHTMLContent } from '@/utils/htmlValidator'; -import { renderContent } from '@/utils/quillUtils'; -import SectionDisplay from '@/views/Forum/SectionDisplay'; - -const LogisticsPage: React.FC = () => { - // Destructure the selected event from the context. - const { selectedEvent } = useForumData(); - - // If selectedEvent is not available, show a loading state. - if (!selectedEvent) { - return ; - } - - // Render static content from the event model. - const vaccinationHTML = renderContent( - selectedEvent.travel_logistics_vaccination_details, - ); - const visaHTML = renderContent(selectedEvent.travel_logistics_visa_details); - - const showVaccination = isValidHTMLContent(vaccinationHTML); - const showVisa = isValidHTMLContent(visaHTML); - - // Filter extra sections assigned to the "logistics" page. - const logisticsSections = selectedEvent.sections?.filter( - (section: any) => - section.pages.includes('logistics') && - isValidHTMLContent(renderContent(section.content)), - ); - - return ( -
- {/* Render Vaccination Section if content exists */} - {showVaccination && ( - <> - -
-
-
-

- Vaccination -

-
-
-
-
- - )} - - {/* Render Visa Invitation Letter Section if content exists */} - {showVisa && ( - <> - -
-
-
-

- Visa invitation letter -

-
-
-
-
- - )} +export const metadata: Metadata = { + title: 'Logistics | Clean Air Forum', + description: + 'Get all the essential logistics information for attending the Clean Air Forum, including travel, accommodation, event schedules, and practical tips for participants.', +}; - {/* Render additional Logistics Sections, if any */} - {logisticsSections && logisticsSections.length > 0 && ( - <> - {logisticsSections.map((section: any) => ( - - ))} - - )} -
- ); +const Page = () => { + return ; }; -export default LogisticsPage; +export default Page; diff --git a/src/website2/src/app/clean-air-forum/partners/page.tsx b/src/website2/src/app/clean-air-forum/partners/page.tsx index ab8ecb1556..8aee8d68e9 100644 --- a/src/website2/src/app/clean-air-forum/partners/page.tsx +++ b/src/website2/src/app/clean-air-forum/partners/page.tsx @@ -1,148 +1,15 @@ -'use client'; +import { Metadata } from 'next'; -import DOMPurify from 'dompurify'; -import React from 'react'; +import PartnersPage from '@/views/cleanAirForum/partners/PartnersPage'; -import { Divider } from '@/components/ui'; -import { useForumData } from '@/context/ForumDataContext'; -import { isValidHTMLContent } from '@/utils/htmlValidator'; -import { renderContent } from '@/utils/quillUtils'; -import PaginatedSection from '@/views/cleanairforum/PaginatedSection'; -import SectionDisplay from '@/views/Forum/SectionDisplay'; - -const PartnersPage: React.FC = () => { - const { selectedEvent } = useForumData(); - if (!selectedEvent) return null; - - const conveningPartners = selectedEvent.partners - ?.filter((partner: any) => partner.category === 'Co-Convening Partner') - .map((partner: any) => ({ - id: partner.id, - logoUrl: partner.partner_logo_url, - })); - - const hostPartners = selectedEvent.partners - ?.filter((partner: any) => partner.category === 'Host Partner') - .map((partner: any) => ({ - id: partner.id, - logoUrl: partner.partner_logo_url, - })); - - const programPartners = selectedEvent.partners - ?.filter((partner: any) => partner.category === 'Program Partner') - .map((partner: any) => ({ - id: partner.id, - logoUrl: partner.partner_logo_url, - })); - - const fundingPartners = selectedEvent.partners - ?.filter((partner: any) => partner.category === 'Funding Partner') - .map((partner: any) => ({ - id: partner.id, - logoUrl: partner.partner_logo_url, - })); - - const mainPartnersHTML = renderContent(selectedEvent.partners_text_section); - const showMainPartners = isValidHTMLContent(mainPartnersHTML); - - const partnersSections = selectedEvent.sections?.filter((section: any) => { - if (!section.pages.includes('partners')) return false; - const sectionHTML = renderContent(section.content); - return isValidHTMLContent(sectionHTML); - }); - - return ( -
- {showMainPartners && ( -
-

Partners

-
-
- )} - - {partnersSections && partnersSections.length > 0 && ( - <> - {partnersSections.map((section: any) => ( - - ))} - - )} - - {conveningPartners && conveningPartners.length > 0 && ( - <> - -
-
-

- Convening partners and Collaborators -

-
- -
- - )} - - {hostPartners && hostPartners.length > 0 && ( - <> - -
-
-

- Host partners -

-
- -
- - )} - - {programPartners && programPartners.length > 0 && ( - <> - -
-
-

Exhibitors

-
- -
- - )} +export const metadata: Metadata = { + title: 'Partners | Clean Air Forum | AirQo', + description: + 'Discover the partners collaborating with Clean Air Forum to advance clean air solutions and improve air quality through innovation and community engagement.', +}; - {fundingPartners && fundingPartners.length > 0 && ( - <> - -
-
-

- Funding Partners and Sponsors -

-
- -
- - )} -
- ); +const Page = () => { + return ; }; -export default PartnersPage; +export default Page; diff --git a/src/website2/src/app/clean-air-forum/program-committee/page.tsx b/src/website2/src/app/clean-air-forum/program-committee/page.tsx index ab3f7d2b3d..c1dd8cfb3f 100644 --- a/src/website2/src/app/clean-air-forum/program-committee/page.tsx +++ b/src/website2/src/app/clean-air-forum/program-committee/page.tsx @@ -1,121 +1,15 @@ -/* eslint-disable react-hooks/exhaustive-deps */ -'use client'; +import { Metadata } from 'next'; -import DOMPurify from 'dompurify'; -import React, { useMemo, useState } from 'react'; +import CommitteePage from '@/views/cleanAirForum/program-committee/CommitteePage'; -import { Divider, MemberCard, Pagination } from '@/components/ui/'; -import { useForumData } from '@/context/ForumDataContext'; -import { isValidHTMLContent } from '@/utils/htmlValidator'; -import { renderContent } from '@/utils/quillUtils'; -import SectionDisplay from '@/views/Forum/SectionDisplay'; - -const CommitteePage: React.FC = () => { - // Always call useForumData to get the selectedEvent. - const { selectedEvent } = useForumData(); - - // Instead of conditionally calling hooks based on selectedEvent, - // extract fallback values unconditionally. - const persons = selectedEvent?.persons || []; - const sections = selectedEvent?.sections || []; - const committeeText = selectedEvent?.committee_text_section || ''; - - // Local state for pagination. - const [currentPage, setCurrentPage] = useState(1); - const membersPerPage = 6; - - // Memoize committee members using a fallback empty array. - const committeeMembers = useMemo(() => { - return persons.filter( - (person: any) => - person.category === 'Committee Member' || - person.category === 'Committee Member and Key Note Speaker' || - person.category === 'Speaker and Committee Member', - ); - }, [persons]); - - // Calculate total pages. - const totalPages = useMemo(() => { - return Math.ceil(committeeMembers.length / membersPerPage); - }, [committeeMembers, membersPerPage]); - - // Get members for the current page. - const displayedMembers = useMemo(() => { - const startIdx = (currentPage - 1) * membersPerPage; - return committeeMembers.slice(startIdx, startIdx + membersPerPage); - }, [currentPage, committeeMembers, membersPerPage]); - - // Render main committee text. - const committeeHTML = renderContent(committeeText); - const showCommitteeMain = isValidHTMLContent(committeeHTML); - - // Filter extra sections assigned to the "committee" page. - const committeeSections = useMemo(() => { - return sections.filter((section: any) => { - if (!section.pages.includes('committee')) return false; - const sectionHTML = renderContent(section.content); - return isValidHTMLContent(sectionHTML); - }); - }, [sections]); - - const handlePageChange = (newPage: number) => setCurrentPage(newPage); - - // If selectedEvent is still not available, you might render a loading indicator. - if (!selectedEvent) { - return null; - } - - return ( -
- - - {/* Program Committee Text Section */} -
- {showCommitteeMain && ( - <> -

Program Committee

-
- - )} -
- - {/* Extra Committee Sections using SectionDisplay */} - {committeeSections.length > 0 && ( - <> - {committeeSections.map((section: any) => ( - - ))} - - )} - - {/* Member Cards Grid */} -
- {displayedMembers.map((person: any) => ( - - ))} -
+export const metadata: Metadata = { + title: 'Program Committee | Clean Air Forum | AirQo', + description: + 'Meet the expert program committee of Clean Air Forum. Learn about the thought leaders and industry experts driving innovative strategies for better air quality.', +}; - {/* Pagination Component */} - {totalPages > 1 && ( -
- -
- )} -
- ); +const Page = () => { + return ; }; -export default CommitteePage; +export default Page; diff --git a/src/website2/src/app/clean-air-forum/resources/page.tsx b/src/website2/src/app/clean-air-forum/resources/page.tsx index a107048955..2a4a1b5831 100644 --- a/src/website2/src/app/clean-air-forum/resources/page.tsx +++ b/src/website2/src/app/clean-air-forum/resources/page.tsx @@ -1,148 +1,15 @@ -'use client'; +import { Metadata } from 'next'; -import React, { useState } from 'react'; -import { FaChevronDown, FaChevronUp, FaFilePdf } from 'react-icons/fa'; +import ResourcesPage from '@/views/cleanAirForum/resources/ResourcesPage'; -import { Divider } from '@/components/ui'; -import { useForumData } from '@/context/ForumDataContext'; - -const getFileNameFromUrl = (url: string | null | undefined): string | null => { - if (!url || typeof url !== 'string') { - console.error('Invalid URL:', url); - return null; - } - const segments = url.split('/'); - return segments.pop() || null; -}; - -const AccordionItem = ({ session, isOpen, toggleAccordion }: any) => { - return ( -
-
-

- {session.session_title} -

- {isOpen ? : } -
- {isOpen && ( -
- {session?.resource_files?.map((file: any) => ( -
-
-
-

- {file.resource_summary} -

- - - {getFileNameFromUrl(file.file_url)} - -
-
-
- ))} -
- )} -
- ); +export const metadata: Metadata = { + title: 'Resources | Clean Air Forum | AirQo', + description: + 'Access comprehensive resources, presentations, and documents from Clean Air Forum to stay informed about the latest trends and innovations in air quality management.', }; -const ResourcesPage: React.FC = () => { - const { selectedEvent } = useForumData(); - const [openAccordions, setOpenAccordions] = useState<{ - [resourceIndex: number]: { [sessionIndex: number]: boolean }; - }>({}); - const [allExpanded, setAllExpanded] = useState(false); - - if (!selectedEvent) { - return null; - } - - const handleToggleAccordion = ( - resourceIndex: number, - sessionIndex: number, - ) => { - setOpenAccordions((prevState) => ({ - ...prevState, - [resourceIndex]: { - ...prevState[resourceIndex], - [sessionIndex]: !prevState[resourceIndex]?.[sessionIndex], - }, - })); - }; - - const handleExpandAll = () => { - setAllExpanded(true); - const expandedAccordions: any = {}; - selectedEvent.forum_resources?.forEach( - (resource: any, resourceIndex: number) => { - expandedAccordions[resourceIndex] = {}; - resource.resource_sessions?.forEach((_: any, sessionIndex: number) => { - expandedAccordions[resourceIndex][sessionIndex] = true; - }); - }, - ); - setOpenAccordions(expandedAccordions); - }; - - const handleCollapseAll = () => { - setAllExpanded(false); - setOpenAccordions({}); - }; - - return ( -
-
- - -
- - {selectedEvent.forum_resources?.map( - (resource: any, resourceIndex: number) => ( -
-

- {resource.resource_title} -

- {resource.resource_sessions?.map( - (session: any, sessionIndex: number) => ( - - handleToggleAccordion(resourceIndex, sessionIndex) - } - /> - ), - )} - -
- ), - )} -
- ); +const Page = () => { + return ; }; -export default ResourcesPage; +export default Page; diff --git a/src/website2/src/app/clean-air-forum/sessions/page.tsx b/src/website2/src/app/clean-air-forum/sessions/page.tsx index 010ab091cf..24399efeff 100644 --- a/src/website2/src/app/clean-air-forum/sessions/page.tsx +++ b/src/website2/src/app/clean-air-forum/sessions/page.tsx @@ -1,158 +1,15 @@ -'use client'; +import { Metadata } from 'next'; -import { format } from 'date-fns'; -import DOMPurify from 'dompurify'; -import React, { useState } from 'react'; -import { FaChevronDown, FaChevronUp } from 'react-icons/fa'; +import ProgramsPage from '@/views/cleanAirForum/sessions-programs/ProgramsPage'; -import { Divider } from '@/components/ui'; -import { useForumData } from '@/context/ForumDataContext'; -import { isValidHTMLContent } from '@/utils/htmlValidator'; -import { renderContent } from '@/utils/quillUtils'; -import SectionDisplay from '@/views/Forum/SectionDisplay'; - -interface AccordionItemProps { - title: string; - subText: string; - sessions: any[]; - isOpen: boolean; - onToggle: () => void; -} - -const AccordionItem: React.FC = ({ - title, - subText, - sessions, - isOpen, - onToggle, -}) => { - const formatTime = (time: string) => { - try { - return format(new Date(`1970-01-01T${time}Z`), 'p'); - } catch (error) { - console.error(error); - return time; - } - }; - - return ( -
-
-
-

{title}

-
-
- {isOpen ? : } -
- {isOpen && ( -
- {sessions?.map((item: any, index: number) => ( -
- -
-
- {formatTime(item.start_time)} -
-
-

{item.session_title}

-
-
-
-
- ))} -
- )} -
- ); +export const metadata: Metadata = { + title: 'Sessions & Programs | Clean Air Forum | AirQo', + description: + 'Explore the schedule and detailed program information for Clean Air Forum. Find out about the sessions, topics, and speakers shaping the future of air quality management.', }; -const ProgramsPage: React.FC = () => { - const { selectedEvent } = useForumData(); - const [openAccordion, setOpenAccordion] = useState(null); - - if (!selectedEvent) { - return null; - } - - const scheduleHTML = renderContent(selectedEvent.schedule_details); - const showSchedule = isValidHTMLContent(scheduleHTML); - - const registrationHTML = renderContent(selectedEvent.registration_details); - const showRegistration = isValidHTMLContent(registrationHTML); - - const sessionSections = selectedEvent.sections?.filter((section: any) => { - if (!section.pages.includes('session')) return false; - const sectionHTML = renderContent(section.content); - return isValidHTMLContent(sectionHTML); - }); - - const handleToggle = (id: string) => { - setOpenAccordion(openAccordion === id ? null : id); - }; - - return ( -
- {showSchedule && ( -
-

Schedule

-
-
- )} - - {sessionSections && sessionSections.length > 0 && ( - <> - {sessionSections.map((section: any) => ( - - ))} - - )} - - <> - {selectedEvent.programs?.map((program: any) => ( - handleToggle(program.id)} - /> - ))} - - - {showRegistration && ( -
- -
-
-

Registration

-
-
-
-
- )} -
- ); +const Page = () => { + return ; }; -export default ProgramsPage; +export default Page; diff --git a/src/website2/src/app/clean-air-forum/speakers/page.tsx b/src/website2/src/app/clean-air-forum/speakers/page.tsx index af98135d6f..8f4891cb02 100644 --- a/src/website2/src/app/clean-air-forum/speakers/page.tsx +++ b/src/website2/src/app/clean-air-forum/speakers/page.tsx @@ -1,147 +1,15 @@ -'use client'; +import { Metadata } from 'next'; -import DOMPurify from 'dompurify'; -import React, { useState } from 'react'; +import SpeakersPage from '@/views/cleanAirForum/speakers/SpeakersPage'; -import { Divider, MemberCard, Pagination } from '@/components/ui/'; -import { useForumData } from '@/context/ForumDataContext'; -import { isValidHTMLContent } from '@/utils/htmlValidator'; -import { renderContent } from '@/utils/quillUtils'; -import SectionDisplay from '@/views/Forum/SectionDisplay'; - -const SpeakersPage: React.FC = () => { - // Now we use the selectedEvent from context - const { selectedEvent } = useForumData(); - const membersPerPage = 6; - const [currentKeyNotePage, setCurrentKeyNotePage] = useState(1); - const [currentSpeakersPage, setCurrentSpeakersPage] = useState(1); - - if (!selectedEvent) { - return null; - } - - // Filter keynote speakers and speakers from selectedEvent.persons. - // (Adjust your filtering logic as needed.) - const keyNoteSpeakers = - selectedEvent.persons?.filter( - (person: any) => - person.category === 'Key Note Speaker' || - person.category === 'Committee Member and Key Note Speaker', - ) || []; - const speakers = - selectedEvent.persons?.filter( - (person: any) => - person.category === 'Speaker' || - person.category === 'Speaker and Committee Member', - ) || []; - - // Pagination calculations for Keynote Speakers. - const totalKeyNotePages = Math.ceil(keyNoteSpeakers.length / membersPerPage); - const startKeyNoteIdx = (currentKeyNotePage - 1) * membersPerPage; - const displayedKeyNoteSpeakers = keyNoteSpeakers.slice( - startKeyNoteIdx, - startKeyNoteIdx + membersPerPage, - ); - - // Pagination calculations for Speakers. - const totalSpeakersPages = Math.ceil(speakers.length / membersPerPage); - const startSpeakersIdx = (currentSpeakersPage - 1) * membersPerPage; - const displayedSpeakers = speakers.slice( - startSpeakersIdx, - startSpeakersIdx + membersPerPage, - ); - - // Handlers for page changes. - const handleKeyNotePageChange = (newPage: number) => - setCurrentKeyNotePage(newPage); - const handleSpeakersPageChange = (newPage: number) => - setCurrentSpeakersPage(newPage); - - // Validate the main speakers text section. - const mainSpeakersHTML = renderContent(selectedEvent.speakers_text_section); - const showMainSpeakers = isValidHTMLContent(mainSpeakersHTML); - - // Filter extra sections assigned to the "speakers" page. - const speakersExtraSections = selectedEvent.sections?.filter( - (section: any) => { - if (!section.pages.includes('speakers')) return false; - const sectionHTML = renderContent(section.content); - return isValidHTMLContent(sectionHTML); - }, - ); - - return ( -
- - - {/* Speakers Text Section */} - {showMainSpeakers && ( -
-
-
- )} - - {/* Keynote Speakers Section */} -

Keynote Speakers

- -
- {displayedKeyNoteSpeakers.map((person: any) => ( - - ))} -
- {totalKeyNotePages > 1 && ( -
- -
- )} - - - - {/* Speakers Section */} -

Speakers

-
- {displayedSpeakers.map((person: any) => ( - - ))} -
- {totalSpeakersPages > 1 && ( -
- -
- )} +export const metadata: Metadata = { + title: 'Speakers | Clean Air Forum | AirQo', + description: + 'Meet the distinguished speakers at Clean Air Forum. Learn from industry leaders and experts who are driving change in air quality and environmental management.', +}; - {/* Extra Speakers Sections */} - {speakersExtraSections && speakersExtraSections.length > 0 && ( - <> - {speakersExtraSections.map((section: any) => ( - - ))} - - )} -
- ); +const Page = () => { + return ; }; -export default SpeakersPage; +export default Page; diff --git a/src/website2/src/app/clean-air-forum/sponsorships/page.tsx b/src/website2/src/app/clean-air-forum/sponsorships/page.tsx index 28e2a89534..600ae4b8da 100644 --- a/src/website2/src/app/clean-air-forum/sponsorships/page.tsx +++ b/src/website2/src/app/clean-air-forum/sponsorships/page.tsx @@ -1,82 +1,15 @@ -'use client'; +import { Metadata } from 'next'; -import DOMPurify from 'dompurify'; -import React from 'react'; +import SponsorshipPage from '@/views/cleanAirForum/sponsorship/SponsorshipPage'; -import { Divider } from '@/components/ui'; -import { useForumData } from '@/context/ForumDataContext'; -import { renderContent } from '@/utils/quillUtils'; -import PaginatedSection from '@/views/cleanairforum/PaginatedSection'; -import SectionDisplay from '@/views/Forum/SectionDisplay'; - -const SponsorshipPage: React.FC = () => { - const { selectedEvent } = useForumData(); - if (!selectedEvent) return null; - - const sponsorPartner = selectedEvent.partners - ?.filter((partner: any) => partner.category === 'Sponsor Partner') - .map((partner: any) => ({ - id: partner.id, - logoUrl: partner.partner_logo_url, - })); - - const sponsorshipSections = selectedEvent.sections?.filter((section: any) => { - if (!section.pages.includes('sponsorships')) return false; - const html = renderContent(section.content); - return html.trim().length > 0; - }); - - const mainSponsorshipHTML = renderContent( - selectedEvent.sponsorship_opportunities_partners, - ); - const showMainSponsorship = mainSponsorshipHTML.trim().length > 0; - - return ( -
- {showMainSponsorship && ( -
- -
-

Sponsorship opportunities

- -
-
-
- )} - - {sponsorshipSections && sponsorshipSections.length > 0 && ( - <> - {sponsorshipSections.map((section: any) => ( - - ))} - - )} +export const metadata: Metadata = { + title: 'Sponsorship | Clean Air Forum | AirQo', + description: + 'Discover sponsorship opportunities for Clean Air Forum. Learn about tailored sponsorship packages designed to support innovation and community engagement in air quality management.', +}; - {sponsorPartner && sponsorPartner.length > 0 && ( - <> - -
-
-
-

- Sponsors -

-
- -
-
- - )} -
- ); +const Page = () => { + return ; }; -export default SponsorshipPage; +export default Page; diff --git a/src/website2/src/app/clean-air-network/about/page.tsx b/src/website2/src/app/clean-air-network/about/page.tsx new file mode 100644 index 0000000000..b97864b717 --- /dev/null +++ b/src/website2/src/app/clean-air-network/about/page.tsx @@ -0,0 +1,19 @@ +import { Metadata } from 'next'; + +import CleanAirPage from '@/views/cleanAirNetwork/about/CleanAirPage'; + +export const metadata: Metadata = { + title: 'About Clean Air Network | AirQo', + description: + 'Discover Clean Air Network – our mission, vision, and the collaborative efforts we undertake to drive innovation and improve air quality through community and stakeholder engagement.', +}; + +const Page = () => { + return ( +
+ +
+ ); +}; + +export default Page; diff --git a/src/website2/src/app/clean-air-network/events/[id]/page.tsx b/src/website2/src/app/clean-air-network/events/[id]/page.tsx index 6b46e523ca..a30542cae5 100644 --- a/src/website2/src/app/clean-air-network/events/[id]/page.tsx +++ b/src/website2/src/app/clean-air-network/events/[id]/page.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import SingleEvent from '@/views/cleanairforum/events/SingleEvent'; +import SingleEvent from '@/views/cleanAirNetwork/events/SingleEvent'; const page = ({ params }: { params: any }) => { return ( diff --git a/src/website2/src/app/clean-air-network/events/page.tsx b/src/website2/src/app/clean-air-network/events/page.tsx index 1033d25b12..e2b83ddc5e 100644 --- a/src/website2/src/app/clean-air-network/events/page.tsx +++ b/src/website2/src/app/clean-air-network/events/page.tsx @@ -1,6 +1,14 @@ -import EventsPage from '@/views/cleanairforum/events/EventsPage'; +import { Metadata } from 'next'; -const page = () => { +import EventsPage from '@/views/cleanAirNetwork/events/EventsPage'; + +export const metadata: Metadata = { + title: 'Events | Clean Air Network | AirQo', + description: + 'Explore upcoming and past events hosted by Clean Air Network. Stay informed about conferences, webinars, and networking opportunities designed to advance clean air initiatives.', +}; + +const Page = () => { return (
@@ -8,4 +16,4 @@ const page = () => { ); }; -export default page; +export default Page; diff --git a/src/website2/src/app/clean-air-network/membership/page.tsx b/src/website2/src/app/clean-air-network/membership/page.tsx index ee1803b08d..f57f069738 100644 --- a/src/website2/src/app/clean-air-network/membership/page.tsx +++ b/src/website2/src/app/clean-air-network/membership/page.tsx @@ -1,6 +1,14 @@ -import MemberPage from '@/views/cleanairforum/membership/MemberPage'; +import { Metadata } from 'next'; -const page = () => { +import MemberPage from '@/views/cleanAirNetwork/membership/MemberPage'; + +export const metadata: Metadata = { + title: 'Membership | Clean Air Network | AirQo', + description: + 'Join Clean Air Network – connect with professionals dedicated to advancing air quality. Learn about membership benefits, exclusive resources, and opportunities for collaboration.', +}; + +const Page = () => { return (
@@ -8,4 +16,4 @@ const page = () => { ); }; -export default page; +export default Page; diff --git a/src/website2/src/app/clean-air-network/page.tsx b/src/website2/src/app/clean-air-network/page.tsx deleted file mode 100644 index b7a801b61d..0000000000 --- a/src/website2/src/app/clean-air-network/page.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; - -import CleanAirPage from './CleanAirPage'; - -const page = () => { - return ( -
- -
- ); -}; - -export default page; diff --git a/src/website2/src/app/clean-air-network/resources/page.tsx b/src/website2/src/app/clean-air-network/resources/page.tsx index 805d6b0e40..985d5abf52 100644 --- a/src/website2/src/app/clean-air-network/resources/page.tsx +++ b/src/website2/src/app/clean-air-network/resources/page.tsx @@ -1,6 +1,14 @@ -import ResourcePage from '@/views/cleanairforum/resources/ResourcePage'; +import { Metadata } from 'next'; -const page = () => { +import ResourcePage from '@/views/cleanAirNetwork/resources/ResourcePage'; + +export const metadata: Metadata = { + title: 'Resources | Clean Air Network | AirQo', + description: + 'Access a wide range of resources and insights on air quality management from Clean Air Network. Explore reports, case studies, and research to stay ahead in clean air initiatives.', +}; + +const Page = () => { return (
@@ -8,4 +16,4 @@ const page = () => { ); }; -export default page; +export default Page; diff --git a/src/website2/src/app/favicon.ico b/src/website2/src/app/favicon.ico index 2e026677d8..80f940cae3 100644 Binary files a/src/website2/src/app/favicon.ico and b/src/website2/src/app/favicon.ico differ diff --git a/src/website2/src/app/layout.tsx b/src/website2/src/app/layout.tsx index ebd32cf9c6..a6061ec024 100644 --- a/src/website2/src/app/layout.tsx +++ b/src/website2/src/app/layout.tsx @@ -39,6 +39,45 @@ export default async function RootLayout({ const description = 'AirQo is transforming air quality management in Africa by providing low-cost sensors, real-time data, and actionable insights to help communities and organizations improve air quality.'; + const keywords = [ + 'AirQo', + 'air quality monitoring', + 'air pollution', + 'PM1', + 'PM2.5', + 'PM10', + 'NO2', + 'SO2', + 'CO', + 'O3', + 'air quality index', + 'AQI', + 'real-time air quality data', + 'low-cost air sensors', + 'urban air pollution', + 'environmental monitoring', + 'climate change', + 'air quality management', + 'clean air solutions', + 'air quality in Africa', + 'environmental health', + 'ambient air monitoring', + 'particulate matter', + 'air quality forecasting', + 'air quality analytics', + 'pollution mitigation', + 'environmental data', + 'sustainable cities', + 'public health', + 'respiratory health', + 'environmental policy', + 'air quality research', + 'air quality standards', + 'air quality compliance', + 'air pollution control', + 'air quality education', + ].join(', '); + const maintenance = await checkMaintenance(); return ( @@ -47,24 +86,54 @@ export default async function RootLayout({ {/* Primary SEO */} {title} - + + + + {/* Real Favicon Generator / Favicon + App Icons */} + + + + + + + {/* Windows / MS Tiles */} + + + + {/* Manifest */} + + {/* Open Graph / Facebook */} + + + {/* Twitter */} + + + {/* Canonical URL */} + {/* GA snippet must appear in for Search Console verification */} diff --git a/src/website2/src/components/layouts/Footer.tsx b/src/website2/src/components/layouts/Footer.tsx index 24c76d3f8e..d70f13fd4f 100644 --- a/src/website2/src/components/layouts/Footer.tsx +++ b/src/website2/src/components/layouts/Footer.tsx @@ -1,12 +1,8 @@ import Image from 'next/image'; import Link from 'next/link'; import React from 'react'; -import { - FaFacebookF, - FaLinkedinIn, - FaTwitter, - FaYoutube, -} from 'react-icons/fa'; +import { FaFacebookF, FaLinkedinIn, FaYoutube } from 'react-icons/fa'; +import { FaXTwitter } from 'react-icons/fa6'; import mainConfig from '@/configs/mainConfigs'; @@ -33,9 +29,9 @@ const Footer = () => { width={70} height={60} /> -

+

Clean air for all
African Cities. -

+

{ aria-label="Twitter" className="text-blue-600 bg-blue-50 rounded-full p-2 hover:bg-blue-200 transition-all" > - +
diff --git a/src/website2/src/components/layouts/Navbar.tsx b/src/website2/src/components/layouts/Navbar.tsx index 584ae869dd..fbb9938c36 100644 --- a/src/website2/src/components/layouts/Navbar.tsx +++ b/src/website2/src/components/layouts/Navbar.tsx @@ -20,7 +20,7 @@ import { import mainConfig from '@/configs/mainConfigs'; import { useDispatch } from '@/hooks'; import { openModal } from '@/store/slices/modalSlice'; -import TabNavigation from '@/views/cleanairforum/TabNavigation'; +import TabNavigation from '@/views/cleanAirNetwork/TabNavigation'; import { trackEvent } from '../GoogleAnalytics'; import NotificationBanner from './NotificationBanner'; diff --git a/src/website2/src/components/layouts/NewsLetter.tsx b/src/website2/src/components/layouts/NewsLetter.tsx index d523f797d3..da39b56ca9 100644 --- a/src/website2/src/components/layouts/NewsLetter.tsx +++ b/src/website2/src/components/layouts/NewsLetter.tsx @@ -78,7 +78,7 @@ const NewsLetter: React.FC = () => { // Split layout for idle state (header and form side-by-side on lg screens)
-

+

Subscribe to our Newsletter

diff --git a/src/website2/src/components/layouts/NotificationBanner.tsx b/src/website2/src/components/layouts/NotificationBanner.tsx index f80e02c09f..dd4348ad7d 100644 --- a/src/website2/src/components/layouts/NotificationBanner.tsx +++ b/src/website2/src/components/layouts/NotificationBanner.tsx @@ -10,7 +10,7 @@ import mainConfig from '@/configs/mainConfigs'; import { trackEvent } from '../GoogleAnalytics'; -const CLEAN_AIR_NETWORK_ROUTE = '/clean-air-network'; +const CLEAN_AIR_NETWORK_ROUTE = '/clean-air-network/about'; const NotificationBanner: React.FC = () => { const handleNetworkClick = (version: 'desktop' | 'mobile') => { @@ -52,7 +52,7 @@ const NotificationBanner: React.FC = () => {

{ - e.stopPropagation(); // Prevent double event firing + e.stopPropagation(); handleNetworkClick('mobile'); }} > diff --git a/src/website2/src/views/Forum/TabNavigation.tsx b/src/website2/src/views/Forum/TabNavigation.tsx deleted file mode 100644 index e34eeb07dc..0000000000 --- a/src/website2/src/views/Forum/TabNavigation.tsx +++ /dev/null @@ -1,63 +0,0 @@ -// components/layouts/TabNavigation.tsx -'use client'; - -import Link from 'next/link'; -import { usePathname, useSearchParams } from 'next/navigation'; -import React from 'react'; - -const TabNavigation: React.FC = () => { - const pathname = usePathname(); - const searchParams = useSearchParams(); - // Read the current slug, if it exists. - const currentSlug = searchParams.get('slug'); - - // Function to check if the tab is active based on the current pathname. - const isActiveTab = (path: string) => - pathname === path || pathname.startsWith(path); - - // Define the tabs list. - const tabs = [ - { href: '/clean-air-forum/about', text: 'About' }, - { href: '/clean-air-forum/program-committee', text: 'Programme Committee' }, - { href: '/clean-air-forum/sessions', text: 'Call for Sessions' }, - { href: '/clean-air-forum/speakers', text: 'Speakers' }, - { href: '/clean-air-forum/partners', text: 'Partners' }, - { href: '/clean-air-forum/sponsorships', text: 'Sponsorships' }, - { href: '/clean-air-forum/logistics', text: 'Travel Logistics' }, - { href: '/clean-air-forum/glossary', text: 'Glossary' }, - { href: '/clean-air-forum/resources', text: 'Resources' }, - ]; - - // If currentSlug exists, append it to the URL as a query parameter. - const buildHref = (href: string) => { - if (currentSlug) { - return `${href}?slug=${encodeURIComponent(currentSlug)}`; - } - return href; - }; - - return ( -
-
-
- {tabs.map((link, index) => ( - - {link.text} - {isActiveTab(link.href) && ( - - )} - - ))} -
-
-
- ); -}; - -export default TabNavigation; diff --git a/src/website2/src/views/about/AboutPage.tsx b/src/website2/src/views/about/AboutPage.tsx index 55da3ea15e..c6ffd06b9e 100644 --- a/src/website2/src/views/about/AboutPage.tsx +++ b/src/website2/src/views/about/AboutPage.tsx @@ -15,7 +15,7 @@ import { useTeamMembers, } from '@/hooks/useApiHooks'; import { openModal } from '@/store/slices/modalSlice'; -import PaginatedSection from '@/views/cleanairforum/PaginatedSection'; +import PaginatedSection from '@/views/cleanAirNetwork/PaginatedSection'; /** Skeleton Loader Component **/ diff --git a/src/website2/src/views/cleanairforum/ContentSection.tsx b/src/website2/src/views/cleanAirNetwork/ContentSection.tsx similarity index 100% rename from src/website2/src/views/cleanairforum/ContentSection.tsx rename to src/website2/src/views/cleanAirNetwork/ContentSection.tsx diff --git a/src/website2/src/views/cleanairforum/EventCard.tsx b/src/website2/src/views/cleanAirNetwork/EventCard.tsx similarity index 100% rename from src/website2/src/views/cleanairforum/EventCard.tsx rename to src/website2/src/views/cleanAirNetwork/EventCard.tsx diff --git a/src/website2/src/views/cleanairforum/EventSkeleton.tsx b/src/website2/src/views/cleanAirNetwork/EventSkeleton.tsx similarity index 100% rename from src/website2/src/views/cleanairforum/EventSkeleton.tsx rename to src/website2/src/views/cleanAirNetwork/EventSkeleton.tsx diff --git a/src/website2/src/views/cleanairforum/FeaturedEvent.tsx b/src/website2/src/views/cleanAirNetwork/FeaturedEvent.tsx similarity index 100% rename from src/website2/src/views/cleanairforum/FeaturedEvent.tsx rename to src/website2/src/views/cleanAirNetwork/FeaturedEvent.tsx diff --git a/src/website2/src/views/cleanairforum/PaginatedSection.tsx b/src/website2/src/views/cleanAirNetwork/PaginatedSection.tsx similarity index 100% rename from src/website2/src/views/cleanairforum/PaginatedSection.tsx rename to src/website2/src/views/cleanAirNetwork/PaginatedSection.tsx diff --git a/src/website2/src/views/cleanairforum/RegisterBanner.tsx b/src/website2/src/views/cleanAirNetwork/RegisterBanner.tsx similarity index 100% rename from src/website2/src/views/cleanairforum/RegisterBanner.tsx rename to src/website2/src/views/cleanAirNetwork/RegisterBanner.tsx diff --git a/src/website2/src/views/cleanAirNetwork/TabNavigation.tsx b/src/website2/src/views/cleanAirNetwork/TabNavigation.tsx new file mode 100644 index 0000000000..8733cdeac7 --- /dev/null +++ b/src/website2/src/views/cleanAirNetwork/TabNavigation.tsx @@ -0,0 +1,49 @@ +'use client'; + +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; +import type React from 'react'; + +import mainConfig from '@/configs/mainConfigs'; + +const tabs = [ + { label: 'About', value: '/clean-air-network/about' }, + { label: 'Membership', value: '/clean-air-network/membership' }, + { label: 'Events', value: '/clean-air-network/events' }, + { label: 'Resources', value: '/clean-air-network/resources' }, +]; + +const TabNavigation: React.FC = () => { + const pathname = usePathname(); + + const isActiveTab = (tabValue: string) => { + if (tabValue === '/clean-air-network/about') { + return pathname === tabValue; + } + return pathname.startsWith(tabValue); + }; + + return ( + + ); +}; + +export default TabNavigation; diff --git a/src/website2/src/app/clean-air-network/CleanAirPage.tsx b/src/website2/src/views/cleanAirNetwork/about/CleanAirPage.tsx similarity index 95% rename from src/website2/src/app/clean-air-network/CleanAirPage.tsx rename to src/website2/src/views/cleanAirNetwork/about/CleanAirPage.tsx index cd9601bc89..8b58f6e837 100644 --- a/src/website2/src/app/clean-air-network/CleanAirPage.tsx +++ b/src/website2/src/views/cleanAirNetwork/about/CleanAirPage.tsx @@ -2,8 +2,8 @@ import Image from 'next/image'; import React from 'react'; -import ContentSection from '@/views/cleanairforum/ContentSection'; -import FeaturedEvent from '@/views/cleanairforum/FeaturedEvent'; +import ContentSection from '@/views/cleanAirNetwork/ContentSection'; +import FeaturedEvent from '@/views/cleanAirNetwork/FeaturedEvent'; const CleanAirPage = () => { const goals = [ diff --git a/src/website2/src/views/cleanairforum/events/EventsPage.tsx b/src/website2/src/views/cleanAirNetwork/events/EventsPage.tsx similarity index 95% rename from src/website2/src/views/cleanairforum/events/EventsPage.tsx rename to src/website2/src/views/cleanAirNetwork/events/EventsPage.tsx index b0b0302d78..d3d5e434c7 100644 --- a/src/website2/src/views/cleanairforum/events/EventsPage.tsx +++ b/src/website2/src/views/cleanAirNetwork/events/EventsPage.tsx @@ -14,9 +14,9 @@ import { } from '@/components/ui'; import mainConfig from '@/configs/mainConfigs'; import { useCleanAirEvents } from '@/hooks/useApiHooks'; -import EventCard from '@/views/cleanairforum/EventCard'; -import EventSkeleton from '@/views/cleanairforum/EventSkeleton'; -import RegisterBanner from '@/views/cleanairforum/RegisterBanner'; +import EventCard from '@/views/cleanAirNetwork/EventCard'; +import EventSkeleton from '@/views/cleanAirNetwork/EventSkeleton'; +import RegisterBanner from '@/views/cleanAirNetwork/RegisterBanner'; const months = [ 'January', diff --git a/src/website2/src/views/cleanairforum/events/SingleEvent.tsx b/src/website2/src/views/cleanAirNetwork/events/SingleEvent.tsx similarity index 100% rename from src/website2/src/views/cleanairforum/events/SingleEvent.tsx rename to src/website2/src/views/cleanAirNetwork/events/SingleEvent.tsx diff --git a/src/website2/src/views/cleanairforum/membership/MemberPage.tsx b/src/website2/src/views/cleanAirNetwork/membership/MemberPage.tsx similarity index 95% rename from src/website2/src/views/cleanairforum/membership/MemberPage.tsx rename to src/website2/src/views/cleanAirNetwork/membership/MemberPage.tsx index 277bfb2a6e..8ee68149d2 100644 --- a/src/website2/src/views/cleanairforum/membership/MemberPage.tsx +++ b/src/website2/src/views/cleanAirNetwork/membership/MemberPage.tsx @@ -5,8 +5,8 @@ import React, { useMemo } from 'react'; import mainConfig from '@/configs/mainConfigs'; import { usePartners } from '@/hooks/useApiHooks'; -import PaginatedSection from '@/views/cleanairforum/PaginatedSection'; -import RegisterBanner from '@/views/cleanairforum/RegisterBanner'; +import PaginatedSection from '@/views/cleanAirNetwork/PaginatedSection'; +import RegisterBanner from '@/views/cleanAirNetwork/RegisterBanner'; const SkeletonPaginatedSection: React.FC = () => { return ( diff --git a/src/website2/src/views/cleanairforum/resources/ResourceCard.tsx b/src/website2/src/views/cleanAirNetwork/resources/ResourceCard.tsx similarity index 100% rename from src/website2/src/views/cleanairforum/resources/ResourceCard.tsx rename to src/website2/src/views/cleanAirNetwork/resources/ResourceCard.tsx diff --git a/src/website2/src/views/cleanairforum/resources/ResourcePage.tsx b/src/website2/src/views/cleanAirNetwork/resources/ResourcePage.tsx similarity index 100% rename from src/website2/src/views/cleanairforum/resources/ResourcePage.tsx rename to src/website2/src/views/cleanAirNetwork/resources/ResourcePage.tsx diff --git a/src/website2/src/views/Forum/BannerSection.tsx b/src/website2/src/views/cleanairforum/BannerSection.tsx similarity index 100% rename from src/website2/src/views/Forum/BannerSection.tsx rename to src/website2/src/views/cleanairforum/BannerSection.tsx diff --git a/src/website2/src/views/Forum/SectionDisplay.tsx b/src/website2/src/views/cleanairforum/SectionDisplay.tsx similarity index 100% rename from src/website2/src/views/Forum/SectionDisplay.tsx rename to src/website2/src/views/cleanairforum/SectionDisplay.tsx diff --git a/src/website2/src/views/cleanairforum/TabNavigation.tsx b/src/website2/src/views/cleanairforum/TabNavigation.tsx index 9fb60cf74e..e34eeb07dc 100644 --- a/src/website2/src/views/cleanairforum/TabNavigation.tsx +++ b/src/website2/src/views/cleanairforum/TabNavigation.tsx @@ -1,48 +1,62 @@ +// components/layouts/TabNavigation.tsx 'use client'; import Link from 'next/link'; -import { usePathname } from 'next/navigation'; -import type React from 'react'; - -import mainConfig from '@/configs/mainConfigs'; - -const tabs = [ - { label: 'About', value: '/clean-air-network' }, - { label: 'Membership', value: '/clean-air-network/membership' }, - { label: 'Events', value: '/clean-air-network/events' }, - { label: 'Resources', value: '/clean-air-network/resources' }, -]; +import { usePathname, useSearchParams } from 'next/navigation'; +import React from 'react'; const TabNavigation: React.FC = () => { const pathname = usePathname(); + const searchParams = useSearchParams(); + // Read the current slug, if it exists. + const currentSlug = searchParams.get('slug'); + + // Function to check if the tab is active based on the current pathname. + const isActiveTab = (path: string) => + pathname === path || pathname.startsWith(path); + + // Define the tabs list. + const tabs = [ + { href: '/clean-air-forum/about', text: 'About' }, + { href: '/clean-air-forum/program-committee', text: 'Programme Committee' }, + { href: '/clean-air-forum/sessions', text: 'Call for Sessions' }, + { href: '/clean-air-forum/speakers', text: 'Speakers' }, + { href: '/clean-air-forum/partners', text: 'Partners' }, + { href: '/clean-air-forum/sponsorships', text: 'Sponsorships' }, + { href: '/clean-air-forum/logistics', text: 'Travel Logistics' }, + { href: '/clean-air-forum/glossary', text: 'Glossary' }, + { href: '/clean-air-forum/resources', text: 'Resources' }, + ]; - const isActiveTab = (tabValue: string) => { - if (tabValue === '/clean-air-network') { - return pathname === tabValue; + // If currentSlug exists, append it to the URL as a query parameter. + const buildHref = (href: string) => { + if (currentSlug) { + return `${href}?slug=${encodeURIComponent(currentSlug)}`; } - return pathname.startsWith(tabValue); + return href; }; return ( - +
); }; diff --git a/src/website2/src/views/Forum/AboutPage.tsx b/src/website2/src/views/cleanairforum/about/AboutPage.tsx similarity index 95% rename from src/website2/src/views/Forum/AboutPage.tsx rename to src/website2/src/views/cleanairforum/about/AboutPage.tsx index c0ec9f38b3..641c647774 100644 --- a/src/website2/src/views/Forum/AboutPage.tsx +++ b/src/website2/src/views/cleanairforum/about/AboutPage.tsx @@ -7,7 +7,7 @@ import { Divider, NoData } from '@/components/ui'; import { useForumData } from '@/context/ForumDataContext'; import { isValidHTMLContent } from '@/utils/htmlValidator'; import { renderContent } from '@/utils/quillUtils'; -import SectionDisplay from '@/views/Forum/SectionDisplay'; +import SectionDisplay from '@/views/cleanAirForum/SectionDisplay'; type SectionRowProps = { title: string; diff --git a/src/website2/src/views/cleanairforum/glossary/GlossaryPage.tsx b/src/website2/src/views/cleanairforum/glossary/GlossaryPage.tsx new file mode 100644 index 0000000000..48f7003000 --- /dev/null +++ b/src/website2/src/views/cleanairforum/glossary/GlossaryPage.tsx @@ -0,0 +1,115 @@ +'use client'; + +import DOMPurify from 'dompurify'; +import Link from 'next/link'; +import React from 'react'; + +import Loading from '@/components/loading'; +import { Divider } from '@/components/ui'; +import { NoData } from '@/components/ui'; +import { useForumData } from '@/context/ForumDataContext'; +import { ForumEvent } from '@/types/forum'; +import { isValidGlossaryContent } from '@/utils/glossaryValidator'; +import { renderContent } from '@/utils/quillUtils'; +import SectionDisplay from '@/views/cleanAirForum/SectionDisplay'; + +const GlossaryPage: React.FC = () => { + // Access data from the context. + const { selectedEvent, eventTitles } = useForumData(); + + // If either is not available, show a loading state. + if (!selectedEvent || !eventTitles) { + return ; + } + + // Extract the events list from eventTitles. + // If eventTitles is an array, use it directly; otherwise, assume it's a ForumTitlesResponse. + const eventsList: ForumEvent[] = Array.isArray(eventTitles) + ? eventTitles + : eventTitles.forum_events; + + if (eventsList.length === 0) { + return ; + } + + // Render the main glossary content using the selected event. + const glossaryHTML = renderContent(selectedEvent.glossary_details); + const showGlossaryMain = isValidGlossaryContent(glossaryHTML); + + const glossarySections = selectedEvent.sections?.filter((section: any) => { + if (!section.pages.includes('glossary')) return false; + const html = renderContent(section.content); + return html.trim().length > 0; + }); + + return ( +
+ + + {/* Clean Air Forum Events Section (Sidebar) */} +
+ {/* Left column: Heading */} +
+

+ Clean Air Forum Events +

+
+ {/* Right column: List of event links */} +
+
    + {eventsList.map((event) => { + // Use the unique_title directly in the link. + const href = `/clean-air-forum/about?slug=${encodeURIComponent( + event.unique_title, + )}`; + return ( +
  • + + {event.title} + +
  • + ); + })} +
+
+
+ + {/* Clean Air Glossary Section */} + {showGlossaryMain && ( + <> + +
+ {/* Left column: Heading */} +
+

+ Clean Air Glossary +

+
+ {/* Right column: Glossary content */} +
+
+ + )} + + {/* Additional Glossary Sections (if any) */} + {glossarySections && glossarySections.length > 0 && ( + <> + {glossarySections.map((section: any) => ( + + ))} + + )} +
+ ); +}; + +export default GlossaryPage; diff --git a/src/website2/src/views/cleanairforum/logistics/LogisticsPage.tsx b/src/website2/src/views/cleanairforum/logistics/LogisticsPage.tsx new file mode 100644 index 0000000000..adb3800151 --- /dev/null +++ b/src/website2/src/views/cleanairforum/logistics/LogisticsPage.tsx @@ -0,0 +1,96 @@ +'use client'; + +import DOMPurify from 'dompurify'; +import React from 'react'; + +import Loading from '@/components/loading'; +import { Divider } from '@/components/ui'; +import { useForumData } from '@/context/ForumDataContext'; +import { isValidHTMLContent } from '@/utils/htmlValidator'; +import { renderContent } from '@/utils/quillUtils'; +import SectionDisplay from '@/views/cleanAirForum/SectionDisplay'; + +const LogisticsPage: React.FC = () => { + // Destructure the selected event from the context. + const { selectedEvent } = useForumData(); + + // If selectedEvent is not available, show a loading state. + if (!selectedEvent) { + return ; + } + + // Render static content from the event model. + const vaccinationHTML = renderContent( + selectedEvent.travel_logistics_vaccination_details, + ); + const visaHTML = renderContent(selectedEvent.travel_logistics_visa_details); + + const showVaccination = isValidHTMLContent(vaccinationHTML); + const showVisa = isValidHTMLContent(visaHTML); + + // Filter extra sections assigned to the "logistics" page. + const logisticsSections = selectedEvent.sections?.filter( + (section: any) => + section.pages.includes('logistics') && + isValidHTMLContent(renderContent(section.content)), + ); + + return ( +
+ {/* Render Vaccination Section if content exists */} + {showVaccination && ( + <> + +
+
+
+

+ Vaccination +

+
+
+
+
+ + )} + + {/* Render Visa Invitation Letter Section if content exists */} + {showVisa && ( + <> + +
+
+
+

+ Visa invitation letter +

+
+
+
+
+ + )} + + {/* Render additional Logistics Sections, if any */} + {logisticsSections && logisticsSections.length > 0 && ( + <> + {logisticsSections.map((section: any) => ( + + ))} + + )} +
+ ); +}; + +export default LogisticsPage; diff --git a/src/website2/src/views/cleanairforum/partners/PartnersPage.tsx b/src/website2/src/views/cleanairforum/partners/PartnersPage.tsx new file mode 100644 index 0000000000..e4810575ef --- /dev/null +++ b/src/website2/src/views/cleanairforum/partners/PartnersPage.tsx @@ -0,0 +1,148 @@ +'use client'; + +import DOMPurify from 'dompurify'; +import React from 'react'; + +import { Divider } from '@/components/ui'; +import { useForumData } from '@/context/ForumDataContext'; +import { isValidHTMLContent } from '@/utils/htmlValidator'; +import { renderContent } from '@/utils/quillUtils'; +import SectionDisplay from '@/views/cleanAirForum/SectionDisplay'; +import PaginatedSection from '@/views/cleanAirNetwork/PaginatedSection'; + +const PartnersPage: React.FC = () => { + const { selectedEvent } = useForumData(); + if (!selectedEvent) return null; + + const conveningPartners = selectedEvent.partners + ?.filter((partner: any) => partner.category === 'Co-Convening Partner') + .map((partner: any) => ({ + id: partner.id, + logoUrl: partner.partner_logo_url, + })); + + const hostPartners = selectedEvent.partners + ?.filter((partner: any) => partner.category === 'Host Partner') + .map((partner: any) => ({ + id: partner.id, + logoUrl: partner.partner_logo_url, + })); + + const programPartners = selectedEvent.partners + ?.filter((partner: any) => partner.category === 'Program Partner') + .map((partner: any) => ({ + id: partner.id, + logoUrl: partner.partner_logo_url, + })); + + const fundingPartners = selectedEvent.partners + ?.filter((partner: any) => partner.category === 'Funding Partner') + .map((partner: any) => ({ + id: partner.id, + logoUrl: partner.partner_logo_url, + })); + + const mainPartnersHTML = renderContent(selectedEvent.partners_text_section); + const showMainPartners = isValidHTMLContent(mainPartnersHTML); + + const partnersSections = selectedEvent.sections?.filter((section: any) => { + if (!section.pages.includes('partners')) return false; + const sectionHTML = renderContent(section.content); + return isValidHTMLContent(sectionHTML); + }); + + return ( +
+ {showMainPartners && ( +
+

Partners

+
+
+ )} + + {partnersSections && partnersSections.length > 0 && ( + <> + {partnersSections.map((section: any) => ( + + ))} + + )} + + {conveningPartners && conveningPartners.length > 0 && ( + <> + +
+
+

+ Convening partners and Collaborators +

+
+ +
+ + )} + + {hostPartners && hostPartners.length > 0 && ( + <> + +
+
+

+ Host partners +

+
+ +
+ + )} + + {programPartners && programPartners.length > 0 && ( + <> + +
+
+

Exhibitors

+
+ +
+ + )} + + {fundingPartners && fundingPartners.length > 0 && ( + <> + +
+
+

+ Funding Partners and Sponsors +

+
+ +
+ + )} +
+ ); +}; + +export default PartnersPage; diff --git a/src/website2/src/views/cleanairforum/program-committee/CommitteePage.tsx b/src/website2/src/views/cleanairforum/program-committee/CommitteePage.tsx new file mode 100644 index 0000000000..4066ec1be2 --- /dev/null +++ b/src/website2/src/views/cleanairforum/program-committee/CommitteePage.tsx @@ -0,0 +1,121 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +'use client'; + +import DOMPurify from 'dompurify'; +import React, { useMemo, useState } from 'react'; + +import { Divider, MemberCard, Pagination } from '@/components/ui/'; +import { useForumData } from '@/context/ForumDataContext'; +import { isValidHTMLContent } from '@/utils/htmlValidator'; +import { renderContent } from '@/utils/quillUtils'; +import SectionDisplay from '@/views/cleanAirForum/SectionDisplay'; + +const CommitteePage: React.FC = () => { + // Always call useForumData to get the selectedEvent. + const { selectedEvent } = useForumData(); + + // Instead of conditionally calling hooks based on selectedEvent, + // extract fallback values unconditionally. + const persons = selectedEvent?.persons || []; + const sections = selectedEvent?.sections || []; + const committeeText = selectedEvent?.committee_text_section || ''; + + // Local state for pagination. + const [currentPage, setCurrentPage] = useState(1); + const membersPerPage = 6; + + // Memoize committee members using a fallback empty array. + const committeeMembers = useMemo(() => { + return persons.filter( + (person: any) => + person.category === 'Committee Member' || + person.category === 'Committee Member and Key Note Speaker' || + person.category === 'Speaker and Committee Member', + ); + }, [persons]); + + // Calculate total pages. + const totalPages = useMemo(() => { + return Math.ceil(committeeMembers.length / membersPerPage); + }, [committeeMembers, membersPerPage]); + + // Get members for the current page. + const displayedMembers = useMemo(() => { + const startIdx = (currentPage - 1) * membersPerPage; + return committeeMembers.slice(startIdx, startIdx + membersPerPage); + }, [currentPage, committeeMembers, membersPerPage]); + + // Render main committee text. + const committeeHTML = renderContent(committeeText); + const showCommitteeMain = isValidHTMLContent(committeeHTML); + + // Filter extra sections assigned to the "committee" page. + const committeeSections = useMemo(() => { + return sections.filter((section: any) => { + if (!section.pages.includes('committee')) return false; + const sectionHTML = renderContent(section.content); + return isValidHTMLContent(sectionHTML); + }); + }, [sections]); + + const handlePageChange = (newPage: number) => setCurrentPage(newPage); + + // If selectedEvent is still not available, you might render a loading indicator. + if (!selectedEvent) { + return null; + } + + return ( +
+ + + {/* Program Committee Text Section */} +
+ {showCommitteeMain && ( + <> +

Program Committee

+
+ + )} +
+ + {/* Extra Committee Sections using SectionDisplay */} + {committeeSections.length > 0 && ( + <> + {committeeSections.map((section: any) => ( + + ))} + + )} + + {/* Member Cards Grid */} +
+ {displayedMembers.map((person: any) => ( + + ))} +
+ + {/* Pagination Component */} + {totalPages > 1 && ( +
+ +
+ )} +
+ ); +}; + +export default CommitteePage; diff --git a/src/website2/src/views/cleanairforum/resources/ResourcesPage.tsx b/src/website2/src/views/cleanairforum/resources/ResourcesPage.tsx new file mode 100644 index 0000000000..a107048955 --- /dev/null +++ b/src/website2/src/views/cleanairforum/resources/ResourcesPage.tsx @@ -0,0 +1,148 @@ +'use client'; + +import React, { useState } from 'react'; +import { FaChevronDown, FaChevronUp, FaFilePdf } from 'react-icons/fa'; + +import { Divider } from '@/components/ui'; +import { useForumData } from '@/context/ForumDataContext'; + +const getFileNameFromUrl = (url: string | null | undefined): string | null => { + if (!url || typeof url !== 'string') { + console.error('Invalid URL:', url); + return null; + } + const segments = url.split('/'); + return segments.pop() || null; +}; + +const AccordionItem = ({ session, isOpen, toggleAccordion }: any) => { + return ( +
+
+

+ {session.session_title} +

+ {isOpen ? : } +
+ {isOpen && ( +
+ {session?.resource_files?.map((file: any) => ( +
+
+
+

+ {file.resource_summary} +

+ + + {getFileNameFromUrl(file.file_url)} + +
+
+
+ ))} +
+ )} +
+ ); +}; + +const ResourcesPage: React.FC = () => { + const { selectedEvent } = useForumData(); + const [openAccordions, setOpenAccordions] = useState<{ + [resourceIndex: number]: { [sessionIndex: number]: boolean }; + }>({}); + const [allExpanded, setAllExpanded] = useState(false); + + if (!selectedEvent) { + return null; + } + + const handleToggleAccordion = ( + resourceIndex: number, + sessionIndex: number, + ) => { + setOpenAccordions((prevState) => ({ + ...prevState, + [resourceIndex]: { + ...prevState[resourceIndex], + [sessionIndex]: !prevState[resourceIndex]?.[sessionIndex], + }, + })); + }; + + const handleExpandAll = () => { + setAllExpanded(true); + const expandedAccordions: any = {}; + selectedEvent.forum_resources?.forEach( + (resource: any, resourceIndex: number) => { + expandedAccordions[resourceIndex] = {}; + resource.resource_sessions?.forEach((_: any, sessionIndex: number) => { + expandedAccordions[resourceIndex][sessionIndex] = true; + }); + }, + ); + setOpenAccordions(expandedAccordions); + }; + + const handleCollapseAll = () => { + setAllExpanded(false); + setOpenAccordions({}); + }; + + return ( +
+
+ + +
+ + {selectedEvent.forum_resources?.map( + (resource: any, resourceIndex: number) => ( +
+

+ {resource.resource_title} +

+ {resource.resource_sessions?.map( + (session: any, sessionIndex: number) => ( + + handleToggleAccordion(resourceIndex, sessionIndex) + } + /> + ), + )} + +
+ ), + )} +
+ ); +}; + +export default ResourcesPage; diff --git a/src/website2/src/views/cleanairforum/sessions-programs/ProgramsPage.tsx b/src/website2/src/views/cleanairforum/sessions-programs/ProgramsPage.tsx new file mode 100644 index 0000000000..265a8fb81d --- /dev/null +++ b/src/website2/src/views/cleanairforum/sessions-programs/ProgramsPage.tsx @@ -0,0 +1,158 @@ +'use client'; + +import { format } from 'date-fns'; +import DOMPurify from 'dompurify'; +import React, { useState } from 'react'; +import { FaChevronDown, FaChevronUp } from 'react-icons/fa'; + +import { Divider } from '@/components/ui'; +import { useForumData } from '@/context/ForumDataContext'; +import { isValidHTMLContent } from '@/utils/htmlValidator'; +import { renderContent } from '@/utils/quillUtils'; +import SectionDisplay from '@/views/cleanAirForum/SectionDisplay'; + +interface AccordionItemProps { + title: string; + subText: string; + sessions: any[]; + isOpen: boolean; + onToggle: () => void; +} + +const AccordionItem: React.FC = ({ + title, + subText, + sessions, + isOpen, + onToggle, +}) => { + const formatTime = (time: string) => { + try { + return format(new Date(`1970-01-01T${time}Z`), 'p'); + } catch (error) { + console.error(error); + return time; + } + }; + + return ( +
+
+
+

{title}

+
+
+ {isOpen ? : } +
+ {isOpen && ( +
+ {sessions?.map((item: any, index: number) => ( +
+ +
+
+ {formatTime(item.start_time)} +
+
+

{item.session_title}

+
+
+
+
+ ))} +
+ )} +
+ ); +}; + +const ProgramsPage: React.FC = () => { + const { selectedEvent } = useForumData(); + const [openAccordion, setOpenAccordion] = useState(null); + + if (!selectedEvent) { + return null; + } + + const scheduleHTML = renderContent(selectedEvent.schedule_details); + const showSchedule = isValidHTMLContent(scheduleHTML); + + const registrationHTML = renderContent(selectedEvent.registration_details); + const showRegistration = isValidHTMLContent(registrationHTML); + + const sessionSections = selectedEvent.sections?.filter((section: any) => { + if (!section.pages.includes('session')) return false; + const sectionHTML = renderContent(section.content); + return isValidHTMLContent(sectionHTML); + }); + + const handleToggle = (id: string) => { + setOpenAccordion(openAccordion === id ? null : id); + }; + + return ( +
+ {showSchedule && ( +
+

Schedule

+
+
+ )} + + {sessionSections && sessionSections.length > 0 && ( + <> + {sessionSections.map((section: any) => ( + + ))} + + )} + + <> + {selectedEvent.programs?.map((program: any) => ( + handleToggle(program.id)} + /> + ))} + + + {showRegistration && ( +
+ +
+
+

Registration

+
+
+
+
+ )} +
+ ); +}; + +export default ProgramsPage; diff --git a/src/website2/src/views/cleanairforum/speakers/SpeakersPage.tsx b/src/website2/src/views/cleanairforum/speakers/SpeakersPage.tsx new file mode 100644 index 0000000000..c9fb8f40c6 --- /dev/null +++ b/src/website2/src/views/cleanairforum/speakers/SpeakersPage.tsx @@ -0,0 +1,147 @@ +'use client'; + +import DOMPurify from 'dompurify'; +import React, { useState } from 'react'; + +import { Divider, MemberCard, Pagination } from '@/components/ui/'; +import { useForumData } from '@/context/ForumDataContext'; +import { isValidHTMLContent } from '@/utils/htmlValidator'; +import { renderContent } from '@/utils/quillUtils'; +import SectionDisplay from '@/views/cleanAirForum/SectionDisplay'; + +const SpeakersPage: React.FC = () => { + // Now we use the selectedEvent from context + const { selectedEvent } = useForumData(); + const membersPerPage = 6; + const [currentKeyNotePage, setCurrentKeyNotePage] = useState(1); + const [currentSpeakersPage, setCurrentSpeakersPage] = useState(1); + + if (!selectedEvent) { + return null; + } + + // Filter keynote speakers and speakers from selectedEvent.persons. + // (Adjust your filtering logic as needed.) + const keyNoteSpeakers = + selectedEvent.persons?.filter( + (person: any) => + person.category === 'Key Note Speaker' || + person.category === 'Committee Member and Key Note Speaker', + ) || []; + const speakers = + selectedEvent.persons?.filter( + (person: any) => + person.category === 'Speaker' || + person.category === 'Speaker and Committee Member', + ) || []; + + // Pagination calculations for Keynote Speakers. + const totalKeyNotePages = Math.ceil(keyNoteSpeakers.length / membersPerPage); + const startKeyNoteIdx = (currentKeyNotePage - 1) * membersPerPage; + const displayedKeyNoteSpeakers = keyNoteSpeakers.slice( + startKeyNoteIdx, + startKeyNoteIdx + membersPerPage, + ); + + // Pagination calculations for Speakers. + const totalSpeakersPages = Math.ceil(speakers.length / membersPerPage); + const startSpeakersIdx = (currentSpeakersPage - 1) * membersPerPage; + const displayedSpeakers = speakers.slice( + startSpeakersIdx, + startSpeakersIdx + membersPerPage, + ); + + // Handlers for page changes. + const handleKeyNotePageChange = (newPage: number) => + setCurrentKeyNotePage(newPage); + const handleSpeakersPageChange = (newPage: number) => + setCurrentSpeakersPage(newPage); + + // Validate the main speakers text section. + const mainSpeakersHTML = renderContent(selectedEvent.speakers_text_section); + const showMainSpeakers = isValidHTMLContent(mainSpeakersHTML); + + // Filter extra sections assigned to the "speakers" page. + const speakersExtraSections = selectedEvent.sections?.filter( + (section: any) => { + if (!section.pages.includes('speakers')) return false; + const sectionHTML = renderContent(section.content); + return isValidHTMLContent(sectionHTML); + }, + ); + + return ( +
+ + + {/* Speakers Text Section */} + {showMainSpeakers && ( +
+
+
+ )} + + {/* Keynote Speakers Section */} +

Keynote Speakers

+ +
+ {displayedKeyNoteSpeakers.map((person: any) => ( + + ))} +
+ {totalKeyNotePages > 1 && ( +
+ +
+ )} + + + + {/* Speakers Section */} +

Speakers

+
+ {displayedSpeakers.map((person: any) => ( + + ))} +
+ {totalSpeakersPages > 1 && ( +
+ +
+ )} + + {/* Extra Speakers Sections */} + {speakersExtraSections && speakersExtraSections.length > 0 && ( + <> + {speakersExtraSections.map((section: any) => ( + + ))} + + )} +
+ ); +}; + +export default SpeakersPage; diff --git a/src/website2/src/views/cleanairforum/sponsorship/SponsorshipPage.tsx b/src/website2/src/views/cleanairforum/sponsorship/SponsorshipPage.tsx new file mode 100644 index 0000000000..47257b1cee --- /dev/null +++ b/src/website2/src/views/cleanairforum/sponsorship/SponsorshipPage.tsx @@ -0,0 +1,82 @@ +'use client'; + +import DOMPurify from 'dompurify'; +import React from 'react'; + +import { Divider } from '@/components/ui'; +import { useForumData } from '@/context/ForumDataContext'; +import { renderContent } from '@/utils/quillUtils'; +import SectionDisplay from '@/views/cleanAirForum/SectionDisplay'; +import PaginatedSection from '@/views/cleanAirNetwork/PaginatedSection'; + +const SponsorshipPage: React.FC = () => { + const { selectedEvent } = useForumData(); + if (!selectedEvent) return null; + + const sponsorPartner = selectedEvent.partners + ?.filter((partner: any) => partner.category === 'Sponsor Partner') + .map((partner: any) => ({ + id: partner.id, + logoUrl: partner.partner_logo_url, + })); + + const sponsorshipSections = selectedEvent.sections?.filter((section: any) => { + if (!section.pages.includes('sponsorships')) return false; + const html = renderContent(section.content); + return html.trim().length > 0; + }); + + const mainSponsorshipHTML = renderContent( + selectedEvent.sponsorship_opportunities_partners, + ); + const showMainSponsorship = mainSponsorshipHTML.trim().length > 0; + + return ( +
+ {showMainSponsorship && ( +
+ +
+

Sponsorship opportunities

+ +
+
+
+ )} + + {sponsorshipSections && sponsorshipSections.length > 0 && ( + <> + {sponsorshipSections.map((section: any) => ( + + ))} + + )} + + {sponsorPartner && sponsorPartner.length > 0 && ( + <> + +
+
+
+

+ Sponsors +

+
+ +
+
+ + )} +
+ ); +}; + +export default SponsorshipPage;