Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion src/assets/react.svg

This file was deleted.

26 changes: 21 additions & 5 deletions src/components/Navbar.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import React, { useState, useEffect } from 'react';
import { NavLink, Link, useLocation } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { Languages } from 'lucide-react';
import { Languages, Sun, Moon } from 'lucide-react';
import { useTheme } from '../hooks/useTheme';

const Navbar = () => {
const [isOpen, setIsOpen] = useState(false);
const [scrolled, setScrolled] = useState(false);
const location = useLocation();
const { t, i18n } = useTranslation();
const { theme, toggleTheme } = useTheme();

const toggleLanguage = () => {
i18n.changeLanguage(i18n.language === 'en' ? 'fr' : 'en');
Expand Down Expand Up @@ -61,6 +63,9 @@ const Navbar = () => {
<Languages size={16} />
{i18n.language === 'en' ? 'FR' : 'EN'}
</button>
<button onClick={toggleTheme} className="theme-toggle" aria-label="Toggle theme">
{theme === 'dark' ? <Sun size={18} /> : <Moon size={18} />}
</button>
</div>

{/* Mobile drawer overlay */}
Expand All @@ -75,6 +80,12 @@ const Navbar = () => {
>
&times;
</button>
<div className="nav-drawer-header">
<Link to="/" className="nav-logo" onClick={() => setIsOpen(false)}>
<img src="/images/branding/python-cameroon-logo.webp" alt="PyCon Cameroon Logo" />
<span>PyCon CM</span>
</Link>
</div>
<div className="nav-drawer-links">
<NavLink to="/about" className={({ isActive }) => isActive ? "active" : ""}>{t('nav.about')}</NavLink>
<NavLink to="/speakers" className={({ isActive }) => isActive ? "active" : ""}>{t('nav.speakers')}</NavLink>
Expand All @@ -85,10 +96,15 @@ const Navbar = () => {
<img src="/images/partners/canonical-cm.webp" alt="" style={{ width: '22px', height: '22px', objectFit: 'contain', borderRadius: '50%' }} />
{t('nav.ubucon')}
</NavLink>
<button onClick={toggleLanguage} className="lang-toggle" aria-label="Toggle language">
<Languages size={16} />
{i18n.language === 'en' ? 'FR' : 'EN'}
</button>
<div className="nav-drawer-actions">
<button onClick={toggleLanguage} className="lang-toggle" aria-label="Toggle language">
<Languages size={18} />
{i18n.language === 'en' ? 'FR' : 'EN'}
</button>
<button onClick={toggleTheme} className="theme-toggle" aria-label="Toggle theme">
{theme === 'dark' ? <Sun size={20} /> : <Moon size={20} />}
</button>
</div>
</div>
</div>

Expand Down
29 changes: 26 additions & 3 deletions src/components/UbuConMap.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,30 @@ const ubuntuIcon = new L.DivIcon({

const CAMEROON_CENTER = [5.9631, 10.1591];

const TILES = {
dark: {
url: 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png',
attribution: '&copy; <a href="https://carto.com/">CARTO</a> &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
},
light: {
url: 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png',
attribution: '&copy; <a href="https://carto.com/">CARTO</a> &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
},
};

const UbuConMap = () => {
const [events, setEvents] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [theme, setTheme] = useState(document.documentElement.getAttribute('data-theme') || 'dark');

useEffect(() => {
const observer = new MutationObserver(() => {
setTheme(document.documentElement.getAttribute('data-theme') || 'dark');
});
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
return () => observer.disconnect();
}, []);

useEffect(() => {
fetch('https://ubucon.org/events.json')
Expand All @@ -35,6 +55,8 @@ const UbuConMap = () => {
});
}, []);

const tile = TILES[theme] || TILES.dark;

if (loading) {
return (
<div style={{ height: '450px', display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'var(--color-dark-alt)', borderRadius: 'var(--radius-md)' }}>
Expand All @@ -55,12 +77,13 @@ const UbuConMap = () => {
<MapContainer
center={CAMEROON_CENTER}
zoom={4}
style={{ height: '450px', width: '100%', borderRadius: 'var(--radius-md)' }}
style={{ height: '450px', width: '100%', borderRadius: 'var(--radius-md)', zIndex: 0 }}
scrollWheelZoom={true}
>
<TileLayer
attribution='&copy; <a href="https://carto.com/">CARTO</a> &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
url="https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png"
key={theme}
attribution={tile.attribution}
url={tile.url}
/>
{events.map((evt, i) => (
<Marker key={i} position={evt.coordinates} icon={ubuntuIcon}>
Expand Down
22 changes: 22 additions & 0 deletions src/hooks/useTheme.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useState, useEffect, useCallback } from 'react';

function getInitialTheme() {
const saved = localStorage.getItem('pycon-theme');
if (saved === 'light' || saved === 'dark') return saved;
return 'dark';
}

export function useTheme() {
const [theme, setTheme] = useState(getInitialTheme);

useEffect(() => {
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('pycon-theme', theme);
}, [theme]);

const toggleTheme = useCallback(() => {
setTheme(prev => prev === 'dark' ? 'light' : 'dark');
}, []);

return { theme, toggleTheme };
}
Loading
Loading