diff --git a/server.js b/server.js index d925d40..d516abf 100644 --- a/server.js +++ b/server.js @@ -207,7 +207,9 @@ const faviconStorage = multer.diskStorage({ cb(null, UPLOAD_DIR); }, filename: (req, file, cb) => { - cb(null, 'favicon.png'); + // Preserve original file extension (e.g., .ico, .png) + const ext = path.extname(file.originalname) || '.png'; + cb(null, `favicon${ext}`); } }); const uploadFavicon = multer({ storage: faviconStorage }); @@ -215,20 +217,65 @@ const uploadFavicon = multer({ storage: faviconStorage }); // Upload Favicon app.post('/api/favicon', uploadFavicon.single('favicon'), (req, res) => { if (!req.file) return res.status(400).json({ error: 'No file uploaded' }); - res.json({ success: true, url: '/uploads/favicon.png' }); + // Respond with the stored filename (including extension) + const storedName = req.file.filename; + res.json({ success: true, url: `/uploads/${storedName}` }); }); // Serve Favicon (Dynamic) app.get('/api/favicon', (req, res) => { - const faviconPath = path.join(UPLOAD_DIR, 'favicon.png'); - if (fs.existsSync(faviconPath)) { + // Determine the stored favicon file (any supported extension) + const possibleExts = ['.ico', '.png', '.svg', '.jpg', '.jpeg']; + let faviconPath = null; + for (const ext of possibleExts) { + const candidate = path.join(UPLOAD_DIR, `favicon${ext}`); + if (fs.existsSync(candidate)) { + faviconPath = candidate; + break; + } + } + if (faviconPath) { + // Set appropriate Content-Type based on extension + const mimeMap = { + '.ico': 'image/x-icon', + '.png': 'image/png', + '.svg': 'image/svg+xml', + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg' + }; + const ext = path.extname(faviconPath).toLowerCase(); + res.setHeader('Content-Type', mimeMap[ext] || 'application/octet-stream'); + // Prevent caching so updates appear immediately + res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate'); res.sendFile(faviconPath); } else { - // Fallback to default + // Fallback to default (404) res.status(404).send('Not found'); } }); +// Fallback for browsers requesting /favicon.ico directly +app.get('/favicon.ico', (req, res) => { + const possibleExts = ['.ico', '.png', '.svg', '.jpg', '.jpeg']; + for (const ext of possibleExts) { + const candidate = path.join(UPLOAD_DIR, `favicon${ext}`); + if (fs.existsSync(candidate)) { + const mimeMap = { + '.ico': 'image/x-icon', + '.png': 'image/png', + '.svg': 'image/svg+xml', + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg' + }; + const mime = mimeMap[ext] || 'application/octet-stream'; + res.setHeader('Content-Type', mime); + res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate'); + return res.sendFile(candidate); + } + } + res.status(404).send('Not found'); +}); + // Serve Frontend (Production) const DIST_DIR = path.join(__dirname, 'dist'); if (fs.existsSync(DIST_DIR)) { diff --git a/src/App.jsx b/src/App.jsx index 00446dc..f5d9b59 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,9 +1,9 @@ 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 { fetchPhotos } from './data/photos'; -import { Settings, Sun, Moon } from 'lucide-react'; function App() { const [view, setView] = useState(() => { @@ -37,7 +37,11 @@ function App() { }; useEffect(() => { - loadPhotos(); + const fetchAndSet = async () => { + const data = await fetchPhotos(); + setPhotos(data); + }; + fetchAndSet(); }, [view]); // Handle browser back/forward diff --git a/src/components/PhotoDetail.jsx b/src/components/PhotoDetail.jsx index 5184883..a671062 100644 --- a/src/components/PhotoDetail.jsx +++ b/src/components/PhotoDetail.jsx @@ -1,8 +1,8 @@ -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import { X, Camera, Aperture, Clock, Gauge, ArrowLeft, MapPin } from 'lucide-react'; const PhotoDetail = ({ photo, onClose }) => { - const [loaded, setLoaded] = useState(false); + // Removed unused loaded state const [isFullScreen, setIsFullScreen] = useState(false); const [showTitle, setShowTitle] = useState(false); @@ -14,14 +14,7 @@ const PhotoDetail = ({ photo, onClose }) => { } }; - useEffect(() => { - setLoaded(true); - // Disable scroll on body when modal is open - document.body.style.overflow = 'hidden'; - return () => { - document.body.style.overflow = 'auto'; - }; - }, []); + // Removed effect that set loaded state and handled body scroll if (!photo) return null; @@ -36,7 +29,7 @@ const PhotoDetail = ({ photo, onClose }) => { zIndex: 100, display: 'flex', flexDirection: 'column', - opacity: loaded ? 1 : 0, + opacity: 1, transition: 'opacity 0.3s ease', overflowY: 'auto' }}