From 1e8fd7ee96ce4929245e7164989772829ae45763 Mon Sep 17 00:00:00 2001 From: Ochieng Paul Date: Mon, 13 Jan 2025 01:51:04 +0300 Subject: [PATCH 1/6] updates --- website2/package-lock.json | 22 +- website2/package.json | 1 + website2/src/app/(about)/about-us/page.tsx | 2 +- .../src/app/(about)/careers/[id]/page.tsx | 4 +- website2/src/app/(about)/careers/page.tsx | 4 +- website2/src/app/(about)/events/[id]/page.tsx | 4 +- website2/src/app/(about)/events/page.tsx | 2 +- website2/src/app/(about)/press/page.tsx | 2 +- website2/src/app/(about)/resources/page.tsx | 2 +- website2/src/app/clean-air-forum/layout.tsx | 11 +- .../clean-air-network/event-details/page.tsx | 7 - .../clean-air-network/events/[id]/page.tsx | 2 +- .../src/app/clean-air-network/events/page.tsx | 4 +- .../app/clean-air-network/membership/page.tsx | 4 +- .../resources/ResourcePage.tsx | 260 ---------------- .../app/clean-air-network/resources/page.tsx | 4 +- website2/{public => src/app}/favicon.ico | Bin website2/src/app/home/page.tsx | 3 +- website2/src/app/layout.tsx | 21 +- website2/src/app/partners/[id]/page.tsx | 53 +--- .../app/products/mobile-app/MobilePage.tsx | 2 +- website2/src/{app => components}/loading.tsx | 0 website2/src/hooks/swrConfig.ts | 18 ++ website2/src/hooks/useApiHooks.tsx | 291 ++++++++++++++++++ .../about-us => views/about}/AboutPage.tsx | 77 +---- .../(about) => views}/careers/CareerPage.tsx | 74 +++-- .../[id] => views/careers}/DetailsPage.tsx | 71 ++--- .../cleanairforum}/events/EventsPage.tsx | 128 ++------ .../cleanairforum/events}/SingleEvent.tsx | 0 .../cleanairforum}/membership/MemberPage.tsx | 124 +++----- .../cleanairforum/resources/ResourcePage.tsx | 209 +++++++++++++ .../(about) => views}/events/EventPage.tsx | 66 ++-- .../[id] => views/events}/SingleEvent.tsx | 124 ++------ .../home}/AnalyticsContentSection.tsx | 0 .../home}/AppDownloadSection.tsx | 0 .../Home => views/home}/FeaturedCarousel.tsx | 62 ++-- website2/src/{app => views}/home/HomePage.tsx | 11 +- .../Home => views/home}/HomePlayerSection.tsx | 2 +- .../Home => views/home}/HomeStatsSection.tsx | 41 +-- .../(about) => views}/press/PressPage.tsx | 43 +-- .../publications}/ResourcePage.tsx | 62 ++-- website2/tailwind.config.ts | 2 + website2/tsconfig.json | 3 +- 43 files changed, 854 insertions(+), 968 deletions(-) delete mode 100644 website2/src/app/clean-air-network/event-details/page.tsx delete mode 100644 website2/src/app/clean-air-network/resources/ResourcePage.tsx rename website2/{public => src/app}/favicon.ico (100%) rename website2/src/{app => components}/loading.tsx (100%) create mode 100644 website2/src/hooks/swrConfig.ts create mode 100644 website2/src/hooks/useApiHooks.tsx rename website2/src/{app/(about)/about-us => views/about}/AboutPage.tsx (85%) rename website2/src/{app/(about) => views}/careers/CareerPage.tsx (78%) rename website2/src/{app/(about)/careers/[id] => views/careers}/DetailsPage.tsx (59%) rename website2/src/{app/clean-air-network => views/cleanairforum}/events/EventsPage.tsx (73%) rename website2/src/{app/clean-air-network/events/[id] => views/cleanairforum/events}/SingleEvent.tsx (100%) rename website2/src/{app/clean-air-network => views/cleanairforum}/membership/MemberPage.tsx (67%) create mode 100644 website2/src/views/cleanairforum/resources/ResourcePage.tsx rename website2/src/{app/(about) => views}/events/EventPage.tsx (82%) rename website2/src/{app/(about)/events/[id] => views/events}/SingleEvent.tsx (69%) rename website2/src/{components/sections/Home => views/home}/AnalyticsContentSection.tsx (100%) rename website2/src/{components/sections/Home => views/home}/AppDownloadSection.tsx (100%) rename website2/src/{components/sections/Home => views/home}/FeaturedCarousel.tsx (76%) rename website2/src/{app => views}/home/HomePage.tsx (89%) rename website2/src/{components/sections/Home => views/home}/HomePlayerSection.tsx (96%) rename website2/src/{components/sections/Home => views/home}/HomeStatsSection.tsx (90%) rename website2/src/{app/(about) => views}/press/PressPage.tsx (74%) rename website2/src/{app/(about)/resources => views/publications}/ResourcePage.tsx (77%) diff --git a/website2/package-lock.json b/website2/package-lock.json index 6406e8e7b5..4ea714cac0 100644 --- a/website2/package-lock.json +++ b/website2/package-lock.json @@ -34,6 +34,7 @@ "rimraf": "^4.4.1", "sass": "^1.79.4", "sharp": "^0.33.5", + "swr": "^2.3.0", "tailwind-merge": "^2.5.3", "tailwindcss-animate": "^1.0.7" }, @@ -4471,7 +4472,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, "engines": { "node": ">=6" } @@ -9703,6 +9703,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swr": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.0.tgz", + "integrity": "sha512-NyZ76wA4yElZWBHzSgEJc28a0u6QZvhb6w0azeL2k7+Q1gAzVK+IqQYXhVOC/mzi+HZIozrZvBVeSeOZNR2bqA==", + "dependencies": { + "dequal": "^2.0.3", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/synckit": { "version": "0.9.2", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", @@ -10227,11 +10239,11 @@ } }, "node_modules/use-sync-external-store": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", - "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz", + "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==", "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/util-deprecate": { diff --git a/website2/package.json b/website2/package.json index 4cb0d13da3..1bb447bde9 100644 --- a/website2/package.json +++ b/website2/package.json @@ -39,6 +39,7 @@ "rimraf": "^4.4.1", "sass": "^1.79.4", "sharp": "^0.33.5", + "swr": "^2.3.0", "tailwind-merge": "^2.5.3", "tailwindcss-animate": "^1.0.7" }, diff --git a/website2/src/app/(about)/about-us/page.tsx b/website2/src/app/(about)/about-us/page.tsx index 230c8fb733..627eba06cd 100644 --- a/website2/src/app/(about)/about-us/page.tsx +++ b/website2/src/app/(about)/about-us/page.tsx @@ -1,6 +1,6 @@ import { Metadata } from 'next'; -import AboutPage from './AboutPage'; +import AboutPage from '@/views/about/AboutPage'; export const metadata: Metadata = { title: 'About Us | AirQo', diff --git a/website2/src/app/(about)/careers/[id]/page.tsx b/website2/src/app/(about)/careers/[id]/page.tsx index fe48446530..26a5bc1bb9 100644 --- a/website2/src/app/(about)/careers/[id]/page.tsx +++ b/website2/src/app/(about)/careers/[id]/page.tsx @@ -1,6 +1,4 @@ -import React from 'react'; - -import DetailsPage from './DetailsPage'; +import DetailsPage from '@/views/careers/DetailsPage'; const page = ({ params }: { params: any }) => { return ( diff --git a/website2/src/app/(about)/careers/page.tsx b/website2/src/app/(about)/careers/page.tsx index 7e58c4fd5e..f4bc355b5e 100644 --- a/website2/src/app/(about)/careers/page.tsx +++ b/website2/src/app/(about)/careers/page.tsx @@ -1,6 +1,4 @@ -import React from 'react'; - -import CareerPage from './CareerPage'; +import CareerPage from '@/views/careers/CareerPage'; const page = () => { return ( diff --git a/website2/src/app/(about)/events/[id]/page.tsx b/website2/src/app/(about)/events/[id]/page.tsx index 68e9c57fa7..d57fff950e 100644 --- a/website2/src/app/(about)/events/[id]/page.tsx +++ b/website2/src/app/(about)/events/[id]/page.tsx @@ -1,6 +1,4 @@ -import React from 'react'; - -import SingleEvent from './SingleEvent'; +import SingleEvent from '@/views/events/SingleEvent'; const page = ({ params }: { params: any }) => { return ( diff --git a/website2/src/app/(about)/events/page.tsx b/website2/src/app/(about)/events/page.tsx index 7b7810727b..5719428b82 100644 --- a/website2/src/app/(about)/events/page.tsx +++ b/website2/src/app/(about)/events/page.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import EventPage from './EventPage'; +import EventPage from '@/views/events/EventPage'; const page = () => { return ( diff --git a/website2/src/app/(about)/press/page.tsx b/website2/src/app/(about)/press/page.tsx index af8b48491b..51b7693b4d 100644 --- a/website2/src/app/(about)/press/page.tsx +++ b/website2/src/app/(about)/press/page.tsx @@ -1,6 +1,6 @@ import { Metadata } from 'next'; -import PressPage from './PressPage'; +import PressPage from '@/views/press/PressPage'; export const metadata: Metadata = { title: 'Press | AirQo in the News', diff --git a/website2/src/app/(about)/resources/page.tsx b/website2/src/app/(about)/resources/page.tsx index 136c69f6a5..572b27ef63 100644 --- a/website2/src/app/(about)/resources/page.tsx +++ b/website2/src/app/(about)/resources/page.tsx @@ -1,6 +1,6 @@ import { Metadata } from 'next'; -import ResourcePage from './ResourcePage'; +import ResourcePage from '@/views/publications/ResourcePage'; export const metadata: Metadata = { title: 'Resources | Air Quality Data and Tools by AirQo', diff --git a/website2/src/app/clean-air-forum/layout.tsx b/website2/src/app/clean-air-forum/layout.tsx index 978388abe4..04c6f551fd 100644 --- a/website2/src/app/clean-air-forum/layout.tsx +++ b/website2/src/app/clean-air-forum/layout.tsx @@ -10,8 +10,6 @@ import BannerSection from '@/components/sections/Forum/BannerSection'; import { ForumDataProvider } from '@/context/ForumDataContext'; import { getForumEvents } from '@/services/apiService'; -import Loading from '../loading'; - // export const metadata: Metadata = { // title: 'Clean Air Forum | AirQo Africa', // description: @@ -56,8 +54,7 @@ type CleanAirLayoutProps = { }; const CleanAirLayout: React.FC = ({ children }) => { - const [data, setData] = useState(null); // Replace `any` with your actual data type - const [loading, setLoading] = useState(true); + const [data, setData] = useState(null); useEffect(() => { const fetchForumEvents = async () => { @@ -67,18 +64,12 @@ const CleanAirLayout: React.FC = ({ children }) => { } catch (error) { console.error('Failed to fetch forum events:', error); setData(null); - } finally { - setLoading(false); } }; fetchForumEvents(); }, []); - if (loading) { - return ; - } - return (
diff --git a/website2/src/app/clean-air-network/event-details/page.tsx b/website2/src/app/clean-air-network/event-details/page.tsx deleted file mode 100644 index 9a152275b5..0000000000 --- a/website2/src/app/clean-air-network/event-details/page.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react'; - -const page = () => { - return
page
; -}; - -export default page; diff --git a/website2/src/app/clean-air-network/events/[id]/page.tsx b/website2/src/app/clean-air-network/events/[id]/page.tsx index 68e9c57fa7..6b46e523ca 100644 --- a/website2/src/app/clean-air-network/events/[id]/page.tsx +++ b/website2/src/app/clean-air-network/events/[id]/page.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import SingleEvent from './SingleEvent'; +import SingleEvent from '@/views/cleanairforum/events/SingleEvent'; const page = ({ params }: { params: any }) => { return ( diff --git a/website2/src/app/clean-air-network/events/page.tsx b/website2/src/app/clean-air-network/events/page.tsx index 00f8f03435..1033d25b12 100644 --- a/website2/src/app/clean-air-network/events/page.tsx +++ b/website2/src/app/clean-air-network/events/page.tsx @@ -1,6 +1,4 @@ -import React from 'react'; - -import EventsPage from './EventsPage'; +import EventsPage from '@/views/cleanairforum/events/EventsPage'; const page = () => { return ( diff --git a/website2/src/app/clean-air-network/membership/page.tsx b/website2/src/app/clean-air-network/membership/page.tsx index fdff9e6a8d..ee1803b08d 100644 --- a/website2/src/app/clean-air-network/membership/page.tsx +++ b/website2/src/app/clean-air-network/membership/page.tsx @@ -1,6 +1,4 @@ -import React from 'react'; - -import MemberPage from './MemberPage'; +import MemberPage from '@/views/cleanairforum/membership/MemberPage'; const page = () => { return ( diff --git a/website2/src/app/clean-air-network/resources/ResourcePage.tsx b/website2/src/app/clean-air-network/resources/ResourcePage.tsx deleted file mode 100644 index 999b91de04..0000000000 --- a/website2/src/app/clean-air-network/resources/ResourcePage.tsx +++ /dev/null @@ -1,260 +0,0 @@ -'use client'; - -import Image from 'next/image'; -import React, { useEffect, useState } from 'react'; - -import { - CustomButton, - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, - NoData, - Pagination, -} from '@/components/ui'; -import { getCleanAirResources } from '@/services/apiService'; - -// Define the structure of the fetched resource -interface FetchedResource { - id: string; - resource_title: string; - resource_link: string | null; - resource_file: string; - author_title: string; - resource_category: string; - resource_authors: string; - order: number; -} - -// Define the structure used in the component -interface Resource { - category: string; - title: string; - createdBy: string; - link?: string; - file?: string; -} - -const ResourcePage = () => { - const [resources, setResources] = useState([]); - const [filteredResources, setFilteredResources] = useState([]); - const [selectedCategory, setSelectedCategory] = useState('All'); - const [currentPage, setCurrentPage] = useState(1); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - const itemsPerPage = 3; - - // Categories for the dropdown filter - const categories = [ - 'All', - 'Toolkits', - 'Technical Reports', - 'Workshop Reports', - 'Research Publications', - ]; - - // Function to map API data to component structure - const mapResources = (fetchedResources: FetchedResource[]): Resource[] => { - return fetchedResources.map((res) => { - // Map the resource_category to match the dropdown categories - const categoryMap: { [key: string]: string } = { - toolkit: 'Toolkits', - technical_report: 'Technical Reports', - workshop_report: 'Workshop Reports', - research_publication: 'Research Publications', - }; - - return { - category: categoryMap[res.resource_category] || 'Others', - title: res.resource_title, - createdBy: res.resource_authors, - link: res.resource_link ?? undefined, // Convert null to undefined - file: res.resource_file || undefined, // Ensure file is undefined if empty - }; - }); - }; - - // Fetch resources from the API - useEffect(() => { - const fetchResources = async () => { - setLoading(true); - try { - const fetchedData: FetchedResource[] = await getCleanAirResources(); - const mappedData = mapResources(fetchedData); - setResources(mappedData); - setFilteredResources(mappedData); - } catch (err) { - setError('Failed to fetch resources. Please try again later.'); - console.error(err); - } finally { - setLoading(false); - } - }; - - fetchResources(); - }, []); - - // Handle category filter change - const handleCategoryChange = (category: string) => { - setSelectedCategory(category); - setCurrentPage(1); // Reset to first page when filter changes - - if (category === 'All') { - setFilteredResources(resources); - } else { - setFilteredResources( - resources.filter((resource) => resource.category === category), - ); - } - }; - - // Pagination logic - const totalPages = Math.ceil(filteredResources.length / itemsPerPage); - const paginatedResources = filteredResources.slice( - (currentPage - 1) * itemsPerPage, - currentPage * itemsPerPage, - ); - - // Loading Skeleton Component - const LoadingSkeleton = () => ( -
- {Array.from({ length: itemsPerPage }).map((_, index) => ( -
-
-
-
-
-
-
- ))} -
- ); - - return ( -
- {/* Main banner section */} -
-
-
- Air Quality Management Banner -
-
-
- - {/* Resource Filter and List */} -
-
-
-

- Resource Center -

- {/* Filter Dropdown */} - - - - - - {categories.map((category) => ( - handleCategoryChange(category)} - > - {category} - - ))} - - -
- - {/* Loading and Error States */} - {loading && } - {error && } - - {/* Resource Cards */} - {!loading && !error && paginatedResources.length === 0 && } - - {!loading && !error && paginatedResources.length > 0 && ( -
- {paginatedResources.map((resource, index) => ( -
-

- {resource.category.toUpperCase()} -

-

- {resource.title} -

-
-

Created by

-

{resource.createdBy}

-
-
- {/* Read Action Plan Button */} - - resource.link - ? window.open( - resource.link, - '_blank', - 'noopener,noreferrer', - ) - : window.open( - resource.file, - '_blank', - 'noopener,noreferrer', - ) - } - > - Read action plan → - - - {/* Download Button */} - {resource.file && ( - - window.open( - resource.file, - '_blank', - 'noopener,noreferrer', - ) - } - > - Download - - )} -
-
- ))} -
- )} - - {/* Pagination */} - {!loading && !error && filteredResources.length > itemsPerPage && ( - - )} -
-
-
- ); -}; - -export default ResourcePage; diff --git a/website2/src/app/clean-air-network/resources/page.tsx b/website2/src/app/clean-air-network/resources/page.tsx index 61f9546b7f..805d6b0e40 100644 --- a/website2/src/app/clean-air-network/resources/page.tsx +++ b/website2/src/app/clean-air-network/resources/page.tsx @@ -1,6 +1,4 @@ -import React from 'react'; - -import ResourcePage from './ResourcePage'; +import ResourcePage from '@/views/cleanairforum/resources/ResourcePage'; const page = () => { return ( diff --git a/website2/public/favicon.ico b/website2/src/app/favicon.ico similarity index 100% rename from website2/public/favicon.ico rename to website2/src/app/favicon.ico diff --git a/website2/src/app/home/page.tsx b/website2/src/app/home/page.tsx index 076dfcec03..d7fc3c5fd2 100644 --- a/website2/src/app/home/page.tsx +++ b/website2/src/app/home/page.tsx @@ -1,8 +1,7 @@ import React from 'react'; import MainLayout from '@/components/layouts/MainLayout'; - -import HomePage from './HomePage'; +import HomePage from '@/views/home/HomePage'; const page = () => { return ( diff --git a/website2/src/app/layout.tsx b/website2/src/app/layout.tsx index 7705649afa..80f2f06009 100644 --- a/website2/src/app/layout.tsx +++ b/website2/src/app/layout.tsx @@ -1,9 +1,10 @@ import '@/styles/globals.scss'; import localFont from 'next/font/local'; -import { ReactNode } from 'react'; +import { ReactNode, Suspense } from 'react'; import EngagementDialog from '@/components/dialogs/EngagementDialog'; +import Loading from '@/components/loading'; import { ErrorBoundary } from '@/components/ui'; import { ReduxDataProvider } from '@/context/ReduxDataProvider'; import { checkMaintenance } from '@/lib/maintenance'; @@ -38,14 +39,16 @@ export default async function RootLayout({ - {maintenance.isActive ? ( - - ) : ( - <> - - {children} - - )} + }> + {maintenance.isActive ? ( + + ) : ( + <> + + {children} + + )} + diff --git a/website2/src/app/partners/[id]/page.tsx b/website2/src/app/partners/[id]/page.tsx index 20b7a65782..b4770e7dbb 100644 --- a/website2/src/app/partners/[id]/page.tsx +++ b/website2/src/app/partners/[id]/page.tsx @@ -2,52 +2,15 @@ import Image from 'next/image'; import { useParams, useRouter } from 'next/navigation'; -import React, { useEffect, useState } from 'react'; +import React from 'react'; -import { getPartnerDetails } from '@/services/apiService'; - -// Define the types for the partner and description data -interface PartnerDescription { - id: string; - description: string; -} - -interface Partner { - id: string; - partner_name: string; - partner_image_url?: string; - partner_logo_url?: string; - type: string; - descriptions: PartnerDescription[]; -} +import { usePartnerDetails } from '@/hooks/useApiHooks'; const PartnerDetailsPage: React.FC = () => { const router = useRouter(); const params = useParams(); - const { id } = params as { id: string }; // Explicitly typing the params - - const [partner, setPartner] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - - // Fetch partner details when component mounts or ID changes - useEffect(() => { - const fetchPartner = async () => { - try { - const data = await getPartnerDetails(id); - setPartner(data); - } catch (err) { - console.error('Error fetching partner details:', err); - setError('Failed to load partner details.'); - } finally { - setLoading(false); - } - }; - - if (id) { - fetchPartner(); - } - }, [id]); + const { id } = params as { id: string }; + const { partnerDetails: partner, isLoading, isError } = usePartnerDetails(id); // Skeleton loader component const SkeletonLoader = () => ( @@ -60,7 +23,7 @@ const PartnerDetailsPage: React.FC = () => { ); // Handle loading state - if (loading) { + if (isLoading) { return (
@@ -69,10 +32,10 @@ const PartnerDetailsPage: React.FC = () => { } // Handle error state - if (error) { + if (isError) { return (
-

{error}

+

Failed to load partner details.

- + ({ + id: partner.id, + logoUrl: partner.partner_logo_url || '', + }))} + /> )}
diff --git a/website2/src/app/(about)/careers/CareerPage.tsx b/website2/src/views/careers/CareerPage.tsx similarity index 78% rename from website2/src/app/(about)/careers/CareerPage.tsx rename to website2/src/views/careers/CareerPage.tsx index fa734f0fb3..c6dc8924f2 100644 --- a/website2/src/app/(about)/careers/CareerPage.tsx +++ b/website2/src/views/careers/CareerPage.tsx @@ -1,41 +1,33 @@ 'use client'; + import { isBefore, parseISO } from 'date-fns'; import { useRouter } from 'next/navigation'; -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import { FiArrowRight } from 'react-icons/fi'; import { CustomButton, NoData } from '@/components/ui'; -import { getCareers, getDepartments } from '@/services/apiService'; +import { useCareers, useDepartments } from '@/hooks/useApiHooks'; const CareerPage: React.FC = () => { const router = useRouter(); - const [departments, setDepartments] = useState([]); - const [careers, setCareers] = useState([]); + const { + departments, + isLoading: departmentsLoading, + isError: departmentsError, + } = useDepartments(); + const { + careers, + isLoading: careersLoading, + isError: careersError, + } = useCareers(); const [selectedDepartmentId, setSelectedDepartmentId] = useState('all'); // Default to All - const [loading, setLoading] = useState(true); // Loading state - - // Fetch departments and careers when the component mounts - useEffect(() => { - const fetchData = async () => { - try { - const departmentsResponse = await getDepartments(); - const careersResponse = await getCareers(); - setDepartments([ - { id: 'all', name: 'Open positions' }, - ...departmentsResponse, - ]); - setCareers(careersResponse); - setLoading(false); - } catch (error) { - console.error('Error fetching data:', error); - setLoading(false); - } - }; - - fetchData(); - }, []); + // Add "Open Positions" to the departments list + const allDepartments = [ + { id: 'all', name: 'Open Positions' }, + ...(departments || []), + ]; // Function to check if the position is still open (closing date in the future) const isJobOpen = (closingDate: string) => { @@ -43,16 +35,18 @@ const CareerPage: React.FC = () => { }; // Filter jobs based on the selected department ID and show only open positions - const filteredJobs = careers.filter((career) => { + const filteredJobs = careers?.filter((career: any) => { const isOpen = isJobOpen(career.closing_date); if (selectedDepartmentId === 'all') return isOpen; return isOpen && career.department === selectedDepartmentId; }); // Group the jobs by department and filter only open jobs - const groupedJobsByDepartment = filteredJobs.reduce((acc: any, job: any) => { - const department = departments.find((dept) => dept.id === job.department); - const departmentName = department ? department.name : 'Open positions'; + const groupedJobsByDepartment = filteredJobs?.reduce((acc: any, job: any) => { + const department = departments?.find( + (dept: any) => dept.id === job.department, + ); + const departmentName = department ? department.name : 'Open Positions'; if (!acc[departmentName]) { acc[departmentName] = { jobs: [], openCount: 0 }; } @@ -61,6 +55,9 @@ const CareerPage: React.FC = () => { return acc; }, {}); + // Show loading skeletons + const isLoading = departmentsLoading || careersLoading; + return (
{/* Header Section */} @@ -87,15 +84,14 @@ const CareerPage: React.FC = () => {

Categories

- {/* Display loading skeleton for categories */} - {loading + {isLoading ? [...Array(5)].map((_, idx) => (
)) - : departments.map((department: any) => ( + : allDepartments.map((department: any) => (
{/* Loading Skeleton for Job Listings */} - {loading && ( + {isLoading && (
{[...Array(3)].map((_, idx) => ( @@ -130,13 +126,14 @@ const CareerPage: React.FC = () => { )} {/* Job Listings Section */} - {!loading && ( + {!isLoading && (
- {/* Group by department and show number of open jobs */} - {Object.keys(groupedJobsByDepartment).length === 0 ? ( + {careersError || departmentsError ? ( + + ) : Object.keys(groupedJobsByDepartment || {}).length === 0 ? ( ) : ( - Object.keys(groupedJobsByDepartment).map((departmentName) => ( + Object.keys(groupedJobsByDepartment || {}).map((departmentName) => (

{departmentName} ( @@ -152,7 +149,6 @@ const CareerPage: React.FC = () => { >

{job.title}

- {/* Display if the job is open or closed */}

= ({ id }) => { - const [career, setCareer] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(''); +const DetailsPage: React.FC<{ id: string }> = ({ id }) => { + const { careerDetails, isLoading, isError } = useCareerDetails(id); const router = useRouter(); - useEffect(() => { - const fetchCareerDetails = async () => { - try { - const careerData = await getCareerDetails(id); - setCareer(careerData); - setLoading(false); - } catch (error) { - console.error('Error fetching career details:', error); - setError('Unable to load career details'); - setLoading(false); - } - }; - - fetchCareerDetails(); - }, [id]); - - if (loading) { + if (isLoading) { return (

{/* Skeleton for the Header */} @@ -60,11 +43,15 @@ const DetailsPage: React.FC = ({ id }) => { ); } - if (error) { - return
{error}
; + if (isError) { + return ( +
+ Unable to load career details. Please try again later. +
+ ); } - if (!career) { + if (!careerDetails) { return ; } @@ -84,12 +71,14 @@ const DetailsPage: React.FC = ({ id }) => { {/* Header Section */}

- {career.title} + {careerDetails.title}

-

Job Type: {career.type}

- Closing Date: {format(new Date(career.closing_date), 'PPP')} + Job Type: {careerDetails.type} +

+

+ Closing Date: {format(new Date(careerDetails.closing_date), 'PPP')}

@@ -98,20 +87,24 @@ const DetailsPage: React.FC = ({ id }) => {
{/* Apply Button */} -
- window.open(career.apply_url, '_blank')} - rel="noopener noreferrer" - className="bg-green-500 hover:bg-green-600 text-white font-semibold" - > - Apply Now - -
+ {careerDetails.apply_url && ( +
+ window.open(careerDetails.apply_url, '_blank')} + rel="noopener noreferrer" + className="bg-green-500 hover:bg-green-600 text-white font-semibold" + > + Apply Now + +
+ )}
); }; diff --git a/website2/src/app/clean-air-network/events/EventsPage.tsx b/website2/src/views/cleanairforum/events/EventsPage.tsx similarity index 73% rename from website2/src/app/clean-air-network/events/EventsPage.tsx rename to website2/src/views/cleanairforum/events/EventsPage.tsx index 015f5c3fd1..9686cd265b 100644 --- a/website2/src/app/clean-air-network/events/EventsPage.tsx +++ b/website2/src/views/cleanairforum/events/EventsPage.tsx @@ -2,7 +2,7 @@ import { format, isAfter, isBefore } from 'date-fns'; import Image from 'next/image'; -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { FaChevronDown, FaChevronUp } from 'react-icons/fa'; import EventCard from '@/components/sections/CleanAir/EventCard'; @@ -15,7 +15,7 @@ import { DropdownMenuTrigger, Pagination, } from '@/components/ui'; -import { getCleanAirEvents } from '@/services/apiService'; +import { useCleanAirEvents } from '@/hooks/useApiHooks'; const months = [ 'January', @@ -33,113 +33,52 @@ const months = [ ]; const categories = [ - { - name: 'All Categories', - value: '', - }, - { - name: 'Webinar', - value: 'webinar', - }, - { - name: 'Workshop', - value: 'workshop', - }, - { - name: 'Marathon', - value: 'marathon', - }, - { - name: 'Conference', - value: 'conference', - }, - { - name: 'Summit', - value: 'summit', - }, - { - name: 'Commemoration', - value: 'commemoration', - }, - { - name: 'In-Person', - value: 'in-person', - }, - { - name: 'Hybrid', - value: 'hybrid', - }, + { name: 'All Categories', value: '' }, + { name: 'Webinar', value: 'webinar' }, + { name: 'Workshop', value: 'workshop' }, + { name: 'Marathon', value: 'marathon' }, + { name: 'Conference', value: 'conference' }, + { name: 'Summit', value: 'summit' }, + { name: 'Commemoration', value: 'commemoration' }, + { name: 'In-Person', value: 'in-person' }, + { name: 'Hybrid', value: 'hybrid' }, ]; const EventsPage: React.FC = () => { - const [events, setEvents] = useState([]); - const [filteredEvents, setFilteredEvents] = useState([]); + const { cleanAirEvents, isLoading, isError } = useCleanAirEvents(); const [selectedMonth, setSelectedMonth] = useState(null); const [selectedCategory, setSelectedCategory] = useState(null); const [showUpcoming, setShowUpcoming] = useState(true); const [showPast, setShowPast] = useState(true); const [currentUpcomingPage, setCurrentUpcomingPage] = useState(1); const [currentPastPage, setCurrentPastPage] = useState(1); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); const itemsPerPage = 4; - console.log(error); const currentDate = useMemo(() => new Date(), []); - // Fetch events on component mount - useEffect(() => { - const fetchEvents = async () => { - try { - const fetchedEvents = await getCleanAirEvents(); - setEvents(fetchedEvents); - setFilteredEvents(fetchedEvents); - } catch (err) { - setError('Failed to load events. Please try again later.'); - console.error(err); - } finally { - setLoading(false); - } - }; + // Filter events based on selectedMonth and selectedCategory + const filteredEvents = useMemo(() => { + if (!cleanAirEvents) return []; - fetchEvents(); - }, []); + let events = [...cleanAirEvents]; - // Handle month selection - const handleMonthChange = (month: string) => { - setSelectedMonth(month); - }; - - // Handle category selection - const handleCategoryChange = (category: string) => { - setSelectedCategory(category); - }; - - // Apply filters whenever selectedMonth or selectedCategory changes - useEffect(() => { - let tempEvents = [...events]; - - // Filter by month if (selectedMonth) { - tempEvents = tempEvents.filter((event) => { + events = events.filter((event) => { const eventDate = new Date(event.start_date); return format(eventDate, 'MMMM') === selectedMonth; }); } - // Filter by category if (selectedCategory) { - tempEvents = tempEvents.filter( + events = events.filter( (event) => event.event_category === selectedCategory, ); } - setFilteredEvents(tempEvents); - setCurrentUpcomingPage(1); - setCurrentPastPage(1); - }, [selectedMonth, selectedCategory, events]); + return events; + }, [cleanAirEvents, selectedMonth, selectedCategory]); - // Split events into upcoming and past based on date comparison + // Split events into upcoming and past const upcomingEvents = useMemo( () => filteredEvents.filter((event) => @@ -187,7 +126,6 @@ const EventsPage: React.FC = () => { {/* Main banner section */}
- {/* Image */}
{ className="rounded-lg object-cover w-full" />
- - {/* Text */}

The CLEAN-Air Network provides a platform for facilitating @@ -219,7 +155,6 @@ const EventsPage: React.FC = () => { {/* Filter Section */}

- {/* Date Dropdown */}
{showUpcoming && (
- {loading ? ( - // Display skeletons + {isLoading ? ( Array.from({ length: itemsPerPage }).map((_, index) => ( )) + ) : isError ? ( +

Failed to load events.

) : paginatedUpcomingEvents.length > 0 ? ( paginatedUpcomingEvents.map((event) => ( @@ -295,8 +230,7 @@ const EventsPage: React.FC = () => {

No upcoming events

)} - {/* Pagination for upcoming events */} - {!loading && upcomingEvents.length > itemsPerPage && ( + {!isLoading && upcomingEvents.length > itemsPerPage && ( {
{showPast && (
- {loading ? ( - // Display skeletons + {isLoading ? ( Array.from({ length: itemsPerPage }).map((_, index) => ( )) + ) : isError ? ( +

Failed to load events.

) : paginatedPastEvents.length > 0 ? ( paginatedPastEvents.map((event) => ( @@ -333,8 +268,7 @@ const EventsPage: React.FC = () => {

No past events

)} - {/* Pagination for past events */} - {!loading && pastEvents.length > itemsPerPage && ( + {!isLoading && pastEvents.length > itemsPerPage && ( { return ( @@ -33,75 +32,42 @@ const SkeletonPaginatedSection: React.FC = () => { }; const MemberPage: React.FC = () => { - const [implementingPartners, setImplementingPartners] = useState< - { id: string; logoUrl: string }[] - >([]); - const [policyPartners, setPolicyPartners] = useState< - { id: string; logoUrl: string }[] - >([]); - const [supportingPartners, setSupportingPartners] = useState< - { id: string; logoUrl: string }[] - >([]); - const [privateSectorPartners, setPrivateSectorPartners] = useState< - { id: string; logoUrl: string }[] - >([]); - const [loading, setLoading] = useState(true); - - useEffect(() => { - const fetchData = async () => { - try { - const data: any[] = await getPartners(); - - // Filter partners for 'cleanair' category - const cleanairPartners = data.filter( - (partner) => partner.website_category.toLowerCase() === 'cleanair', - ); - - // Categorize partners by type - const implementing = cleanairPartners - .filter((partner) => partner.type.toLowerCase() === 'ca-network') - .map((partner) => ({ - id: partner.id, - logoUrl: partner.partner_logo_url || partner.partner_logo || '', - })); - - const policy = cleanairPartners - .filter((partner) => partner.type.toLowerCase() === 'ca-forum') - .map((partner) => ({ - id: partner.id, - logoUrl: partner.partner_logo_url || partner.partner_logo || '', - })); - - const supporting = cleanairPartners - .filter((partner) => partner.type.toLowerCase() === 'ca-support') - .map((partner) => ({ - id: partner.id, - logoUrl: partner.partner_logo_url || partner.partner_logo || '', - })); - - const privateSector = cleanairPartners - .filter( - (partner) => partner.type.toLowerCase() === 'ca-private-sector', - ) - .map((partner) => ({ - id: partner.id, - logoUrl: partner.partner_logo_url || partner.partner_logo || '', - })); - - // Set state - setImplementingPartners(implementing); - setPolicyPartners(policy); - setSupportingPartners(supporting); - setPrivateSectorPartners(privateSector); - } catch (error) { - console.error('Error fetching partners:', error); - } finally { - setLoading(false); - } + const { partners, isLoading } = usePartners(); + + // Categorize partners using useMemo for performance optimization + const { + implementingPartners, + policyPartners, + supportingPartners, + privateSectorPartners, + } = useMemo(() => { + if (!partners) + return { + implementingPartners: [], + policyPartners: [], + supportingPartners: [], + privateSectorPartners: [], + }; + + const cleanairPartners = partners.filter( + (partner: any) => partner.website_category.toLowerCase() === 'cleanair', + ); + + const categorize = (type: string) => + cleanairPartners + .filter((partner: any) => partner.type.toLowerCase() === type) + .map((partner: any) => ({ + id: partner.id, + logoUrl: partner.partner_logo_url || partner.partner_logo || '', + })); + + return { + implementingPartners: categorize('ca-network'), + policyPartners: categorize('ca-forum'), + supportingPartners: categorize('ca-support'), + privateSectorPartners: categorize('ca-private-sector'), }; - - fetchData(); - }, []); + }, [partners]); return (
@@ -136,7 +102,7 @@ const MemberPage: React.FC = () => { {/* Implementing Partners Section */}
- {loading ? ( + {isLoading ? ( ) : ( { {/* Policy Forum Section */}
- {loading ? ( + {isLoading ? ( ) : ( { {/* Private Sector Forum Section */}
- {loading ? ( + {isLoading ? ( ) : ( - Private Sector
- Forum + Private Sector
Forum

} description={ @@ -212,14 +177,13 @@ const MemberPage: React.FC = () => { {/* Supporting Partners Section */}
- {loading ? ( + {isLoading ? ( ) : ( - Supporting
- Partners + Supporting
Partners } description={ @@ -244,7 +208,7 @@ const MemberPage: React.FC = () => {
{/* Register Banner */} - {!loading && } + {!isLoading && }
); }; diff --git a/website2/src/views/cleanairforum/resources/ResourcePage.tsx b/website2/src/views/cleanairforum/resources/ResourcePage.tsx new file mode 100644 index 0000000000..a2b8e7295d --- /dev/null +++ b/website2/src/views/cleanairforum/resources/ResourcePage.tsx @@ -0,0 +1,209 @@ +'use client'; + +import Image from 'next/image'; +import React, { useMemo, useState } from 'react'; + +import { + CustomButton, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, + NoData, + Pagination, +} from '@/components/ui'; +import { useCleanAirResources } from '@/hooks/useApiHooks'; + +const ResourcePage = () => { + const { cleanAirResources, isLoading, isError } = useCleanAirResources(); + const [selectedCategory, setSelectedCategory] = useState('All'); + const [currentPage, setCurrentPage] = useState(1); + const itemsPerPage = 3; + + // Categories for the dropdown filter + const categories = [ + 'All', + 'Toolkits', + 'Technical Reports', + 'Workshop Reports', + 'Research Publications', + ]; + + // Map and filter resources based on the selected category + const filteredResources = useMemo(() => { + if (selectedCategory === 'All') return cleanAirResources || []; + + const categoryMap: { [key: string]: string } = { + Toolkits: 'toolkit', + 'Technical Reports': 'technical_report', + 'Workshop Reports': 'workshop_report', + 'Research Publications': 'research_publication', + }; + + return ( + cleanAirResources?.filter( + (resource: any) => + resource.resource_category === categoryMap[selectedCategory], + ) || [] + ); + }, [cleanAirResources, selectedCategory]); + + // Pagination logic + const totalPages = Math.ceil(filteredResources.length / itemsPerPage); + const paginatedResources = useMemo( + () => + filteredResources.slice( + (currentPage - 1) * itemsPerPage, + currentPage * itemsPerPage, + ), + [filteredResources, currentPage, itemsPerPage], + ); + + // Loading Skeleton Component + const LoadingSkeleton = () => ( +
+ {Array.from({ length: itemsPerPage }).map((_, index) => ( +
+
+
+
+
+
+
+ ))} +
+ ); + + return ( +
+ {/* Main banner section */} +
+
+
+ Air Quality Management Banner +
+
+
+ + {/* Resource Filter and List */} +
+
+
+

+ Resource Center +

+ {/* Filter Dropdown */} + + + + + + {categories.map((category) => ( + { + setSelectedCategory(category); + setCurrentPage(1); + }} + > + {category} + + ))} + + +
+ + {/* Loading and Error States */} + {isLoading && } + {isError && ( + + )} + + {/* Resource Cards */} + {!isLoading && !isError && paginatedResources.length === 0 && ( + + )} + + {!isLoading && !isError && paginatedResources.length > 0 && ( +
+ {paginatedResources.map((resource: any, index: any) => ( +
+

+ {resource.resource_category.toUpperCase()} +

+

+ {resource.resource_title} +

+
+

Created by

+

{resource.resource_authors}

+
+
+ {/* Read Action Plan Button */} + {resource.resource_link && ( + + window.open( + resource.resource_link, + '_blank', + 'noopener,noreferrer', + ) + } + > + Read action plan → + + )} + + {/* Download Button */} + {resource.resource_file && ( + + window.open( + resource.resource_file, + '_blank', + 'noopener,noreferrer', + ) + } + > + Download + + )} +
+
+ ))} +
+ )} + + {/* Pagination */} + {!isLoading && + !isError && + filteredResources.length > itemsPerPage && ( + + )} +
+
+
+ ); +}; + +export default ResourcePage; diff --git a/website2/src/app/(about)/events/EventPage.tsx b/website2/src/views/events/EventPage.tsx similarity index 82% rename from website2/src/app/(about)/events/EventPage.tsx rename to website2/src/views/events/EventPage.tsx index e774546a23..f044fbee0f 100644 --- a/website2/src/app/(about)/events/EventPage.tsx +++ b/website2/src/views/events/EventPage.tsx @@ -2,52 +2,27 @@ import { format, isSameMonth, parse } from 'date-fns'; import Image from 'next/image'; import { useRouter } from 'next/navigation'; -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import { FiCalendar, FiClock } from 'react-icons/fi'; import EventCardsSection from '@/components/sections/Events/EventCardsSection'; import { CustomButton } from '@/components/ui'; -import { getAirQoEvents } from '@/services/apiService'; +import { useAirQoEvents } from '@/hooks/useApiHooks'; const EventPage: React.FC = () => { const router = useRouter(); + const { airQoEvents, isLoading, isError } = useAirQoEvents(); const [selectedTab, setSelectedTab] = useState('upcoming'); - const [upcomingEvents, setUpcomingEvents] = useState([]); - const [pastEvents, setPastEvents] = useState([]); - const [featuredEvents, setFeaturedEvents] = useState([]); - const [isLoading, setIsLoading] = useState(true); - useEffect(() => { - const fetchEvents = async () => { - try { - const events = await getAirQoEvents(); - - const upcoming = events.filter( - (event: any) => new Date(event.end_date) > new Date(), - ); - const past = events.filter( - (event: any) => new Date(event.end_date) <= new Date(), - ); - const featured = events.filter( - (event: any) => event.event_tag === 'Featured', - ); - - setUpcomingEvents(upcoming); - setPastEvents(past); - setFeaturedEvents(featured); - setIsLoading(false); - } catch (error) { - console.error('Error fetching events:', error); - setIsLoading(false); - } - }; - - fetchEvents(); - }, []); - - const handleTabClick = (tab: string) => { - setSelectedTab(tab); - }; + const upcomingEvents = airQoEvents.filter( + (event: any) => new Date(event.end_date) > new Date(), + ); + const pastEvents = airQoEvents.filter( + (event: any) => new Date(event.end_date) <= new Date(), + ); + const featuredEvents = airQoEvents.filter( + (event: any) => event.event_tag === 'Featured', + ); const firstFeaturedEvent = featuredEvents.length > 0 ? featuredEvents[0] : null; @@ -67,6 +42,10 @@ const EventPage: React.FC = () => { } }; + const handleTabClick = (tab: string) => { + setSelectedTab(tab); + }; + return (
{/* Header Section */} @@ -87,6 +66,16 @@ const EventPage: React.FC = () => {
+ ) : isError ? ( +
+
+

Error Loading Events

+

+ We couldn't fetch the events at this time. Please try again + later. +

+
+
) : ( firstFeaturedEvent && (
@@ -140,7 +129,7 @@ const EventPage: React.FC = () => {
{firstFeaturedEvent.title} { {/* Event Cards Section */} {isLoading ? (
- {/* Skeleton for Event Cards */}
{[...Array(6)].map((_, idx) => (
= ({ id }) => { +const SingleEvent: React.FC<{ id: string }> = ({ id }) => { const router = useRouter(); - const [event, setEvent] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - - useEffect(() => { - const fetchEventDetails = async () => { - try { - const response = await getEventDetails(id); - setEvent(response); - } catch (err) { - console.error('Error fetching event details:', err); - setError('Failed to load event details. Please try again later.'); - } finally { - setLoading(false); - } - }; - - fetchEventDetails(); - }, [id]); + const { eventDetails, isLoading, isError } = useEventDetails(id); // Function to format the date range based on whether the months are the same const formatDateRange = (startDate: string, endDate: string) => { @@ -52,10 +33,10 @@ const SingleEvent: React.FC = ({ id }) => { } }; - if (loading) { + if (isLoading) { return (
- {/* Header Skeleton */} + {/* Loading Skeleton */}
@@ -64,78 +45,26 @@ const SingleEvent: React.FC = ({ id }) => {
- - {/* Partner Logos Skeleton */} -
-
- {[...Array(4)].map((_, index) => ( -
-
-
- ))} -
-
- - {/* Event Details Skeleton */} -
-
-
- {[...Array(5)].map((_, index) => ( -
- ))} -
-
- - {/* Event Program Skeleton */} -
-
- {[...Array(3)].map((_, index) => ( -
-
-
- {[...Array(3)].map((_, idx) => ( -
- ))} -
-
- ))} -
- - {/* Inquiry Skeleton */} -
-
- {[...Array(2)].map((_, index) => ( -
-
-
-
-
- ))} -
); } - if (error) { + if (isError) { return (
-

{error}

+

+ Failed to load event details. Please try again later. +

); } - if (!event) { + if (!eventDetails) { return ; } - // Use the utility function directly with the event details (JSON string) - const eventDetailsHtml = event?.event_details + const event = eventDetails; + const eventDetailsHtml = event.event_details ? convertDeltaToHtml(event.event_details) : ''; @@ -151,15 +80,13 @@ const SingleEvent: React.FC = ({ id }) => { >
- {/* bread crumb */} -
- {data.forum_resources.map((resource: any, resourceIndex: number) => ( + {data?.forum_resources?.map((resource: any, resourceIndex: number) => (
{/* Section Title (Resource Title) */}

@@ -138,7 +138,7 @@ const Page = () => {

{/* Accordion Items for Each Session */} - {resource.resource_sessions.map( + {resource?.resource_sessions?.map( (session: any, sessionIndex: number) => ( = ({ {isOpen && (
- {sessions.map((item: any, index: any) => ( + {sessions?.map((item: any, index: any) => (
@@ -93,7 +93,7 @@ const Page = () => {
{/* Programs Accordion */} - {data?.programs.map((program: any) => ( + {data?.programs?.map((program: any) => ( { {/* Keynote Speakers Section */}

Keynote Speakers

- {displayedKeyNoteSpeakers.map((person: any) => ( + {displayedKeyNoteSpeakers?.map((person: any) => ( { {/* Speakers Section */}

Speakers

- {displayedSpeakers.map((person: any) => ( + {displayedSpeakers?.map((person: any) => ( { return ( diff --git a/website2/src/views/home/HomePlayerSection.tsx b/website2/src/views/home/HomePlayerSection.tsx index 8722432799..4333658754 100644 --- a/website2/src/views/home/HomePlayerSection.tsx +++ b/website2/src/views/home/HomePlayerSection.tsx @@ -50,7 +50,7 @@ const HomePlayerSection = () => { } } } - }, [isModalOpen]); + }, [isBackgroundVideoPlaying, isModalOpen]); const handlePlayButtonClick = () => { setIsModalOpen(true); From 091b9f86484600132d58658010f13bfd5696cd46 Mon Sep 17 00:00:00 2001 From: Ochieng Paul Date: Mon, 13 Jan 2025 12:52:44 +0300 Subject: [PATCH 6/6] updates --- website2/src/app/clean-air-forum/speakers/page.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/website2/src/app/clean-air-forum/speakers/page.tsx b/website2/src/app/clean-air-forum/speakers/page.tsx index 80d4cf2b1b..27eeaed363 100644 --- a/website2/src/app/clean-air-forum/speakers/page.tsx +++ b/website2/src/app/clean-air-forum/speakers/page.tsx @@ -18,32 +18,32 @@ const Page = () => { } // Filter keynote speakers and speakers - const KeyNoteSpeakers = data.persons.filter( + const KeyNoteSpeakers = data?.persons?.filter( (person: any) => person.category === 'Key Note Speaker' || person.category === 'Committee Member and Key Note Speaker', ); - const Speakers = data.persons.filter( + const Speakers = data?.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 totalKeyNotePages = Math.ceil(KeyNoteSpeakers?.length / membersPerPage); const startKeyNoteIdx = (currentKeyNotePage - 1) * membersPerPage; const endKeyNoteIdx = startKeyNoteIdx + membersPerPage; - const displayedKeyNoteSpeakers = KeyNoteSpeakers.slice( + const displayedKeyNoteSpeakers = KeyNoteSpeakers?.slice( startKeyNoteIdx, endKeyNoteIdx, ); // Pagination calculations for speakers - const totalSpeakersPages = Math.ceil(Speakers.length / membersPerPage); + const totalSpeakersPages = Math.ceil(Speakers?.length / membersPerPage); const startSpeakersIdx = (currentSpeakersPage - 1) * membersPerPage; const endSpeakersIdx = startSpeakersIdx + membersPerPage; - const displayedSpeakers = Speakers.slice(startSpeakersIdx, endSpeakersIdx); + const displayedSpeakers = Speakers?.slice(startSpeakersIdx, endSpeakersIdx); // Handle page changes const handleKeyNotePageChange = (newPage: number) => {