Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: Deploy to GitHub Pages

on:
push:
branches:
- easy
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: "pages"
cancel-in-progress: true

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: latest

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "22"
cache: "pnpm"

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build
run: pnpm run build

- name: Create 404.html for SPA routing
run: |
cp ./dist/index.html ./dist/404.html
- name: Setup Pages
uses: actions/configure-pages@v4

- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: "./dist"

- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
23 changes: 23 additions & 0 deletions 404.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Page Not Found</title>
<script>
// SPA 라우팅을 위한 리다이렉트
// GitHub Pages에서 존재하지 않는 경로 접근 시 404.html로 이동
// 이를 index.html로 리다이렉트하여 React Router가 처리하도록 함
(function() {
var redirect = sessionStorage.redirect;
delete sessionStorage.redirect;
if (redirect && redirect != location.href) {
history.replaceState(null, null, redirect);
}
})();
</script>
</head>
<body>
<!-- 리다이렉트 중이므로 body 내용은 보이지 않음 -->
</body>
</html>
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"test:e2e:report": "npx playwright show-report",
"test:generate": "playwright codegen localhost:5173",
"prepare": "husky",
"gh-pages": "pnpm run build && gh-pages -d dist"
"deploy": "pnpm run build && gh-pages -d dist"
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
Expand Down
56 changes: 46 additions & 10 deletions src/components/ProductList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,27 @@ const goToDetailPage = async (productId) => {
/**
* 상품 목록 컴포넌트
*/
export function ProductList({ products = [], loading = false, error = null, totalCount = 0, hasMore = true }) {
export function ProductList({
products = [],
loading = false,
error = null,
totalCount = 0,
hasMore = true,
}) {
// 에러 상태
if (error) {
return (
<div className="text-center py-12">
<div className="text-red-500 mb-4">
<PublicImage src="/error-large-icon.svg" alt="오류" className="mx-auto h-12 w-12" />
<PublicImage
src="/error-large-icon.svg"
alt="오류"
className="mx-auto h-12 w-12"
/>
</div>
<h3 className="text-lg font-medium text-gray-900 mb-2">오류가 발생했습니다</h3>
<h3 className="text-lg font-medium text-gray-900 mb-2">
오류가 발생했습니다
</h3>
<p className="text-gray-600 mb-4">{error}</p>
<button
id="retry-btn"
Expand All @@ -47,9 +59,15 @@ export function ProductList({ products = [], loading = false, error = null, tota
return (
<div className="text-center py-12">
<div className="text-gray-400 mb-4">
<PublicImage src="/search-large-icon.svg" alt="검색" className="mx-auto h-12 w-12" />
<PublicImage
src="/search-large-icon.svg"
alt="검색"
className="mx-auto h-12 w-12"
/>
</div>
<h3 className="text-lg font-medium text-gray-900 mb-2">상품을 찾을 수 없습니다</h3>
<h3 className="text-lg font-medium text-gray-900 mb-2">
상품을 찾을 수 없습니다
</h3>
<p className="text-gray-600">다른 검색어를 시도해보세요.</p>
</div>
);
Expand All @@ -60,33 +78,51 @@ export function ProductList({ products = [], loading = false, error = null, tota
{/* 상품 개수 정보 */}
{totalCount > 0 && (
<div className="mb-4 text-sm text-gray-600">
총 <span className="font-medium text-gray-900">{totalCount.toLocaleString()}개</span>의 상품
총{" "}
<span className="font-medium text-gray-900">
{totalCount.toLocaleString()}개
</span>
의 상품
</div>
)}

{/* 상품 그리드 */}
<div className="grid grid-cols-2 gap-4 mb-6" id="products-grid">
{/* 로딩 스켈레톤 */}
{/* 상품 카드 */}
{products.map((product) => (
<ProductCard {...product} onClick={goToDetailPage} />
))}

{loading && Array.from({ length: 6 }).map(() => <ProductCardSkeleton />)}
{/* 무한 스크롤 로딩: 상품이 있고 로딩 중일 때만 */}
{loading &&
products.length > 0 &&
Array.from({ length: 6 }).map(() => <ProductCardSkeleton />)}

{/* 초기 로딩: 상품이 없고 로딩 중일 때만 */}
{loading &&
products.length === 0 &&
Array.from({ length: 6 }).map(() => <ProductCardSkeleton />)}
</div>

{/* 무한 스크롤 로딩 */}
{loading && products.length > 0 && (
<div className="text-center py-4">
<div className="inline-flex items-center">
<PublicImage src="/loading-icon.svg" alt="로딩" className="animate-spin h-5 w-5 text-blue-600 mr-2" />
<PublicImage
src="/loading-icon.svg"
alt="로딩"
className="animate-spin h-5 w-5 text-blue-600 mr-2"
/>
<span className="text-sm text-gray-600">상품을 불러오는 중...</span>
</div>
</div>
)}

{/* 더 이상 로드할 상품이 없음 */}
{!hasMore && products.length > 0 && !loading && (
<div className="text-center py-4 text-sm text-gray-500">모든 상품을 확인했습니다</div>
<div className="text-center py-4 text-sm text-gray-500">
모든 상품을 확인했습니다
</div>
)}

{/* 무한 스크롤 트리거 */}
Expand Down
Loading