Skip to content

Commit 6ba04a9

Browse files
author
bebusl
committed
draft
1 parent 151d45f commit 6ba04a9

File tree

13 files changed

+1732
-1141
lines changed

13 files changed

+1732
-1141
lines changed

playwright.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export default defineConfig({
77
retries: process.env.CI ? 2 : 0,
88
workers: process.env.CI ? 1 : undefined,
99
reporter: process.env.CI ? "dot" : "html",
10+
timeout: 15000,
1011
use: {
1112
baseURL: "http://localhost:5173",
1213
trace: "on-first-retry",

src/__tests__/product.list.test.js

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -189,19 +189,28 @@ describe("6. 상품 검색", () => {
189189
expect(searchInput.placeholder).toMatch(//i);
190190
});
191191

192-
test("Enter 키로 검색이 수행할 수 있으며, 검색어와 일치하는 상품들만 목록에 표시된다", async () => {
193-
await screen.findByText(/ /i);
194-
195-
const searchInput = document.querySelector("#search-input");
196-
197-
await userEvent.type(searchInput, "젤리");
198-
await userEvent.keyboard("{Enter}");
199-
200-
await screen.findByText("3개");
201-
202-
const productCards = [...document.querySelectorAll(".product-card")];
203-
expect(getByRole(productCards[0], "heading", { level: 3, name: //i })).toBeInTheDocument();
204-
expect(getByRole(productCards[1], "heading", { level: 3, name: //i })).toBeInTheDocument();
205-
expect(getByRole(productCards[2], "heading", { level: 3, name: //i })).toBeInTheDocument();
206-
});
192+
test(
193+
"Enter 키로 검색이 수행할 수 있으며, 검색어와 일치하는 상품들만 목록에 표시된다",
194+
async () => {
195+
console.log("SCREEN!", screen);
196+
await screen.findByText(/ /i);
197+
198+
const searchInput = document.querySelector("#search-input");
199+
200+
await userEvent.type(searchInput, "젤리");
201+
await userEvent.keyboard("{Enter}");
202+
203+
await screen.findByText("3개");
204+
205+
const productCards = [...document.querySelectorAll(".product-card")];
206+
expect(getByRole(productCards[0], "heading", { level: 3, name: //i })).toBeInTheDocument();
207+
expect(getByRole(productCards[1], "heading", { level: 3, name: //i })).toBeInTheDocument();
208+
expect(getByRole(productCards[2], "heading", { level: 3, name: //i })).toBeInTheDocument();
209+
},
210+
{
211+
timeout: 300000,
212+
},
213+
);
207214
});
215+
216+
// 시발 테스트 안돌아가 영~ ~

src/components/cart.js

Lines changed: 349 additions & 0 deletions
Large diffs are not rendered by default.

src/components/detail.js

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
// 상세페이지_로딩: `
2+
// <div class="min-h-screen bg-gray-50">
3+
// <header class="bg-white shadow-sm sticky top-0 z-40">
4+
// <div class="max-w-md mx-auto px-4 py-4">
5+
// <div class="flex items-center justify-between">
6+
// <div class="flex items-center space-x-3">
7+
// <button onclick="window.history.back()" class="p-2 text-gray-700 hover:text-gray-900 transition-colors">
8+
// <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
9+
// <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
10+
// </svg>
11+
// </button>
12+
// <h1 class="text-lg font-bold text-gray-900">상품 상세</h1>
13+
// </div>
14+
// <div class="flex items-center space-x-2">
15+
// <!-- 장바구니 아이콘 -->
16+
// <button id="cart-icon-btn" class="relative p-2 text-gray-700 hover:text-gray-900 transition-colors">
17+
// <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
18+
// <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4m2.6 8L6 2H3m4 11v6a1 1 0 001 1h1a1 1 0 001-1v-6M13 13v6a1 1 0 001 1h1a1 1 0 001-1v-6"></path>
19+
// </svg>
20+
// </button>
21+
// </div>
22+
// </div>
23+
// </div>
24+
// </header>
25+
// <main class="max-w-md mx-auto px-4 py-4">
26+
// <div class="py-20 bg-gray-50 flex items-center justify-center">
27+
// <div class="text-center">
28+
// <div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
29+
// <p class="text-gray-600">상품 정보를 불러오는 중...</p>
30+
// </div>
31+
// </div>
32+
// </main>
33+
// <footer class="bg-white shadow-sm sticky top-0 z-40">
34+
// <div class="max-w-md mx-auto py-8 text-center text-gray-500">
35+
// <p>© 2025 항해플러스 프론트엔드 쇼핑몰</p>
36+
// </div>
37+
// </footer>
38+
// </div>
39+
// `,
40+
41+
// 상세페이지_로딩완료: `
42+
// <div class="min-h-screen bg-gray-50">
43+
// <header class="bg-white shadow-sm sticky top-0 z-40">
44+
// <div class="max-w-md mx-auto px-4 py-4">
45+
// <div class="flex items-center justify-between">
46+
// <div class="flex items-center space-x-3">
47+
// <button onclick="window.history.back()" class="p-2 text-gray-700 hover:text-gray-900 transition-colors">
48+
// <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
49+
// <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
50+
// </svg>
51+
// </button>
52+
// <h1 class="text-lg font-bold text-gray-900">상품 상세</h1>
53+
// </div>
54+
// <div class="flex items-center space-x-2">
55+
// <!-- 장바구니 아이콘 -->
56+
// <button id="cart-icon-btn" class="relative p-2 text-gray-700 hover:text-gray-900 transition-colors">
57+
// <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
58+
// <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4m2.6 8L6 2H3m4 11v6a1 1 0 001 1h1a1 1 0 001-1v-6M13 13v6a1 1 0 001 1h1a1 1 0 001-1v-6"></path>
59+
// </svg>
60+
// <span class="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full h-5 w-5 flex items-center justify-center">
61+
// 1
62+
// </span>
63+
// </button>
64+
// </div>
65+
// </div>
66+
// </div>
67+
// </header>
68+
// <main class="max-w-md mx-auto px-4 py-4">
69+
// <!-- 브레드크럼 -->
70+
// <nav class="mb-4">
71+
// <div class="flex items-center space-x-2 text-sm text-gray-600">
72+
// <a href="/" data-link="" class="hover:text-blue-600 transition-colors">홈</a>
73+
// <svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
74+
// <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
75+
// </svg>
76+
// <button class="breadcrumb-link" data-category1="생활/건강">
77+
// 생활/건강
78+
// </button>
79+
// <svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
80+
// <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
81+
// </svg>
82+
// <button class="breadcrumb-link" data-category2="생활용품">
83+
// 생활용품
84+
// </button>
85+
// </div>
86+
// </nav>
87+
// <!-- 상품 상세 정보 -->
88+
// <div class="bg-white rounded-lg shadow-sm mb-6">
89+
// <!-- 상품 이미지 -->
90+
// <div class="p-4">
91+
// <div class="aspect-square bg-gray-100 rounded-lg overflow-hidden mb-4">
92+
// <img src="https://shopping-phinf.pstatic.net/main_8506721/85067212996.1.jpg" alt="PVC 투명 젤리 쇼핑백 1호 와인 답례품 구디백 비닐 손잡이 미니 간식 선물포장" class="w-full h-full object-cover product-detail-image">
93+
// </div>
94+
// <!-- 상품 정보 -->
95+
// <div>
96+
// <p class="text-sm text-gray-600 mb-1"></p>
97+
// <h1 class="text-xl font-bold text-gray-900 mb-3">PVC 투명 젤리 쇼핑백 1호 와인 답례품 구디백 비닐 손잡이 미니 간식 선물포장</h1>
98+
// <!-- 평점 및 리뷰 -->
99+
// <div class="flex items-center mb-3">
100+
// <div class="flex items-center">
101+
// <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20">
102+
// <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"></path>
103+
// </svg>
104+
// <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20">
105+
// <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"></path>
106+
// </svg>
107+
// <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20">
108+
// <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"></path>
109+
// </svg>
110+
// <svg class="w-4 h-4 text-yellow-400" fill="currentColor" viewBox="0 0 20 20">
111+
// <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"></path>
112+
// </svg>
113+
// <svg class="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20">
114+
// <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"></path>
115+
// </svg>
116+
// </div>
117+
// <span class="ml-2 text-sm text-gray-600">4.0 (749개 리뷰)</span>
118+
// </div>
119+
// <!-- 가격 -->
120+
// <div class="mb-4">
121+
// <span class="text-2xl font-bold text-blue-600">220원</span>
122+
// </div>
123+
// <!-- 재고 -->
124+
// <div class="text-sm text-gray-600 mb-4">
125+
// 재고 107개
126+
// </div>
127+
// <!-- 설명 -->
128+
// <div class="text-sm text-gray-700 leading-relaxed mb-6">
129+
// PVC 투명 젤리 쇼핑백 1호 와인 답례품 구디백 비닐 손잡이 미니 간식 선물포장에 대한 상세 설명입니다. 브랜드의 우수한 품질을 자랑하는 상품으로, 고객 만족도가 높은 제품입니다.
130+
// </div>
131+
// </div>
132+
// </div>
133+
// <!-- 수량 선택 및 액션 -->
134+
// <div class="border-t border-gray-200 p-4">
135+
// <div class="flex items-center justify-between mb-4">
136+
// <span class="text-sm font-medium text-gray-900">수량</span>
137+
// <div class="flex items-center">
138+
// <button id="quantity-decrease" class="w-8 h-8 flex items-center justify-center border border-gray-300
139+
// rounded-l-md bg-gray-50 hover:bg-gray-100">
140+
// <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
141+
// <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 12H4"></path>
142+
// </svg>
143+
// </button>
144+
// <input type="number" id="quantity-input" value="1" min="1" max="107" class="w-16 h-8 text-center text-sm border-t border-b border-gray-300
145+
// focus:ring-1 focus:ring-blue-500 focus:border-blue-500">
146+
// <button id="quantity-increase" class="w-8 h-8 flex items-center justify-center border border-gray-300
147+
// rounded-r-md bg-gray-50 hover:bg-gray-100">
148+
// <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
149+
// <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
150+
// </svg>
151+
// </button>
152+
// </div>
153+
// </div>
154+
// <!-- 액션 버튼 -->
155+
// <button id="add-to-cart-btn" data-product-id="85067212996" class="w-full bg-blue-600 text-white py-3 px-4 rounded-md
156+
// hover:bg-blue-700 transition-colors font-medium">
157+
// 장바구니 담기
158+
// </button>
159+
// </div>
160+
// </div>
161+
// <!-- 상품 목록으로 이동 -->
162+
// <div class="mb-6">
163+
// <button class="block w-full text-center bg-gray-100 text-gray-700 py-3 px-4 rounded-md
164+
// hover:bg-gray-200 transition-colors go-to-product-list">
165+
// 상품 목록으로 돌아가기
166+
// </button>
167+
// </div>
168+
// <!-- 관련 상품 -->
169+
// <div class="bg-white rounded-lg shadow-sm">
170+
// <div class="p-4 border-b border-gray-200">
171+
// <h2 class="text-lg font-bold text-gray-900">관련 상품</h2>
172+
// <p class="text-sm text-gray-600">같은 카테고리의 다른 상품들</p>
173+
// </div>
174+
// <div class="p-4">
175+
// <div class="grid grid-cols-2 gap-3 responsive-grid">
176+
// <div class="bg-gray-50 rounded-lg p-3 related-product-card cursor-pointer" data-product-id="86940857379">
177+
// <div class="aspect-square bg-white rounded-md overflow-hidden mb-2">
178+
// <img src="https://shopping-phinf.pstatic.net/main_8694085/86940857379.1.jpg" alt="샷시 풍지판 창문 바람막이 베란다 문 틈막이 창틀 벌레 차단 샤시 방충망 틈새막이" class="w-full h-full object-cover" loading="lazy">
179+
// </div>
180+
// <h3 class="text-sm font-medium text-gray-900 mb-1 line-clamp-2">샷시 풍지판 창문 바람막이 베란다 문 틈막이 창틀 벌레 차단 샤시 방충망 틈새막이</h3>
181+
// <p class="text-sm font-bold text-blue-600">230원</p>
182+
// </div>
183+
// <div class="bg-gray-50 rounded-lg p-3 related-product-card cursor-pointer" data-product-id="82094468339">
184+
// <div class="aspect-square bg-white rounded-md overflow-hidden mb-2">
185+
// <img src="https://shopping-phinf.pstatic.net/main_8209446/82094468339.4.jpg" alt="실리카겔 50g 습기제거제 제품 /산업 신발 의류 방습제" class="w-full h-full object-cover" loading="lazy">
186+
// </div>
187+
// <h3 class="text-sm font-medium text-gray-900 mb-1 line-clamp-2">실리카겔 50g 습기제거제 제품 /산업 신발 의류 방습제</h3>
188+
// <p class="text-sm font-bold text-blue-600">280원</p>
189+
// </div>
190+
// </div>
191+
// </div>
192+
// </div>
193+
// </main>
194+
// <footer class="bg-white shadow-sm sticky top-0 z-40">
195+
// <div class="max-w-md mx-auto py-8 text-center text-gray-500">
196+
// <p>© 2025 항해플러스 프론트엔드 쇼핑몰</p>
197+
// </div>
198+
// </footer>
199+
// </div>
200+
// `,

src/components/layout.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export const header = (children) => `<header class="bg-white shadow-sm sticky top-0 z-40">${children}</header>`;
2+
export const main = (children) => `<main class="max-w-md mx-auto px-4 py-4">${children}</main>`;
3+
export const footer = () => `<footer class="bg-white shadow-sm sticky top-0 z-40">
4+
<div class="max-w-md mx-auto py-8 text-center text-gray-500">
5+
<p>© 2025 항해플러스 프론트엔드 쇼핑몰</p>
6+
</div>
7+
</footer>`;

src/components/notfound.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
export const notFound = () => {
2+
return `<div class="text-center my-4 py-20 shadow-md p-6 bg-white rounded-lg">
3+
<svg viewBox="0 0 320 180" xmlns="http://www.w3.org/2000/svg">
4+
<defs>
5+
<linearGradient id="blueGradient" x1="0%" y1="0%" x2="100%" y2="100%">
6+
<stop offset="0%" style="stop-color:#4285f4;stop-opacity:1" />
7+
<stop offset="100%" style="stop-color:#1a73e8;stop-opacity:1" />
8+
</linearGradient>
9+
<filter id="softShadow" x="-50%" y="-50%" width="200%" height="200%">
10+
<feDropShadow dx="0" dy="2" stdDeviation="8" flood-color="#000000" flood-opacity="0.1"/>
11+
</filter>
12+
</defs>
13+
14+
<!-- 404 Numbers -->
15+
<text x="160" y="85" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="48" font-weight="600" fill="url(#blueGradient)" text-anchor="middle">404</text>
16+
17+
<!-- Icon decoration -->
18+
<circle cx="80" cy="60" r="3" fill="#e8f0fe" opacity="0.8"/>
19+
<circle cx="240" cy="60" r="3" fill="#e8f0fe" opacity="0.8"/>
20+
<circle cx="90" cy="45" r="2" fill="#4285f4" opacity="0.5"/>
21+
<circle cx="230" cy="45" r="2" fill="#4285f4" opacity="0.5"/>
22+
23+
<!-- Message -->
24+
<text x="160" y="110" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="14" font-weight="400" fill="#5f6368" text-anchor="middle">페이지를 찾을 수 없습니다</text>
25+
26+
<!-- Subtle bottom accent -->
27+
<rect x="130" y="130" width="60" height="2" rx="1" fill="url(#blueGradient)" opacity="0.3"/>
28+
</svg>
29+
30+
<a href="/" data-link class="inline-block px-6 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors">홈으로</a>
31+
</div>`;
32+
};

src/components/product.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
export const productList = (products) => {
2+
let list = "";
3+
4+
if (Array.isArray(products))
5+
products.forEach((product) => {
6+
list += productItem({
7+
brand: product.brand,
8+
price: product.lprice,
9+
image: product.image,
10+
title: product.title,
11+
});
12+
});
13+
14+
return list;
15+
};
16+
17+
export const productItem = ({ brand, price, image, title }) => {
18+
return `<div class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden product-card"
19+
data-product-id="85067212996">
20+
<!-- 상품 이미지 -->
21+
<div class="aspect-square bg-gray-100 overflow-hidden cursor-pointer product-image">
22+
<img src="${image}"
23+
alt="${title}"
24+
class="w-full h-full object-cover hover:scale-105 transition-transform duration-200"
25+
loading="lazy">
26+
</div>
27+
<!-- 상품 정보 -->
28+
<div class="p-3">
29+
<div class="cursor-pointer product-info mb-3">
30+
<h3 class="text-sm font-medium text-gray-900 line-clamp-2 mb-1">
31+
${title}
32+
</h3>
33+
<p class="text-xs text-gray-500 mb-2">${brand}</p>
34+
<p class="text-lg font-bold text-gray-900">
35+
${price}
36+
</p>
37+
</div>
38+
<!-- 장바구니 버튼 -->
39+
<button class="w-full bg-blue-600 text-white text-sm py-2 px-3 rounded-md
40+
hover:bg-blue-700 transition-colors add-to-cart-btn" data-product-id="85067212996">
41+
장바구니 담기
42+
</button>
43+
</div>
44+
</div>`;
45+
};

0 commit comments

Comments
 (0)