feat: setup frontend project with bun and dynamic data fetching
- Create new frontend project directory with Next.js 15 + React 19 - Migrate from pnpm to bun for faster package management - Add TanStack Query + Axios for dynamic data fetching - Create comprehensive Makefile with development commands - Setup API layer with query hooks and error handling - Configure environment variables and bun settings - Add TypeScript type checking and project documentation - Update CLAUDE.md with bun-specific development workflow
This commit is contained in:
153
frontend/app/page.tsx
Normal file
153
frontend/app/page.tsx
Normal file
@ -0,0 +1,153 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect, useMemo } from "react"
|
||||
import { PhotoGallery } from "@/components/photo-gallery"
|
||||
import { PhotoModal } from "@/components/photo-modal"
|
||||
import { Navigation } from "@/components/navigation"
|
||||
import { FilterBar } from "@/components/filter-bar"
|
||||
import { LoadingSpinner } from "@/components/loading-spinner"
|
||||
import { TimelineView } from "@/components/timeline-view"
|
||||
import { AboutView } from "@/components/about-view"
|
||||
import { ContactView } from "@/components/contact-view"
|
||||
import { usePhotos, type Photo } from "@/lib/queries"
|
||||
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")
|
||||
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
toast({
|
||||
title: "数据加载失败",
|
||||
description: "无法获取照片数据,请稍后重试",
|
||||
variant: "destructive",
|
||||
})
|
||||
}
|
||||
}, [error, toast])
|
||||
|
||||
useEffect(() => {
|
||||
setFilteredPhotos(photos)
|
||||
}, [photos])
|
||||
|
||||
const handleFilter = (category: string) => {
|
||||
setActiveCategory(category)
|
||||
if (category === "all") {
|
||||
setFilteredPhotos(photos)
|
||||
} else {
|
||||
setFilteredPhotos(photos.filter((photo) => photo.category === category))
|
||||
}
|
||||
}
|
||||
|
||||
const handlePhotoClick = (photo: any) => {
|
||||
setSelectedPhoto(photo)
|
||||
}
|
||||
|
||||
const handleCloseModal = () => {
|
||||
setSelectedPhoto(null)
|
||||
}
|
||||
|
||||
const handlePrevPhoto = () => {
|
||||
if (!selectedPhoto) return
|
||||
const currentPhotos = activeTab === "gallery" ? filteredPhotos : photos
|
||||
const currentIndex = currentPhotos.findIndex((p) => p.id === selectedPhoto.id)
|
||||
const prevIndex = currentIndex > 0 ? currentIndex - 1 : currentPhotos.length - 1
|
||||
setSelectedPhoto(currentPhotos[prevIndex])
|
||||
}
|
||||
|
||||
const handleNextPhoto = () => {
|
||||
if (!selectedPhoto) return
|
||||
const currentPhotos = activeTab === "gallery" ? filteredPhotos : photos
|
||||
const currentIndex = currentPhotos.findIndex((p) => p.id === selectedPhoto.id)
|
||||
const nextIndex = currentIndex < currentPhotos.length - 1 ? currentIndex + 1 : 0
|
||||
setSelectedPhoto(currentPhotos[nextIndex])
|
||||
}
|
||||
|
||||
const handleTabChange = (tab: string) => {
|
||||
setActiveTab(tab)
|
||||
// Reset filters when switching tabs
|
||||
if (tab === "timeline") {
|
||||
setActiveCategory("all")
|
||||
setFilteredPhotos(photos)
|
||||
}
|
||||
}
|
||||
|
||||
const handleContactClick = () => {
|
||||
setActiveTab("contact")
|
||||
}
|
||||
|
||||
const getPageTitle = () => {
|
||||
switch (activeTab) {
|
||||
case "gallery":
|
||||
return "摄影作品集"
|
||||
case "timeline":
|
||||
return "创作时间线"
|
||||
case "about":
|
||||
return "关于我"
|
||||
case "contact":
|
||||
return "联系合作"
|
||||
default:
|
||||
return "摄影作品集"
|
||||
}
|
||||
}
|
||||
|
||||
const getPageDescription = () => {
|
||||
switch (activeTab) {
|
||||
case "gallery":
|
||||
return "用镜头记录世界的美好瞬间,每一张照片都是时光的诗篇"
|
||||
case "timeline":
|
||||
return "按时间顺序回顾摄影创作历程,见证技艺与视角的成长轨迹"
|
||||
case "about":
|
||||
return "分享我的摄影故事,探索光影背后的创作理念与人生感悟"
|
||||
case "contact":
|
||||
return "期待与您合作,共同创造独特而珍贵的视觉记忆"
|
||||
default:
|
||||
return "用镜头记录世界的美好瞬间,每一张照片都是时光的诗篇"
|
||||
}
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return <LoadingSpinner />
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-white">
|
||||
<Navigation activeTab={activeTab} onTabChange={handleTabChange} />
|
||||
|
||||
<main className="container mx-auto px-4 py-8">
|
||||
{(activeTab === "gallery" || activeTab === "timeline") && (
|
||||
<div className="text-center mb-12">
|
||||
<h1 className="text-4xl md:text-6xl font-light text-gray-900 mb-4">{getPageTitle()}</h1>
|
||||
<p className="text-lg text-gray-600 max-w-2xl mx-auto">{getPageDescription()}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === "gallery" && (
|
||||
<>
|
||||
<FilterBar activeCategory={activeCategory} onFilter={handleFilter} />
|
||||
<PhotoGallery photos={filteredPhotos} onPhotoClick={handlePhotoClick} />
|
||||
</>
|
||||
)}
|
||||
|
||||
{activeTab === "timeline" && <TimelineView photos={photos} onPhotoClick={handlePhotoClick} />}
|
||||
|
||||
{activeTab === "about" && <AboutView onContactClick={handleContactClick} />}
|
||||
|
||||
{activeTab === "contact" && <ContactView />}
|
||||
</main>
|
||||
|
||||
{selectedPhoto && (
|
||||
<PhotoModal
|
||||
photo={selectedPhoto}
|
||||
onClose={handleCloseModal}
|
||||
onPrev={handlePrevPhoto}
|
||||
onNext={handleNextPhoto}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user