-
Notifications
You must be signed in to change notification settings - Fork 30
[강정민] sprint3 #38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
jeongmin00
wants to merge
2
commits into
codeit-sprint-fullstack:basic-강정민
Choose a base branch
from
jeongmin00:basic-강정민-sprint3
base: basic-강정민
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
The head ref may contain hidden characters: "basic-\uAC15\uC815\uBBFC-sprint3"
Open
[강정민] sprint3 #38
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,153 +1,79 @@ | ||
| 🐼 판다마켓 (Pandamarket) | ||
|
|
||
| 일상의 모든 물건을 거래하는 중고 거래 플랫폼 랜딩 페이지 | ||
| HTML, CSS 기반 정적 웹 프로젝트 | ||
| 🐼 판다마켓 (Pandamarket) - Sprint 3 | ||
| 일상의 모든 물건을 거래하는 중고 거래 플랫폼의 Article 및 Product API 연동 프로젝트 | ||
|
|
||
| 📌 프로젝트 소개 | ||
|
|
||
| 판다마켓은 중고 물품을 쉽고 안전하게 거래할 수 있는 플랫폼을 목표로 제작된 웹 페이지입니다. | ||
| HTML + CSS만으로 구현하였습니다. | ||
| 판다마켓 서비스의 핵심 데이터인 **게시글(Article)**과 상품(Product) 데이터를 관리하기 위해 외부 API와 통신하는 서비스 로직을 구현한 프로젝트입니다. 자바스크립트의 두 가지 주요 비동기 처리 방식인 Promise(.then)와 async/await을 모두 사용하여 설계되었습니다. | ||
|
|
||
| 🛠️ 기술 스택 | ||
| JavaScript (ES6+) | ||
|
|
||
| HTML5 | ||
|
|
||
| CSS3 | ||
| Fetch API | ||
|
|
||
| Netlify | ||
|
|
||
| Google Analytics 4 | ||
| Node.js (테스트 환경) | ||
|
|
||
| 📂 프로젝트 구조 | ||
| pandamarket/ | ||
| ├── index.html | ||
| ├── login.html | ||
| ├── signup.html | ||
| ├── items.html | ||
| ├── privacy.html | ||
| ├── faq.html | ||
| ├── css/ | ||
| │ ├── reset.css | ||
| │ ├── style.css | ||
| │ ├── login.css | ||
| │ └── signup.css | ||
| └── images/ | ||
| ├── index/ | ||
| ├── login/ | ||
| └── signup/ | ||
|
|
||
| 📄 페이지 구성 | ||
|
|
||
| 1️⃣ 랜딩 페이지 (/) | ||
|
|
||
| GNB (로고, 로그인 버튼) | ||
|
|
||
| HERO 섹션 | ||
|
|
||
| 인기 상품 소개 | ||
|
|
||
| 상품 검색 안내 | ||
| Plaintext | ||
| sprint3/ | ||
| ├── src/ | ||
| │ ├── api/ | ||
| │ │ ├── ArticleService.js # .then() / .catch() 기반 서비스 | ||
| │ │ └── ProductService.js # async / await 기반 서비스 | ||
| │ └── main.js # 서비스 실행 및 API 테스트 | ||
| ├── package.json # 프로젝트 설정 및 의존성 관리 | ||
| └── README.md | ||
| ⚙️ 주요 기능 및 구현 방식 | ||
| 1️⃣ Article Service (.then() 방식) | ||
| 명세서 요구사항에 따라 Promise 체이닝 방식을 사용하여 구현되었습니다. | ||
|
|
||
| 상품 등록 안내 | ||
| 목록 조회: page, pageSize, orderBy, keyword 쿼리 파라미터 적용 | ||
|
|
||
| Footer (Privacy / FAQ / SNS 링크) | ||
| CRUD 구현: 상세 조회, 생성(POST), 수정(PATCH), 삭제(DELETE) | ||
|
|
||
| 2️⃣ 로그인 페이지 (/login) | ||
| 에러 핸들링: .catch() 블록을 통해 네트워크 오류 및 상태 코드(2XX 아님) 예외 처리 | ||
|
|
||
| 이메일 입력 | ||
| 2️⃣ Product Service (async/await 방식) | ||
| 가독성이 높은 현대적 비동기 문법을 사용하여 구현되었습니다. | ||
|
|
||
| 비밀번호 입력 | ||
| 목록 조회: 정렬 기준(recent, favorite)을 포함한 상품 데이터 호출 | ||
|
|
||
| 간편 로그인 (Google / Kakao) | ||
| CRUD 구현: 상품 등록, 상세 정보 수신, 정보 업데이트, 상품 제거 | ||
|
|
||
| 회원가입 이동 링크 | ||
| 에러 핸들링: try-catch 문을 사용하여 비동기 로직의 안정성 확보 | ||
|
|
||
| 3️⃣ 회원가입 페이지 (/signup) | ||
| 3️⃣ 공통 구현 포인트 | ||
| URLSearchParams: 복잡한 쿼리 스트링을 객체 기반으로 안전하게 생성 | ||
|
|
||
| 이메일 | ||
| Request Headers: JSON 데이터 전송을 위한 Content-Type: application/json 명시 | ||
|
|
||
| 닉네임 | ||
| Error Validation: response.ok를 체크하여 HTTP 에러 상태(4xx, 5xx)를 명시적으로 처리 | ||
|
|
||
| 비밀번호 | ||
| 🎨 코드 예시 | ||
| [Product List 조회 - async/await] | ||
| JavaScript | ||
| export const getProductList = async (page = 1, pageSize = 10, orderBy = "recent", keyword = "") => { | ||
| try { | ||
| const params = new URLSearchParams({ page, pageSize, orderBy, keyword }); | ||
| const response = await fetch(`${BASE_URL}?${params.toString()}`); | ||
|
|
||
| 비밀번호 확인 | ||
| if (!response.ok) throw new Error(`목록 조회 실패: ${response.status}`); | ||
| return await response.json(); | ||
|
|
||
| 간편 로그인 | ||
|
|
||
| 🎨 CSS 설계 방식 | ||
|
|
||
| 1️⃣ CSS 변수 활용 | ||
|
|
||
| Palette에 정의된 색상값을 :root에 CSS 변수로 등록하여 사용하였습니다. | ||
|
|
||
| :root { | ||
| --primary: #3692ff; | ||
| --text: #111827; | ||
| --muted: #6b7280; | ||
| --input-bg: #f3f4f6; | ||
| --social-bg: #e6f2ff; | ||
| } catch (error) { | ||
| console.error(error.message); | ||
| } | ||
| }; | ||
| 🚀 실행 방법 | ||
| 프로젝트 폴더로 이동 | ||
|
|
||
| → 유지보수성과 재사용성을 높이기 위한 설계입니다. | ||
|
|
||
| 2️⃣ Flexbox 기반 레이아웃 | ||
|
|
||
| display: flex | ||
|
|
||
| justify-content | ||
|
|
||
| align-items | ||
|
|
||
| gap | ||
|
|
||
| 을 활용하여 반응형에 유연한 구조를 설계하였습니다. | ||
| Bash | ||
| cd sprint3 | ||
| 의존성 설치 (필요시) | ||
|
|
||
| 3️⃣ 시맨틱 태그 사용 | ||
|
|
||
| <main> | ||
|
|
||
| <section> | ||
|
|
||
| <nav> | ||
|
|
||
| <footer> | ||
|
|
||
| 를 사용하여 웹 접근성과 구조적 의미를 고려하였습니다. | ||
|
|
||
| 📊 Google Analytics 설정 | ||
|
|
||
| 방문자 수 확인을 위해 **GA4 (Google Analytics 4)**를 연동하였습니다. | ||
|
|
||
| <script async src="https://www.googletagmanager.com/gtag/js?id=G-Q8XC4C0YZ1""></script> | ||
| <script> | ||
| window.dataLayer = window.dataLayer || []; | ||
| function gtag(){dataLayer.push(arguments);} | ||
| gtag('js', new Date()); | ||
| gtag('config', 'G-Q8XC4C0YZ1"'); | ||
| </script> | ||
|
|
||
| 실시간 보고서에서 방문자 확인 가능 | ||
|
|
||
| 🚀 배포 | ||
|
|
||
| Netlify를 통해 배포 | ||
|
|
||
| 루트 경로(/)를 랜딩 페이지로 설정 | ||
|
|
||
| ✨ 주요 구현 포인트 | ||
|
|
||
| UI 라이브러리 없이 순수 HTML/CSS로 구현 | ||
|
|
||
| CSS 변수로 색상 통합 관리 | ||
|
|
||
| 버튼 및 링크에 cursor: pointer 적용 | ||
|
|
||
| 간편 로그인 아이콘 정렬 개선 | ||
|
|
||
| shrink 방지 설계 | ||
|
|
||
| 접근성을 고려한 aria-label 사용 | ||
| Bash | ||
| npm install | ||
| 테스트 실행 | ||
|
|
||
| Bash | ||
| npm start | ||
| 👩💻 제작자 | ||
|
|
||
| 정민 |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| { | ||
| "name": "sprint3", | ||
| "version": "1.0.0", | ||
| "type": "module", | ||
| "scripts": { | ||
| "start": "node src/main.js" | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| import { API_ENDPOINTS } from "../config/index.js"; | ||
| import { request } from "./request.js"; | ||
|
|
||
| const BASE_URL = API_ENDPOINTS.articles; | ||
|
|
||
| /** | ||
| * 1. 게시글 목록 조회 (GET /articles) | ||
| * 파라미터: page, pageSize, orderBy(recent/like), keyword | ||
| */ | ||
| export const getArticleList = ( | ||
| page = 1, | ||
| pageSize = 10, | ||
| orderBy = "recent", | ||
| keyword = "", | ||
| ) => { | ||
| const params = new URLSearchParams({ page, pageSize, orderBy }); | ||
|
|
||
| if (keyword) { | ||
| params.append("keyword", keyword); | ||
| } | ||
|
|
||
| return request(`${BASE_URL}?${params.toString()}`); | ||
| }; | ||
|
|
||
| /** | ||
| * 2. 게시글 생성 (POST /articles) | ||
| * Request body: image, content, title | ||
| */ | ||
| export const createArticle = (articleData) => { | ||
| return request(BASE_URL, { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify(articleData), | ||
| }); | ||
| }; | ||
|
|
||
| /** | ||
| * 3. 게시글 상세 조회 (GET /articles/{articleId}) | ||
| */ | ||
| export const getArticle = (articleId) => { | ||
| if (!articleId) { | ||
| throw new Error("articleId is required"); | ||
| } | ||
| return request(`${BASE_URL}/${articleId}`); | ||
| }; | ||
|
|
||
| /** | ||
| * 4. 게시글 수정 (PATCH /articles/{articleId}) | ||
| */ | ||
| export const patchArticle = (articleId, updateData) => { | ||
| return request(`${BASE_URL}/${articleId}`, { | ||
| method: "PATCH", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify(updateData), | ||
| }); | ||
| }; | ||
|
|
||
| /** | ||
| * 5. 게시글 삭제 (DELETE /articles/{articleId}) | ||
| */ | ||
| export const deleteArticle = (articleId) => { | ||
| return request(`${BASE_URL}/${articleId}`, { | ||
| method: "DELETE", | ||
| }); | ||
| }; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| import { API_ENDPOINTS } from "../config/index.js"; | ||
| import { request } from "./request.js"; | ||
|
|
||
| const BASE_URL = API_ENDPOINTS.products; | ||
|
|
||
| /** | ||
| * 1. 상품 목록 조회 (GET) | ||
| */ | ||
| export const getProductList = async ( | ||
| page = 1, | ||
| pageSize = 10, | ||
| orderBy = "recent", | ||
| keyword = "", | ||
| ) => { | ||
| const params = new URLSearchParams({ page, pageSize, orderBy }); | ||
| if (keyword) params.append("keyword", keyword); | ||
| return request(`${BASE_URL}?${params.toString()}`); | ||
| }; | ||
|
|
||
| /** | ||
| * 2. 상품 생성 (POST) | ||
| */ | ||
| export const createProduct = async (productData) => { | ||
| return request(BASE_URL, { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify(productData), | ||
| }); | ||
| }; | ||
|
|
||
| /** | ||
| * 3. 상품 상세 조회 (GET) | ||
| */ | ||
| export const getProduct = async (productId) => { | ||
| if (!productId) { | ||
| throw new Error("productId is required"); | ||
| } | ||
| return request(`${BASE_URL}/${productId}`); | ||
| }; | ||
|
|
||
| /** | ||
| * 4. 상품 수정 (PATCH) | ||
| */ | ||
| export const patchProduct = async (productId, updateData) => { | ||
| return request(`${BASE_URL}/${productId}`, { | ||
| method: "PATCH", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify(updateData), | ||
| }); | ||
| }; | ||
|
|
||
| /** | ||
| * 5. 상품 삭제 (DELETE) | ||
| */ | ||
| export const deleteProduct = async (productId) => { | ||
| return request(`${BASE_URL}/${productId}`, { | ||
| method: "DELETE", | ||
| }); | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| export const request = async (url, options = {}) => { | ||
| try { | ||
| const response = await fetch(url, options); | ||
| if (!response.ok) { | ||
| const errorMessage = await response.text(); | ||
| throw new Error( | ||
| `HTTP 오류: ${response.status}\n상세 오류: ${errorMessage}`, | ||
| ); | ||
| } | ||
| return await response.json(); | ||
| } catch (error) { | ||
| console.error(error.message); | ||
| throw error; | ||
| } | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| export const API_BASE_URL = 'https://panda-market-api-crud.vercel.app'; | ||
|
|
||
| export const API_ENDPOINTS = { | ||
| articles: `${API_BASE_URL}/articles`, | ||
| products: `${API_BASE_URL}/products`, | ||
| }; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2
(방어적 프로그래밍) 인자로 넘어오는
articleId가 누락되거나 의도치 않게undefined일 경우URL/.../undefined엔드포인트로 통신하게 되어 예상치 못한 에러를 유발합니다. 로직 상단에if (!articleId) throw new Error('articleId is required');와 같은 Guard Clause(보호 문구)를 사전에 추가해 주시면 훨씬 견고한 함수가 될 것 같습니다!