CHORE(merge): merge from develop
Some checks failed
Build Docker Image / build-and-push (push) Has been cancelled
CI / lint-and-build (push) Has been cancelled

- Initial setup and all features from develop branch
- Includes: auth, deploy, docker, style fixes
- K3S deployment configuration
This commit is contained in:
2026-01-06 17:29:16 +09:00
parent b4ce36ba3b
commit f78454c2a1
159 changed files with 18365 additions and 774 deletions

View File

@@ -0,0 +1,114 @@
'use client';
import { useState, useEffect } from 'react';
import Image from 'next/image';
import Link from 'next/link';
import { GalleryPost, getGalleryPosts } from '@/lib/services';
import { useAuth, usePagination } from '@/hooks';
import Pagination from '@/components/Pagination';
import { FileTextIcon } from 'lucide-react';
export default function GalleryPage() {
const [posts, setPosts] = useState<GalleryPost[]>([]);
const [loading, setLoading] = useState(true);
const { user } = useAuth();
const { currentPage, totalPages, setCurrentPage, setTotalPages } = usePagination();
useEffect(() => {
fetchPosts(currentPage);
}, [currentPage]);
const fetchPosts = async (page: number) => {
setLoading(true);
try {
const result = await getGalleryPosts(page, 9);
setPosts(result.data);
setTotalPages(result.pagination.totalPages);
} catch (error) {
console.error('Failed to fetch posts:', error);
} finally {
setLoading(false);
}
};
return (
<div className="space-y-8 w-full">
<div className="py-12 px-4">
<div className="max-w-7xl mx-auto">
{/* 글쓰기 버튼 */}
{user && (
<div className="flex justify-end mb-4">
<Link
href="/gallery/write"
className="px-4 smalltablet:px-6 py-2 smalltablet:py-2.5 bg-linear-to-br from-[#7ba5d6] to-[#6b95c6] hover:from-[#6b95c6] hover:to-[#5b85b6] text-white rounded-lg shadow-md hover:shadow-lg transition-all font-medium text-xs smalltablet:text-sm"
>
</Link>
</div>
)}
{/* 갤러리 그리드 */}
{loading ? (
<div className="grid grid-cols-2 smalltablet:grid-cols-3 gap-4">
{Array.from({ length: 9 }).map((_, idx) => (
<div key={idx} className="bg-gray-200 rounded-lg overflow-hidden animate-pulse">
<div className="aspect-4/3" />
<div className="p-4">
<div className="h-5 bg-gray-300 rounded w-3/4 mb-2" />
<div className="h-4 bg-gray-300 rounded w-1/2" />
</div>
</div>
))}
</div>
) : posts.length === 0 ? (
<div className="bg-white rounded-lg shadow-sm border border-gray-200 text-center py-20 flex items-center justify-center flex-col">
<FileTextIcon className="mx-auto h-12 w-12 smalltablet:h-16 smalltablet:w-16 text-gray-300 mb-4" />
<p className="text-gray-500 text-base smalltablet:text-lg">
.
</p>
</div>
) : (
<div className="grid grid-cols-2 smalltablet:grid-cols-3 gap-4">
{posts.map((post) => (
<Link
key={post.id}
href={`/gallery/${post.id}`}
className="bg-white rounded-lg overflow-hidden shadow-md hover:shadow-lg transition-shadow"
>
<div className="relative aspect-4/3 bg-gray-100">
{post.thumbnailUrl && (
<Image
src={post.thumbnailUrl}
alt={post.title}
fill
className="object-cover"
/>
)}
{post.images.length > 1 && (
<div className="absolute top-2 right-2 bg-black bg-opacity-60 text-white text-[10px] smalltablet:text-xs px-2 py-1 rounded">
+{post.images.length - 1}
</div>
)}
</div>
<div className="p-4">
<h3 className="text-sm smalltablet:text-base font-semibold text-gray-800 truncate">{post.title}</h3>
<p className="text-xs smalltablet:text-sm text-gray-500 mt-1">
{new Date(post.createdAt).toLocaleDateString('ko-KR')}
</p>
</div>
</Link>
))}
</div>
)}
{/* Pagination */}
<Pagination
currentPage={currentPage}
totalPages={totalPages}
onPageChange={setCurrentPage}
/>
</div>
</div>
</div>
);
}