Files
photo-showcase/src/App.jsx
2025-12-14 14:56:37 +00:00

132 lines
3.8 KiB
JavaScript

import React, { useState, useEffect } from 'react';
import { Sun, Moon } from 'lucide-react';
import Timeline from './components/Timeline';
import PhotoDetail from './components/PhotoDetail';
import Admin from './components/Admin';
import MusicPlayer from './components/MusicPlayer';
import { fetchPhotos } from './data/photos';
function App() {
const [view, setView] = useState(() => {
return window.location.pathname === '/admin' ? 'admin' : 'timeline';
});
const [activePhoto, setActivePhoto] = useState(null);
const [photos, setPhotos] = useState([]);
useEffect(() => {
document.title = import.meta.env.VITE_APP_TITLE || 'Chronicle';
}, []);
// Theme State
const [theme, setTheme] = useState(() => {
return localStorage.getItem('theme_preference') || 'dark';
});
useEffect(() => {
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('theme_preference', theme);
}, [theme]);
const toggleTheme = () => {
setTheme(prev => prev === 'dark' ? 'light' : 'dark');
};
const loadPhotos = async () => {
const data = await fetchPhotos();
setPhotos(data);
};
useEffect(() => {
const fetchAndSet = async () => {
const data = await fetchPhotos();
setPhotos(data);
};
fetchAndSet();
}, [view]);
// Handle browser back/forward
useEffect(() => {
const handlePopState = () => {
setView(window.location.pathname === '/admin' ? 'admin' : 'timeline');
};
window.addEventListener('popstate', handlePopState);
return () => window.removeEventListener('popstate', handlePopState);
}, []);
// Update URL when view changes
useEffect(() => {
const path = view === 'admin' ? '/admin' : '/';
if (window.location.pathname !== path) {
window.history.pushState({}, '', path);
}
}, [view]);
if (view === 'admin') {
return <Admin onBack={() => setView('timeline')} onUpdate={loadPhotos} />;
}
return (
<div className="app-container" style={{ minHeight: '100vh', display: 'flex', flexDirection: 'column' }}>
{/* Theme Toggle */}
<div style={{ position: 'fixed', top: '20px', right: '20px', zIndex: 50 }}>
<button
onClick={toggleTheme}
style={{
background: 'transparent',
color: 'var(--text-primary)',
padding: '8px',
borderRadius: '50%',
border: 'none',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
opacity: 0.7,
transition: 'opacity 0.2s'
}}
onMouseEnter={(e) => e.currentTarget.style.opacity = '1'}
onMouseLeave={(e) => e.currentTarget.style.opacity = '0.7'}
>
{theme === 'dark' ? <Sun size={20} color="white" /> : <Moon size={20} color="black" />}
</button>
</div>
{/* Main Timeline View */}
<div style={{ flex: 1 }}>
<Timeline photos={photos} onSelectPhoto={setActivePhoto} />
</div>
{/* Footer */}
<footer style={{
padding: '2rem',
textAlign: 'center',
color: 'var(--text-secondary)',
fontSize: '0.9rem',
borderTop: '1px solid var(--border)',
marginTop: 'auto'
}}>
<p>© {new Date().getFullYear()} {import.meta.env.VITE_APP_TITLE || 'Chronicle'}. All rights reserved.</p>
</footer>
{/* Detail Overlay */}
{activePhoto && (
<PhotoDetail
photo={activePhoto}
onClose={() => setActivePhoto(null)}
/>
)}
{/* Music Player */}
<MusicPlayer
navidromeUrl={import.meta.env.VITE_NAVIDROME_URL}
username={import.meta.env.VITE_NAVIDROME_USERNAME}
password={import.meta.env.VITE_NAVIDROME_PASSWORD}
playlistId={import.meta.env.VITE_NAVIDROME_PLAYLIST_ID}
/>
</div>
);
}
export default App;