+
Date: Sat, 26 Oct 2024 02:46:28 +0900
Subject: [PATCH 06/11] feat: Add board api ts
---
src/apis/boardTypes.ts | 27 +++++++++++++++++++++++++++
src/apis/boardsApi.ts | 39 +++++++++++++++++++++++++++++++++++++++
2 files changed, 66 insertions(+)
create mode 100644 src/apis/boardTypes.ts
create mode 100644 src/apis/boardsApi.ts
diff --git a/src/apis/boardTypes.ts b/src/apis/boardTypes.ts
new file mode 100644
index 000000000..4dc8c93f5
--- /dev/null
+++ b/src/apis/boardTypes.ts
@@ -0,0 +1,27 @@
+export interface Writer {
+ id: number;
+ nickname: string;
+}
+
+export interface Board {
+ id: number;
+ title: string;
+ content: string;
+ image: string | null;
+ likeCount: number;
+ createdAt: string;
+ updatedAt: string;
+ writer: Writer;
+}
+
+export interface GetBoardsResponse {
+ list: Board[];
+ totalCount: number;
+}
+
+export interface GetBoardsRequestParams {
+ page?: number;
+ pageSize?: number;
+ orderBy?: 'recent' | 'like';
+ keyword?: string;
+}
diff --git a/src/apis/boardsApi.ts b/src/apis/boardsApi.ts
new file mode 100644
index 000000000..7c4165183
--- /dev/null
+++ b/src/apis/boardsApi.ts
@@ -0,0 +1,39 @@
+import { Board, GetBoardsResponse, GetBoardsRequestParams } from './boardTypes';
+
+const BASE_URL = 'https://panda-market-api.vercel.app';
+
+async function fetchFromAPI
(endpoint: string): Promise {
+ try {
+ const response = await fetch(endpoint);
+ if (!response.ok) {
+ throw new Error(
+ `HTTP error! Status: ${response.status} / response: ${response}`
+ );
+ }
+
+ return await response.json();
+ } catch (error) {
+ console.error('Failed to fetch data:', error);
+ throw error;
+ }
+}
+
+export async function getBoards({
+ page = 1,
+ pageSize = 10,
+ orderBy = 'recent',
+ keyword,
+}: GetBoardsRequestParams) {
+ const queryParams = new URLSearchParams({
+ page: page.toString(),
+ pageSize: pageSize.toString(),
+ orderBy,
+ });
+
+ if (keyword) {
+ queryParams.append('keyword', keyword);
+ }
+
+ const endpoint = `${BASE_URL}/products?${queryParams.toString()}`;
+ return await fetchFromAPI(endpoint);
+}
From d965f48061baab34b2ff6484c2d02c726eb4a006 Mon Sep 17 00:00:00 2001
From: heejin
Date: Sat, 26 Oct 2024 03:48:37 +0900
Subject: [PATCH 07/11] feat: Add BestBoardCard structure and css
---
pages/api/hello.ts | 13 ---
.../components/BestBoardCard.module.css | 80 +++++++++++++++++++
pages/boards/components/BestBoardCard.tsx | 36 +++++++++
pages/boards/components/BestBoards.module.css | 20 +++++
pages/boards/components/BestBoards.tsx | 12 +++
pages/boards/index.tsx | 8 +-
{public/images => src/assets}/avatar.svg | 0
src/assets/ic_heart.svg | 5 ++
src/assets/ic_medal.svg | 4 +
src/components/Header.tsx | 3 +-
styles/globals.css | 2 +-
11 files changed, 167 insertions(+), 16 deletions(-)
delete mode 100644 pages/api/hello.ts
create mode 100644 pages/boards/components/BestBoardCard.module.css
create mode 100644 pages/boards/components/BestBoardCard.tsx
create mode 100644 pages/boards/components/BestBoards.module.css
create mode 100644 pages/boards/components/BestBoards.tsx
rename {public/images => src/assets}/avatar.svg (100%)
create mode 100644 src/assets/ic_heart.svg
create mode 100644 src/assets/ic_medal.svg
diff --git a/pages/api/hello.ts b/pages/api/hello.ts
deleted file mode 100644
index f8bcc7e5c..000000000
--- a/pages/api/hello.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
-import type { NextApiRequest, NextApiResponse } from 'next'
-
-type Data = {
- name: string
-}
-
-export default function handler(
- req: NextApiRequest,
- res: NextApiResponse
-) {
- res.status(200).json({ name: 'John Doe' })
-}
diff --git a/pages/boards/components/BestBoardCard.module.css b/pages/boards/components/BestBoardCard.module.css
new file mode 100644
index 000000000..169ff0d5c
--- /dev/null
+++ b/pages/boards/components/BestBoardCard.module.css
@@ -0,0 +1,80 @@
+.bestBoardCardContainer {
+ border-radius: 8px;
+ background: var(--Secondary-50, #f9fafb);
+ padding: 0px 24px;
+ padding-bottom: 15px;
+
+ display: flex;
+ flex-direction: column;
+ gap: 18px;
+}
+
+.bestBoardBadge {
+ width: 102px;
+ height: 30px;
+ text-align: center;
+ padding: 2px 24px;
+ background: var(--Primary-100, #3692ff);
+ border-radius: 0px 0px 16px 16px;
+
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+.bestText {
+ color: #fff;
+ font-size: 16px;
+ font-weight: 600;
+}
+
+.contentContainer {
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
+ flex-wrap: wrap;
+}
+
+.contentTitle {
+ color: var(--Secondary-800, #1f2937);
+ font-size: 20px;
+ font-weight: 600;
+ max-width: 256px;
+}
+
+.contentImgWrapper {
+ position: relative;
+ width: 72px;
+ height: 72px;
+
+ border-radius: 6px;
+ border: 1px solid var(--Secondary-200, #e5e7eb);
+ background: #fff;
+}
+
+.additionalInfo {
+ color: var(--Secondary-600, #4b5563);
+ font-size: 14px;
+ font-weight: 400;
+
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 8px;
+}
+
+.likeCountWrapper {
+ flex: 1;
+
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+/* tablet */
+@media screen and (max-width: 1199px) {
+ .contentTitle {
+ gap: 40px;
+ max-width: 180px;
+ }
+}
diff --git a/pages/boards/components/BestBoardCard.tsx b/pages/boards/components/BestBoardCard.tsx
new file mode 100644
index 000000000..8930f1c1a
--- /dev/null
+++ b/pages/boards/components/BestBoardCard.tsx
@@ -0,0 +1,36 @@
+import Image from 'next/image';
+import styles from './BestBoardCard.module.css';
+import medalSvg from '@/src/assets/ic_medal.svg';
+import heardSvg from '@/src/assets/ic_heart.svg';
+
+export default function BestBoardCard() {
+ return (
+
+
+
+ Best
+
+
+
+ 맥북 16인치 16기가 1테라 정도 사양이면 얼마에 팔아야하나요?
+
+
+
+
+
+
+
총명한판다
+
+
+ 9999+
+
+
2024. 04. 16
+
+
+ );
+}
diff --git a/pages/boards/components/BestBoards.module.css b/pages/boards/components/BestBoards.module.css
new file mode 100644
index 000000000..f9f30b50e
--- /dev/null
+++ b/pages/boards/components/BestBoards.module.css
@@ -0,0 +1,20 @@
+.bestBoards {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 24px;
+}
+
+/* tablet */
+@media screen and (max-width: 1199px) {
+ .bestBoards {
+ grid-template-columns: repeat(2, 1fr);
+ gap: 16px;
+ }
+}
+
+/* mobile */
+@media screen and (max-width: 767px) {
+ .bestBoards {
+ grid-template-columns: repeat(1, 1fr);
+ }
+}
diff --git a/pages/boards/components/BestBoards.tsx b/pages/boards/components/BestBoards.tsx
new file mode 100644
index 000000000..a2f1962c6
--- /dev/null
+++ b/pages/boards/components/BestBoards.tsx
@@ -0,0 +1,12 @@
+import BestBoardCard from './BestBoardCard';
+import styles from './BestBoards.module.css';
+
+export default function BestBoards() {
+ return (
+
+
+
+
+
+ );
+}
diff --git a/pages/boards/index.tsx b/pages/boards/index.tsx
index 995ecddd5..4b76b887c 100644
--- a/pages/boards/index.tsx
+++ b/pages/boards/index.tsx
@@ -1,3 +1,9 @@
+import BestBoards from './components/BestBoards';
+
export default function BoardsIndex() {
- return boardIndex
;
+ return (
+
+
+
+ );
}
diff --git a/public/images/avatar.svg b/src/assets/avatar.svg
similarity index 100%
rename from public/images/avatar.svg
rename to src/assets/avatar.svg
diff --git a/src/assets/ic_heart.svg b/src/assets/ic_heart.svg
new file mode 100644
index 000000000..2d4338bfc
--- /dev/null
+++ b/src/assets/ic_heart.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/assets/ic_medal.svg b/src/assets/ic_medal.svg
new file mode 100644
index 000000000..d650c4019
--- /dev/null
+++ b/src/assets/ic_medal.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/components/Header.tsx b/src/components/Header.tsx
index 4bfbbcbac..1f5362bae 100644
--- a/src/components/Header.tsx
+++ b/src/components/Header.tsx
@@ -1,6 +1,7 @@
import Link from 'next/link';
import styles from './Header.module.css';
import Image from 'next/image';
+import avatarSvg from '@/src/assets/avatar.svg';
export default function Header() {
return (
@@ -16,7 +17,7 @@ export default function Header() {
중고마켓
-
+
diff --git a/styles/globals.css b/styles/globals.css
index e16587f59..14b366aeb 100644
--- a/styles/globals.css
+++ b/styles/globals.css
@@ -15,7 +15,7 @@
--Secondary-100: #f3f4f6;
--Secondary-50: #f9fafb;
--error-red: #f74747;
- --size-max-width: 1140px;
+ --size-max-width: 1200px;
}
* {
From 455b5b37875a74ec49458e0e462eeb7e52cc5935 Mon Sep 17 00:00:00 2001
From: heejin
Date: Sat, 26 Oct 2024 03:54:01 +0900
Subject: [PATCH 08/11] feat: Add bestBoards title
---
pages/boards/components/BestBoards.module.css | 12 ++++++++++++
pages/boards/components/BestBoards.tsx | 13 ++++++++-----
2 files changed, 20 insertions(+), 5 deletions(-)
diff --git a/pages/boards/components/BestBoards.module.css b/pages/boards/components/BestBoards.module.css
index f9f30b50e..303c97577 100644
--- a/pages/boards/components/BestBoards.module.css
+++ b/pages/boards/components/BestBoards.module.css
@@ -4,6 +4,14 @@
gap: 24px;
}
+.title {
+ color: var(--Secondary-900, #111827);
+ font-size: 20px;
+ font-weight: 700;
+
+ padding: 24px 0;
+}
+
/* tablet */
@media screen and (max-width: 1199px) {
.bestBoards {
@@ -17,4 +25,8 @@
.bestBoards {
grid-template-columns: repeat(1, 1fr);
}
+
+ .title {
+ padding: 16px 0;
+ }
}
diff --git a/pages/boards/components/BestBoards.tsx b/pages/boards/components/BestBoards.tsx
index a2f1962c6..349ca3091 100644
--- a/pages/boards/components/BestBoards.tsx
+++ b/pages/boards/components/BestBoards.tsx
@@ -3,10 +3,13 @@ import styles from './BestBoards.module.css';
export default function BestBoards() {
return (
-
-
-
-
-
+
);
}
From 616c5fe90dae465bbf44f1ef5d214a7ae749b81d Mon Sep 17 00:00:00 2001
From: heejin
Date: Sat, 26 Oct 2024 04:27:41 +0900
Subject: [PATCH 09/11] feat: Add BestBoard api call with prefetch
---
next.config.js | 7 +++--
pages/boards/components/BestBoardCard.tsx | 32 ++++++++++++++---------
pages/boards/components/BestBoards.tsx | 13 ++++++---
pages/boards/index.tsx | 26 ++++++++++++++++--
src/apis/boardTypes.ts | 2 +-
src/apis/boardsApi.ts | 2 +-
6 files changed, 59 insertions(+), 23 deletions(-)
diff --git a/next.config.js b/next.config.js
index a843cbee0..da265d11c 100644
--- a/next.config.js
+++ b/next.config.js
@@ -1,6 +1,9 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
-}
+ images: {
+ domains: ['sprint-fe-project.s3.ap-northeast-2.amazonaws.com'],
+ },
+};
-module.exports = nextConfig
+module.exports = nextConfig;
diff --git a/pages/boards/components/BestBoardCard.tsx b/pages/boards/components/BestBoardCard.tsx
index 8930f1c1a..555d74968 100644
--- a/pages/boards/components/BestBoardCard.tsx
+++ b/pages/boards/components/BestBoardCard.tsx
@@ -2,8 +2,21 @@ import Image from 'next/image';
import styles from './BestBoardCard.module.css';
import medalSvg from '@/src/assets/ic_medal.svg';
import heardSvg from '@/src/assets/ic_heart.svg';
+import { Board } from '@/src/apis/boardTypes';
-export default function BestBoardCard() {
+interface BestBoardCardProps
+ extends Pick<
+ Board,
+ 'title' | 'image' | 'writer' | 'likeCount' | 'createdAt'
+ > {}
+
+export default function BestBoardCard({
+ title,
+ image,
+ writer,
+ likeCount,
+ createdAt,
+}: BestBoardCardProps) {
return (
@@ -11,25 +24,18 @@ export default function BestBoardCard() {
Best
-
- 맥북 16인치 16기가 1테라 정도 사양이면 얼마에 팔아야하나요?
-
+
{title}
-
+
-
총명한판다
+
{writer.nickname}
- 9999+
+ {likeCount}
-
2024. 04. 16
+
{new Date(createdAt).toLocaleDateString()}
);
diff --git a/pages/boards/components/BestBoards.tsx b/pages/boards/components/BestBoards.tsx
index 349ca3091..5a81735eb 100644
--- a/pages/boards/components/BestBoards.tsx
+++ b/pages/boards/components/BestBoards.tsx
@@ -1,14 +1,19 @@
+import { Board } from '@/src/apis/boardTypes';
import BestBoardCard from './BestBoardCard';
import styles from './BestBoards.module.css';
-export default function BestBoards() {
+interface BestBoardsProps {
+ boards: Board[];
+}
+
+export default function BestBoards({ boards }: BestBoardsProps) {
return (
베스트 게시글
-
-
-
+ {boards.map((board) => (
+
+ ))}
);
diff --git a/pages/boards/index.tsx b/pages/boards/index.tsx
index 4b76b887c..5a408add3 100644
--- a/pages/boards/index.tsx
+++ b/pages/boards/index.tsx
@@ -1,9 +1,31 @@
+import { GetStaticProps } from 'next';
import BestBoards from './components/BestBoards';
+import { Board } from '@/src/apis/boardTypes';
+import { getBoards } from '@/src/apis/boardsApi';
-export default function BoardsIndex() {
+interface BoardsIndexProps {
+ boards: Board[];
+}
+
+export default function BoardsIndex({ boards }: BoardsIndexProps) {
return (
-
+
);
}
+
+export const getStaticProps: GetStaticProps = async () => {
+ const { list } = await getBoards({
+ page: 1,
+ pageSize: 3,
+ orderBy: 'like',
+ });
+
+ return {
+ props: {
+ boards: list || [],
+ },
+ revalidate: 600, // Re-generate the page every 600 seconds (ISR)
+ };
+};
diff --git a/src/apis/boardTypes.ts b/src/apis/boardTypes.ts
index 4dc8c93f5..e44f3fdff 100644
--- a/src/apis/boardTypes.ts
+++ b/src/apis/boardTypes.ts
@@ -7,7 +7,7 @@ export interface Board {
id: number;
title: string;
content: string;
- image: string | null;
+ image: string; // TODO image null 처리
likeCount: number;
createdAt: string;
updatedAt: string;
diff --git a/src/apis/boardsApi.ts b/src/apis/boardsApi.ts
index 7c4165183..d0b14090f 100644
--- a/src/apis/boardsApi.ts
+++ b/src/apis/boardsApi.ts
@@ -34,6 +34,6 @@ export async function getBoards({
queryParams.append('keyword', keyword);
}
- const endpoint = `${BASE_URL}/products?${queryParams.toString()}`;
+ const endpoint = `${BASE_URL}/articles?${queryParams.toString()}`;
return await fetchFromAPI(endpoint);
}
From 31ca3b381b2c6fbc66dac14dcbf9fa9093ae1f7f Mon Sep 17 00:00:00 2001
From: heejin
Date: Sat, 26 Oct 2024 04:55:06 +0900
Subject: [PATCH 10/11] fix: Add hiding additional rows for bestBoards on
mobile, tablet
---
pages/boards/components/BestBoards.module.css | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/pages/boards/components/BestBoards.module.css b/pages/boards/components/BestBoards.module.css
index 303c97577..aac4c7461 100644
--- a/pages/boards/components/BestBoards.module.css
+++ b/pages/boards/components/BestBoards.module.css
@@ -18,6 +18,10 @@
grid-template-columns: repeat(2, 1fr);
gap: 16px;
}
+
+ .bestBoards > *:nth-child(n + 3) {
+ display: none;
+ }
}
/* mobile */
@@ -29,4 +33,8 @@
.title {
padding: 16px 0;
}
+
+ .bestBoards > *:nth-child(n + 2) {
+ display: none;
+ }
}
From deb77253293af442935c975f7f7c0555a84fa1ef Mon Sep 17 00:00:00 2001
From: heejin
Date: Sat, 26 Oct 2024 05:15:43 +0900
Subject: [PATCH 11/11] feat: Add button component and boards header
---
pages/boards/components/BestBoards.module.css | 3 ++
pages/boards/components/Boards.module.css | 46 +++++++++++++++++++
pages/boards/components/Boards.tsx | 26 +++++++++++
pages/boards/index.tsx | 2 +
src/assets/ic_search.svg | 5 ++
src/components/Button.module.css | 12 +++++
src/components/Button.tsx | 19 ++++++++
7 files changed, 113 insertions(+)
create mode 100644 pages/boards/components/Boards.module.css
create mode 100644 pages/boards/components/Boards.tsx
create mode 100644 src/assets/ic_search.svg
create mode 100644 src/components/Button.module.css
create mode 100644 src/components/Button.tsx
diff --git a/pages/boards/components/BestBoards.module.css b/pages/boards/components/BestBoards.module.css
index aac4c7461..9c335b01e 100644
--- a/pages/boards/components/BestBoards.module.css
+++ b/pages/boards/components/BestBoards.module.css
@@ -2,6 +2,8 @@
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
+
+ margin-bottom: 40px;
}
.title {
@@ -17,6 +19,7 @@
.bestBoards {
grid-template-columns: repeat(2, 1fr);
gap: 16px;
+ margin-bottom: 24px;
}
.bestBoards > *:nth-child(n + 3) {
diff --git a/pages/boards/components/Boards.module.css b/pages/boards/components/Boards.module.css
new file mode 100644
index 000000000..fdc84945d
--- /dev/null
+++ b/pages/boards/components/Boards.module.css
@@ -0,0 +1,46 @@
+.boardsHeader {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 24px;
+}
+
+.title {
+ color: var(--Secondary-800, #1f2937);
+ font-size: 20px;
+ font-weight: 700;
+}
+
+.searchBar {
+ padding: 9px 20px 9px 16px;
+ border-radius: 12px;
+ background: var(--Secondary-100, #f3f4f6);
+
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+.searchData {
+ font-size: 16px;
+ font-weight: 400;
+ background: transparent;
+}
+
+.searchData::placeholder {
+ color: var(--Secondary-400, #9ca3af);
+}
+
+/* tablet */
+@media screen and (max-width: 1199px) {
+ .boardsHeader {
+ margin-bottom: 48px;
+ }
+}
+
+/* mobile */
+@media screen and (max-width: 767px) {
+ .boardsHeader {
+ margin-bottom: 16px;
+ }
+}
diff --git a/pages/boards/components/Boards.tsx b/pages/boards/components/Boards.tsx
new file mode 100644
index 000000000..1d4d84956
--- /dev/null
+++ b/pages/boards/components/Boards.tsx
@@ -0,0 +1,26 @@
+import Image from 'next/image';
+import Button from '@/src/components/Button';
+import styles from './Boards.module.css';
+import searchSvg from '@/src/assets/ic_search.svg';
+
+export default function Boards() {
+ return (
+
+ );
+}
diff --git a/pages/boards/index.tsx b/pages/boards/index.tsx
index 5a408add3..5e584f2c6 100644
--- a/pages/boards/index.tsx
+++ b/pages/boards/index.tsx
@@ -2,6 +2,7 @@ import { GetStaticProps } from 'next';
import BestBoards from './components/BestBoards';
import { Board } from '@/src/apis/boardTypes';
import { getBoards } from '@/src/apis/boardsApi';
+import Boards from './components/Boards';
interface BoardsIndexProps {
boards: Board[];
@@ -11,6 +12,7 @@ export default function BoardsIndex({ boards }: BoardsIndexProps) {
return (
+
);
}
diff --git a/src/assets/ic_search.svg b/src/assets/ic_search.svg
new file mode 100644
index 000000000..e8b451e48
--- /dev/null
+++ b/src/assets/ic_search.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/components/Button.module.css b/src/components/Button.module.css
new file mode 100644
index 000000000..f01db8780
--- /dev/null
+++ b/src/components/Button.module.css
@@ -0,0 +1,12 @@
+.primaryButton {
+ border-radius: 8px;
+ background: var(--Primary-100, #3692ff);
+
+ color: #fff;
+ font-size: 16px;
+ font-weight: 600;
+
+ padding: 12px 23px;
+ width: 100%;
+ height: 100%;
+}
diff --git a/src/components/Button.tsx b/src/components/Button.tsx
new file mode 100644
index 000000000..56cd4dd67
--- /dev/null
+++ b/src/components/Button.tsx
@@ -0,0 +1,19 @@
+import { ButtonHTMLAttributes } from 'react';
+import styles from './Button.module.css';
+
+interface PrimaryButtonProps extends ButtonHTMLAttributes {
+ children: React.ReactNode;
+ className?: string;
+}
+
+export default function Button({
+ children,
+ className,
+ ...props
+}: PrimaryButtonProps) {
+ return (
+
+ );
+}