Skip to content

Commit 590937c

Browse files
committed
feat: add tourist site drawer and enhance related styles
- Introduced a new Tourist Sites page with a drawer for detailed views. - Updated index.css with styles for the site drawer, including animations and responsive design. - Refactored existing components in About, Attend, Financial Aid, Home, Speakers, Sponsor, and Venue pages to improve layout and accessibility. - Replaced alert components with paragraph tags for better semantic structure. - Added localized email functionality in Financial Aid for requesting assistance. Signed-off-by: Steve Yonkeu <yokwejuste@yahoo.com>
1 parent 6155cbf commit 590937c

16 files changed

Lines changed: 728 additions & 262 deletions

src/App.jsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ const Terms = lazy(() => import('./pages/Terms'));
1515
const HealthSafety = lazy(() => import('./pages/HealthSafety'));
1616
const CodeOfConduct = lazy(() => import('./pages/CodeOfConduct'));
1717
const UbuCon = lazy(() => import('./pages/UbuCon'));
18+
const FinancialAid = lazy(() => import('./pages/FinancialAid'));
19+
const TouristSites = lazy(() => import('./pages/TouristSites'));
1820

1921
class ErrorBoundary extends Component {
2022
constructor(props) {
@@ -81,6 +83,8 @@ function App() {
8183
<Route path="health-safety" element={<LazyPage><HealthSafety /></LazyPage>} />
8284
<Route path="code-of-conduct" element={<LazyPage><CodeOfConduct /></LazyPage>} />
8385
<Route path="ubucon" element={<LazyPage><UbuCon /></LazyPage>} />
86+
<Route path="financial-aid" element={<LazyPage><FinancialAid /></LazyPage>} />
87+
<Route path="tourist-sites" element={<LazyPage><TouristSites /></LazyPage>} />
8488
<Route path="*" element={<LegacyRedirect />} />
8589
</Route>
8690

src/components/Footer.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ const Footer = () => {
7979
<a href="mailto:organizers@pythoncameroon.org">organizers@pythoncameroon.org</a>
8080
<Link to={l('/sponsor')}>{t('footer.sponsorship')}</Link>
8181
<Link to={l('/sponsor')}>{t('footer.support')}</Link>
82+
<Link to={l('/financial-aid')}>{t('footer.financialAid')}</Link>
8283
</div>
8384
</div>
8485
</div>
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import React, { useEffect } from 'react';
2+
import { useTranslation } from 'react-i18next';
3+
import { X, MapPin, Clock, Navigation, Lightbulb } from 'lucide-react';
4+
5+
const mapsUrl = (name) =>
6+
`https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(`${name} Yaoundé`)}`;
7+
8+
const TouristSiteDrawer = ({ site, onClose }) => {
9+
const { t } = useTranslation();
10+
11+
useEffect(() => {
12+
if (!site) return undefined;
13+
const handleKey = (e) => {
14+
if (e.key === 'Escape') onClose();
15+
};
16+
document.addEventListener('keydown', handleKey);
17+
const prevOverflow = document.body.style.overflow;
18+
document.body.style.overflow = 'hidden';
19+
return () => {
20+
document.removeEventListener('keydown', handleKey);
21+
document.body.style.overflow = prevOverflow;
22+
};
23+
}, [site, onClose]);
24+
25+
if (!site) return null;
26+
27+
const highlights = Array.isArray(site.highlights) ? site.highlights : [];
28+
29+
return (
30+
<>
31+
<div className="site-drawer-overlay" onClick={onClose} role="presentation" />
32+
<aside className="site-drawer" role="dialog" aria-modal="true" aria-label={site.name}>
33+
<button type="button" className="site-drawer-close" onClick={onClose} aria-label={t('touristSites.close')}>
34+
<X size="1.5rem" />
35+
</button>
36+
37+
<div className="site-drawer-img">
38+
<img src={site.image} alt={site.name} />
39+
</div>
40+
41+
<div className="site-drawer-body">
42+
<h2 style={{ marginBottom: 'var(--spacing-xs)' }}>{site.name}</h2>
43+
<p style={{ color: 'var(--color-text-secondary)' }}>{site.description}</p>
44+
45+
{site.longDescription && (
46+
<p style={{ marginTop: 'var(--spacing-sm)' }}>{site.longDescription}</p>
47+
)}
48+
49+
<h3 style={{ marginTop: 'var(--spacing-md)', marginBottom: 'var(--spacing-xs)' }}>
50+
<Clock size="1em" style={{ verticalAlign: '-0.125em', marginRight: '0.5rem', color: 'var(--color-orange)' }} />
51+
{t('touristSites.duration')}
52+
</h3>
53+
<p>{t('touristSites.durationHours', { hours: site.duration })}</p>
54+
55+
{site.gettingThere && (
56+
<>
57+
<h3 style={{ marginTop: 'var(--spacing-md)', marginBottom: 'var(--spacing-xs)' }}>
58+
<Navigation size="1em" style={{ verticalAlign: '-0.125em', marginRight: '0.5rem', color: 'var(--color-orange)' }} />
59+
{t('touristSites.gettingThere')}
60+
</h3>
61+
<p>{site.gettingThere}</p>
62+
</>
63+
)}
64+
65+
{highlights.length > 0 && (
66+
<>
67+
<h3 style={{ marginTop: 'var(--spacing-md)', marginBottom: 'var(--spacing-xs)' }}>
68+
<Lightbulb size="1em" style={{ verticalAlign: '-0.125em', marginRight: '0.5rem', color: 'var(--color-orange)' }} />
69+
{t('touristSites.highlights')}
70+
</h3>
71+
<ul style={{ paddingLeft: '1.5rem' }}>
72+
{highlights.map((item, i) => <li key={i}>{item}</li>)}
73+
</ul>
74+
</>
75+
)}
76+
77+
<div style={{ marginTop: 'var(--spacing-lg)' }}>
78+
<a
79+
href={site.mapUrl || mapsUrl(site.name)}
80+
target="_blank"
81+
rel="noopener noreferrer"
82+
className="btn btn-primary"
83+
style={{ width: '100%', justifyContent: 'center', display: 'inline-flex', alignItems: 'center' }}
84+
>
85+
<MapPin size="1em" style={{ verticalAlign: '-0.125em', marginRight: '0.5rem' }} />
86+
{t('venue.openInMaps')}
87+
</a>
88+
</div>
89+
</div>
90+
</aside>
91+
</>
92+
);
93+
};
94+
95+
export default TouristSiteDrawer;

src/components/VenueCard.jsx

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,31 @@
11
import React from 'react';
2+
import { useTranslation } from 'react-i18next';
3+
import { MapPin } from 'lucide-react';
24

3-
const VenueCard = ({ venue }) => (
4-
<div className="card animate-on-scroll slide-up">
5-
<div className="card-img">
6-
<img src={venue.image} alt={venue.name} loading="lazy" />
7-
</div>
8-
<h4>{venue.name}</h4>
9-
<p className="card-text">{venue.description}</p>
10-
</div>
11-
);
5+
const mapsUrl = (name) =>
6+
`https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(`${name} Yaoundé`)}`;
7+
8+
const VenueCard = ({ venue }) => {
9+
const { t } = useTranslation();
10+
return (
11+
<a
12+
href={venue.mapUrl || mapsUrl(venue.name)}
13+
target="_blank"
14+
rel="noopener noreferrer"
15+
className="card animate-on-scroll slide-up"
16+
style={{ display: 'flex', flexDirection: 'column', height: '100%', color: 'inherit', textDecoration: 'none' }}
17+
>
18+
<div className="card-img">
19+
<img src={venue.image} alt={venue.name} loading="lazy" />
20+
</div>
21+
<h4>{venue.name}</h4>
22+
<p className="card-text">{venue.description}</p>
23+
<p style={{ marginTop: 'auto', paddingTop: 'var(--spacing-sm)', color: 'var(--color-orange)', fontWeight: 600, fontSize: '0.9rem' }}>
24+
<MapPin size="1em" style={{ verticalAlign: '-0.125em', marginRight: '0.25rem' }} />
25+
{t('venue.openInMaps')}
26+
</p>
27+
</a>
28+
);
29+
};
1230

1331
export default VenueCard;

src/data/touristSites.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export const touristSites = [
2+
{ slug: 'national-museum', image: '/images/venue/national-museum.webp', duration: '1-2' },
3+
{ slug: 'bois-sainte-anastasie', image: '/images/venue/bois-sainte-anastasie.webp', duration: '1' },
4+
{ slug: 'monument-reunification', image: '/images/venue/monument-de-la-reunification.webp', duration: '1' },
5+
{ slug: 'palais-charles-atangana', image: '/images/venue/charles-atangana.webp', duration: '1' },
6+
{ slug: 'mvog-betsi-zoo', image: '/images/venue/mvog-beti-zoo.webp', duration: '2' },
7+
{ slug: 'lac-municipal', image: '/images/venue/lac-municapal-de-yaounde.webp', duration: '1' },
8+
{ slug: 'eco-park', image: '/images/venue/eco-park-yaounde.webp', duration: '2-3' },
9+
{ slug: 'basilique', image: '/images/venue/marie-reines-des-apotres.webp', duration: '1' },
10+
{ slug: 'dade-park', image: '/images/venue/dade-park.webp', duration: '1-2' },
11+
];

0 commit comments

Comments
 (0)