fix: resolve hydration mismatch error and improve project setup
- Fix React hydration mismatch in ThemeProvider with mounted state check - Update layout.tsx to use light theme by default instead of system - Optimize photo filtering with useMemo in page.tsx - Add Express mock API for development - Update CLAUDE.md with comprehensive project documentation - Create backend/ and admin/ directories for future development
This commit is contained in:
@ -24,8 +24,8 @@ export default function RootLayout({
|
||||
<body className={inter.className}>
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
defaultTheme="system"
|
||||
enableSystem
|
||||
defaultTheme="light"
|
||||
enableSystem={false}
|
||||
disableTransitionOnChange
|
||||
>
|
||||
<QueryProvider>
|
||||
|
||||
@ -15,7 +15,6 @@ import { useToast } from "@/components/ui/use-toast"
|
||||
export default function HomePage() {
|
||||
const { data: photos = [], isLoading, error } = usePhotos()
|
||||
const { toast } = useToast()
|
||||
const [filteredPhotos, setFilteredPhotos] = useState<Photo[]>([])
|
||||
const [selectedPhoto, setSelectedPhoto] = useState<Photo | null>(null)
|
||||
const [activeCategory, setActiveCategory] = useState("all")
|
||||
const [activeTab, setActiveTab] = useState("gallery")
|
||||
@ -30,17 +29,15 @@ export default function HomePage() {
|
||||
}
|
||||
}, [error, toast])
|
||||
|
||||
useEffect(() => {
|
||||
setFilteredPhotos(photos)
|
||||
}, [photos])
|
||||
const filteredPhotos = useMemo(() => {
|
||||
if (activeCategory === "all") {
|
||||
return photos
|
||||
}
|
||||
return photos.filter((photo) => photo.category === activeCategory)
|
||||
}, [photos, activeCategory])
|
||||
|
||||
const handleFilter = (category: string) => {
|
||||
setActiveCategory(category)
|
||||
if (category === "all") {
|
||||
setFilteredPhotos(photos)
|
||||
} else {
|
||||
setFilteredPhotos(photos.filter((photo) => photo.category === category))
|
||||
}
|
||||
}
|
||||
|
||||
const handlePhotoClick = (photo: any) => {
|
||||
@ -72,7 +69,6 @@ export default function HomePage() {
|
||||
// Reset filters when switching tabs
|
||||
if (tab === "timeline") {
|
||||
setActiveCategory("all")
|
||||
setFilteredPhotos(photos)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -7,5 +7,15 @@ import {
|
||||
} from 'next-themes'
|
||||
|
||||
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||
const [mounted, setMounted] = React.useState(false)
|
||||
|
||||
React.useEffect(() => {
|
||||
setMounted(true)
|
||||
}, [])
|
||||
|
||||
if (!mounted) {
|
||||
return <>{children}</>
|
||||
}
|
||||
|
||||
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
|
||||
}
|
||||
|
||||
81
frontend/mock-api.js
Normal file
81
frontend/mock-api.js
Normal file
@ -0,0 +1,81 @@
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const app = express();
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
// 模拟照片数据
|
||||
const photos = [
|
||||
{
|
||||
id: 1,
|
||||
src: '/placeholder.jpg',
|
||||
title: '城市夜景',
|
||||
description: '繁华都市中的宁静夜晚',
|
||||
category: 'city',
|
||||
tags: ['夜景', '城市', '建筑'],
|
||||
date: '2024-01-15',
|
||||
exif: {
|
||||
camera: 'Canon EOS R5',
|
||||
lens: '24-70mm f/2.8',
|
||||
settings: 'f/4, 1/60s, ISO 800',
|
||||
location: '上海外滩'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
src: '/placeholder.jpg',
|
||||
title: '自然风光',
|
||||
description: '山川河流间的诗意景色',
|
||||
category: 'nature',
|
||||
tags: ['风景', '自然', '山水'],
|
||||
date: '2024-01-10',
|
||||
exif: {
|
||||
camera: 'Sony A7R IV',
|
||||
lens: '16-35mm f/2.8',
|
||||
settings: 'f/8, 1/125s, ISO 200',
|
||||
location: '张家界'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
src: '/placeholder.jpg',
|
||||
title: '人像摄影',
|
||||
description: '捕捉真实的情感瞬间',
|
||||
category: 'portrait',
|
||||
tags: ['人像', '情感', '生活'],
|
||||
date: '2024-01-05',
|
||||
exif: {
|
||||
camera: 'Nikon D850',
|
||||
lens: '85mm f/1.4',
|
||||
settings: 'f/2.8, 1/200s, ISO 400',
|
||||
location: '工作室'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
// 获取所有照片
|
||||
app.get('/api/photos', (req, res) => {
|
||||
res.json(photos);
|
||||
});
|
||||
|
||||
// 获取单张照片
|
||||
app.get('/api/photos/:id', (req, res) => {
|
||||
const photo = photos.find(p => p.id === parseInt(req.params.id));
|
||||
if (photo) {
|
||||
res.json(photo);
|
||||
} else {
|
||||
res.status(404).json({ error: 'Photo not found' });
|
||||
}
|
||||
});
|
||||
|
||||
// 获取分类
|
||||
app.get('/api/categories', (req, res) => {
|
||||
const categories = [...new Set(photos.map(p => p.category))];
|
||||
res.json(categories);
|
||||
});
|
||||
|
||||
const PORT = 3001;
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Mock API server running on http://localhost:${PORT}`);
|
||||
});
|
||||
@ -45,8 +45,10 @@
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "1.0.4",
|
||||
"cors": "^2.8.5",
|
||||
"date-fns": "4.1.0",
|
||||
"embla-carousel-react": "8.5.1",
|
||||
"express": "^5.1.0",
|
||||
"input-otp": "1.4.1",
|
||||
"lucide-react": "^0.454.0",
|
||||
"next": "15.2.4",
|
||||
|
||||
Reference in New Issue
Block a user