Skip to content

Commit 98bda23

Browse files
authored
Merge pull request #2472 from airqo-platform/website-clean-air
Fix: Clean air forum 2024
2 parents c364dea + c087fa9 commit 98bda23

File tree

18 files changed

+453
-413
lines changed

18 files changed

+453
-413
lines changed

website2/next.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ const nextConfig = {
3838
return [
3939
{
4040
source: '/clean-air/forum',
41-
destination: '/clean-air-forum/about',
41+
destination: '/clean-air-forum/about?slug=clean-air-forum-2024',
4242
permanent: true,
4343
},
4444
{
Lines changed: 36 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,67 @@
11
'use client';
2+
23
import DOMPurify from 'dompurify';
34
import Link from 'next/link';
45
import React from 'react';
56

7+
import Loading from '@/components/loading';
68
import { Divider } from '@/components/ui';
7-
import { useSelector } from '@/hooks/reduxHooks';
9+
import { NoData } from '@/components/ui';
10+
import { useForumData } from '@/context/ForumDataContext';
11+
import { ForumEvent } from '@/types/forum';
812
import { isValidGlossaryContent } from '@/utils/glossaryValidator';
913
import { renderContent } from '@/utils/quillUtils';
1014
import SectionDisplay from '@/views/Forum/SectionDisplay';
1115

12-
const Page: React.FC = () => {
13-
// Retrieve forum events and the selected event index from Redux.
14-
const { events, selectedEventIndex } = useSelector((state) => state.forum);
16+
const GlossaryPage: React.FC = () => {
17+
// Access data from the context.
18+
const { selectedEvent, eventTitles } = useForumData();
1519

16-
if (events.length === 0) {
17-
return null;
20+
// If either is not available, show a loading state.
21+
if (!selectedEvent || !eventTitles) {
22+
return <Loading />;
1823
}
1924

20-
const selectedEvent = events[selectedEventIndex];
25+
// Extract the events list from eventTitles.
26+
// If eventTitles is an array, use it directly; otherwise, assume it's a ForumTitlesResponse.
27+
const eventsList: ForumEvent[] = Array.isArray(eventTitles)
28+
? eventTitles
29+
: eventTitles.forum_events;
2130

22-
// Utility function to create a slug from the event title.
23-
const createSlug = (title: string) => {
24-
return title.split(',')[0].trim().toLowerCase().replace(/\s+/g, '-');
25-
};
31+
if (eventsList.length === 0) {
32+
return <NoData message="No events found" />;
33+
}
2634

27-
// Render the main glossary content.
35+
// Render the main glossary content using the selected event.
2836
const glossaryHTML = renderContent(selectedEvent.glossary_details);
2937
const showGlossaryMain = isValidGlossaryContent(glossaryHTML);
3038

31-
// Filter extra sections assigned to the "glossary" page.
32-
const glossarySections = selectedEvent.sections?.filter((section: any) => {
33-
if (!section.pages.includes('glossary')) return false;
34-
const sectionHTML = renderContent(section.content);
35-
return isValidGlossaryContent(sectionHTML);
36-
});
37-
3839
return (
3940
<div className="px-4 lg:px-0 prose max-w-none flex flex-col gap-6">
4041
<Divider className="bg-black p-0 m-0 h-[1px] w-full" />
4142

42-
{/* Clean Air Forum Events Section */}
43+
{/* Clean Air Forum Events Section (Sidebar) */}
4344
<div className="flex flex-col md:flex-row py-6 md:space-x-8">
4445
{/* Left column: Heading */}
4546
<div className="md:w-1/3 mb-4 md:mb-0">
46-
<h3 className="text-xl font-semibold">Clean Air Forum Events</h3>
47+
<h1 className="text-2xl mt-4 font-semibold">
48+
Clean Air Forum Events
49+
</h1>
4750
</div>
48-
{/* Right column: List of events */}
51+
{/* Right column: List of event links */}
4952
<div className="md:w-2/3">
5053
<ul className="space-y-2">
51-
{events.map((event, index) => {
52-
const slug = createSlug(event.title);
53-
const href = `/clean-air-forum/about?slug=${encodeURIComponent(slug)}`;
54+
{eventsList.map((event) => {
55+
// Use the unique_title directly in the link.
56+
const href = `/clean-air-forum/about?slug=${encodeURIComponent(
57+
event.unique_title,
58+
)}`;
5459
return (
5560
<li key={event.id}>
5661
<Link
5762
href={href}
5863
target="_blank"
59-
onClick={(e) => {
60-
e.preventDefault();
61-
// Open the event page in a new tab.
62-
window.open(href, '_blank');
63-
}}
64-
className={`text-blue-600 hover:underline ${
65-
selectedEventIndex === index ? 'font-bold' : ''
66-
}`}
64+
className="text-blue-600 hover:underline"
6765
>
6866
{event.title}
6967
</Link>
@@ -81,9 +79,9 @@ const Page: React.FC = () => {
8179
<div className="flex flex-col py-6 md:flex-row md:space-x-8">
8280
{/* Left column: Heading */}
8381
<div className="md:w-1/3 mb-4 md:mb-0">
84-
<h2 className="text-2xl font-bold text-gray-900">
82+
<h1 className="text-2xl mt-4 font-bold text-gray-900">
8583
Clean Air Glossary
86-
</h2>
84+
</h1>
8785
</div>
8886
{/* Right column: Glossary content */}
8987
<div
@@ -97,10 +95,9 @@ const Page: React.FC = () => {
9795
)}
9896

9997
{/* Additional Glossary Sections (if any) */}
100-
{glossarySections && glossarySections.length > 0 && (
98+
{selectedEvent.sections && selectedEvent.sections.length > 0 && (
10199
<>
102-
<Divider className="bg-black p-0 m-0 h-[1px] w-full" />
103-
{glossarySections.map((section: any) => (
100+
{selectedEvent.sections.map((section: any) => (
104101
<SectionDisplay key={section.id} section={section} />
105102
))}
106103
</>
@@ -109,4 +106,4 @@ const Page: React.FC = () => {
109106
);
110107
};
111108

112-
export default Page;
109+
export default GlossaryPage;

website2/src/app/clean-air-forum/layout.tsx

Lines changed: 18 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
// components/layouts/CleanAirLayout.tsx
12
'use client';
3+
24
import { useSearchParams } from 'next/navigation';
3-
import React, { ReactNode, useEffect } from 'react';
5+
import React, { ReactNode } from 'react';
46

57
import Footer from '@/components/layouts/Footer';
68
import Navbar from '@/components/layouts/Navbar';
@@ -9,70 +11,40 @@ import Loading from '@/components/loading';
911
import { NoData } from '@/components/ui';
1012
import mainConfig from '@/configs/mainConfigs';
1113
import { ForumDataProvider } from '@/context/ForumDataContext';
12-
import { useDispatch, useSelector } from '@/hooks/reduxHooks';
13-
import { useForumEvents } from '@/hooks/useApiHooks';
14-
import { selectEvent, setEvents } from '@/store/slices/forumSlice';
14+
import { useForumEventDetails, useForumEventTitles } from '@/hooks/useApiHooks';
1515
import BannerSection from '@/views/Forum/BannerSection';
1616

1717
type CleanAirLayoutProps = {
1818
children: ReactNode;
1919
};
2020

2121
const CleanAirLayout: React.FC<CleanAirLayoutProps> = ({ children }) => {
22-
// Fetch forum events from the API.
23-
const { data: forumEvents, isLoading } = useForumEvents();
24-
const dispatch = useDispatch();
2522
const searchParams = useSearchParams();
26-
27-
// When events are fetched, update the Redux slice.
28-
useEffect(() => {
29-
if (forumEvents && forumEvents.length > 0) {
30-
dispatch(setEvents(forumEvents));
31-
}
32-
}, [forumEvents, dispatch]);
33-
34-
// Get the slug query parameter.
23+
// Get the slug query parameter; if missing, the hook returns the "latest" event.
3524
const slug = searchParams.get('slug');
36-
const events = useSelector((state) => state.forum.events);
37-
const selectedEventIndex = useSelector(
38-
(state) => state.forum.selectedEventIndex,
39-
);
40-
41-
// Only run the slug-based selection if a slug is provided.
42-
useEffect(() => {
43-
if (slug && events.length > 0) {
44-
// Normalize the slug by replacing hyphens with spaces and lowercasing.
45-
const normalizedSlug = slug.replace(/-/g, ' ').toLowerCase();
46-
// Find the event whose title (taking the part before a comma) matches.
47-
const index = events.findIndex((event) => {
48-
const cleanTitle = event.title
49-
.split(',')[0]
50-
.trim()
51-
.toLowerCase()
52-
.replace(/-/g, ' ');
53-
return cleanTitle === normalizedSlug;
54-
});
55-
if (index !== -1 && index !== selectedEventIndex) {
56-
dispatch(selectEvent(index));
57-
}
58-
// If no slug match is found, you may choose to do nothing
59-
// rather than resetting to index 0.
60-
}
61-
// Note: we do not reset the index if slug is missing.
62-
}, [slug, events, selectedEventIndex, dispatch]);
6325

64-
const selectedEvent = events[selectedEventIndex];
26+
// Get the details for the selected (or latest) event.
27+
const { data: selectedEvent, isLoading: detailsLoading } =
28+
useForumEventDetails(slug);
29+
// Get the list of event titles (with unique_title values).
30+
const { data: eventTitles, isLoading: titlesLoading } = useForumEventTitles();
6531

66-
if (isLoading) {
32+
if (detailsLoading || titlesLoading) {
6733
return <Loading />;
6834
}
6935

7036
if (!selectedEvent) {
7137
return <NoData message="No event found" />;
7238
}
7339

40+
// Provide both the selected event and the list of event titles to the context.
41+
const forumData = {
42+
selectedEvent,
43+
eventTitles,
44+
};
45+
7446
return (
75-
<ForumDataProvider data={selectedEvent}>
47+
<ForumDataProvider data={forumData}>
7648
<div className="min-h-screen w-full flex flex-col">
7749
{/* Navbar */}
7850
<header className="sticky top-0 z-50">

website2/src/app/clean-air-forum/logistics/page.tsx

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,52 @@
11
'use client';
2+
23
import DOMPurify from 'dompurify';
34
import React from 'react';
45

6+
import Loading from '@/components/loading';
57
import { Divider } from '@/components/ui';
68
import { useForumData } from '@/context/ForumDataContext';
79
import { isValidHTMLContent } from '@/utils/htmlValidator';
810
import { renderContent } from '@/utils/quillUtils';
911
import SectionDisplay from '@/views/Forum/SectionDisplay';
1012

11-
const LogisticsPage = () => {
12-
const data = useForumData();
13+
const LogisticsPage: React.FC = () => {
14+
// Destructure the selected event from the context.
15+
const { selectedEvent } = useForumData();
1316

14-
if (!data) {
15-
return null;
17+
// If selectedEvent is not available, show a loading state.
18+
if (!selectedEvent) {
19+
return <Loading />;
1620
}
1721

1822
// Render static content from the event model.
1923
const vaccinationHTML = renderContent(
20-
data.travel_logistics_vaccination_details,
24+
selectedEvent.travel_logistics_vaccination_details,
2125
);
22-
const visaHTML = renderContent(data.travel_logistics_visa_details);
26+
const visaHTML = renderContent(selectedEvent.travel_logistics_visa_details);
2327

2428
const showVaccination = isValidHTMLContent(vaccinationHTML);
2529
const showVisa = isValidHTMLContent(visaHTML);
2630

2731
// Filter extra sections assigned to the "logistics" page.
28-
const logisticsSections = data.sections?.filter(
32+
const logisticsSections = selectedEvent.sections?.filter(
2933
(section: any) =>
3034
section.pages.includes('logistics') &&
3135
isValidHTMLContent(renderContent(section.content)),
3236
);
3337

3438
return (
3539
<div className="px-4 prose max-w-none lg:px-0">
36-
{/* Render static Vaccination Section if content exists */}
40+
{/* Render Vaccination Section if content exists */}
3741
{showVaccination && (
3842
<>
3943
<Divider className="bg-black p-0 m-0 h-[1px] w-full" />
40-
4144
<div className="py-6">
4245
<div className="flex flex-col md:flex-row md:space-x-8">
4346
<div className="md:w-1/3 mb-4 md:mb-0">
44-
<h2 className="text-2xl font-bold text-gray-900">
47+
<h1 className="text-2xl mt-4 font-bold text-gray-900">
4548
Vaccination
46-
</h2>
49+
</h1>
4750
</div>
4851
<div
4952
className="md:w-2/3 space-y-4"
@@ -56,16 +59,16 @@ const LogisticsPage = () => {
5659
</>
5760
)}
5861

59-
{/* Render static Visa Invitation Letter Section if content exists */}
62+
{/* Render Visa Invitation Letter Section if content exists */}
6063
{showVisa && (
6164
<>
6265
<Divider className="bg-black p-0 m-0 h-[1px] w-full" />
6366
<div className="py-6">
6467
<div className="flex flex-col md:flex-row md:space-x-8">
6568
<div className="md:w-1/3 mb-4 md:mb-0">
66-
<h2 className="text-2xl font-bold text-gray-900">
69+
<h1 className="text-2xl mt-4 font-bold text-gray-900">
6770
Visa invitation letter
68-
</h2>
71+
</h1>
6972
</div>
7073
<div
7174
className="md:w-2/3 space-y-4"
@@ -78,10 +81,9 @@ const LogisticsPage = () => {
7881
</>
7982
)}
8083

81-
{/* Render additional section data for Logistics using SectionDisplay */}
84+
{/* Render additional Logistics Sections, if any */}
8285
{logisticsSections && logisticsSections.length > 0 && (
8386
<>
84-
<Divider className="bg-black p-0 m-0 h-[1px] w-full" />
8587
{logisticsSections.map((section: any) => (
8688
<SectionDisplay key={section.id} section={section} />
8789
))}

0 commit comments

Comments
 (0)