From 001ba650cb96f843528ba839d9d297345a8acf75 Mon Sep 17 00:00:00 2001
From: HyeonChae1104 <211063@jnu.ac.kr>
Date: Tue, 2 Jul 2024 15:26:51 +0900
Subject: [PATCH 01/21] =?UTF-8?q?Docs:=202=EC=A3=BC=EC=B0=A8=20Step1=20?=
=?UTF-8?q?=EA=B5=AC=ED=98=84=20=EA=B8=B0=EB=8A=A5=20=EC=A0=95=EB=A6=AC?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 26 +++++++++++++++++---------
1 file changed, 17 insertions(+), 9 deletions(-)
diff --git a/README.md b/README.md
index 9fa347d00..1ee7843ff 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,23 @@
# 카카오 테크 캠퍼스 - 프론트엔드 카카오 선물하기 편
-[🔗 link](https://edu.nextstep.camp/s/hazAC9xa)
+### 2주차 Step1 구현 기능 정리
+1. Header, Footer와 같은 공통 컴포넌트를 만들어요. (모든 페이지에서 Header와 Footer는 보여질 수 있게 적용)
+2. 각 Url Path별로 페이지 만들기
-## Week 1. 1단계 - 프로젝트 세팅
+3. 메인 페이지 (/)
+- Theme 카테고리 섹션을 추가해요.
+- Theme 카테고리 Item을 클릭 시 Theme 페이지(/theme/:themeKey)로 이동해요.
+- 실시간 급상승 선물랭킹을 추가해요.
+- 필터 기능을 hooks를 사용하여 구현해요. (ex. 전체, 여성이, 남성이, 청소년이 / 받고 싶어한, 많이 선물한, 위시로 받은)
+- 상품 목록을 처음에는 6개만 보여지게 해요. 더보기를 누르는 경우 상품 목록을 더 보여줘요. (접기 버튼을 누르면 다시 6개만 보여지게 해요)
-[🔗 link](https://edu.nextstep.camp/s/hazAC9xa/ls/QzgHvzRM)
+4. Theme 페이지(/theme/:themeKey)
+- [ ] Header 섹션을 추가해요.
+- 재사용성을 고려하여 Header 섹션을 만들어요. (themeKey에 따라 label, title, description, backgroundColor가 달라짐)
+- 상품 목록 섹션을 추가해요.
-## Week 1. 2단계 - Storybook을 사용하여 재사용 가능한 컴포넌트 구현
+5. 로그인 페이지(/login)
+ID와 PW를 입력하면 로그인이 되도록 구현해요. (ID와 PW는 아무 값을 입력해도 통과되도록 해요.)
-[🔗 link](https://edu.nextstep.camp/s/hazAC9xa/ls/4wYFPW1K)
-
-## Week 2. 1단계 - 페이지 만들기
-
-[🔗 link](https://edu.nextstep.camp/s/hazAC9xa/ls/QzV1ncxk)
+6. 나의 페이지(/my-account)
+로그아웃을 할 수있는 버튼을 추가해요.
\ No newline at end of file
From d04cfc0de4773949deca81b1ea56cd9c3c82cc78 Mon Sep 17 00:00:00 2001
From: HyeonChae1104 <211063@jnu.ac.kr>
Date: Wed, 3 Jul 2024 11:10:48 +0900
Subject: [PATCH 02/21] =?UTF-8?q?feat:=20router=20=EC=84=A4=EC=B9=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
package-lock.json | 44 +++++++++++++++++++++++++++++++++++++++++++-
package.json | 7 ++++---
2 files changed, 47 insertions(+), 4 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 8f100a3a8..892a2aff0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,7 +11,8 @@
"@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0",
"react": "^18.2.0",
- "react-dom": "^18.2.0"
+ "react-dom": "^18.2.0",
+ "react-router-dom": "^6.24.0"
},
"devDependencies": {
"@craco/craco": "^7.1.0",
@@ -6135,6 +6136,15 @@
"@babel/runtime": "^7.13.10"
}
},
+ "node_modules/@remix-run/router": {
+ "version": "1.17.0",
+ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.17.0.tgz",
+ "integrity": "sha512-2D6XaHEVvkCn682XBnipbJjgZUU7xjLtA4dGJRBVUKpEaDYOZMENZoZjAOSb7qirxt5RupjzZxz4fK2FO+EFPw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"node_modules/@rollup/plugin-babel": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
@@ -28318,6 +28328,38 @@
}
}
},
+ "node_modules/react-router": {
+ "version": "6.24.0",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.24.0.tgz",
+ "integrity": "sha512-sQrgJ5bXk7vbcC4BxQxeNa5UmboFm35we1AFK0VvQaz9g0LzxEIuLOhHIoZ8rnu9BO21ishGeL9no1WB76W/eg==",
+ "license": "MIT",
+ "dependencies": {
+ "@remix-run/router": "1.17.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8"
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "6.24.0",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.24.0.tgz",
+ "integrity": "sha512-960sKuau6/yEwS8e+NVEidYQb1hNjAYM327gjEyXlc6r3Skf2vtwuJ2l7lssdegD2YjoKG5l8MsVyeTDlVeY8g==",
+ "license": "MIT",
+ "dependencies": {
+ "@remix-run/router": "1.17.0",
+ "react-router": "6.24.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8",
+ "react-dom": ">=16.8"
+ }
+ },
"node_modules/react-scripts": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
diff --git a/package.json b/package.json
index 8a3e091c7..21b33e338 100644
--- a/package.json
+++ b/package.json
@@ -25,11 +25,10 @@
"@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0",
"react": "^18.2.0",
- "react-dom": "^18.2.0"
+ "react-dom": "^18.2.0",
+ "react-router-dom": "^6.24.0"
},
"devDependencies": {
- "react-scripts": "5.0.1",
- "typescript": "^4.9.5",
"@craco/craco": "^7.1.0",
"@emotion/eslint-plugin": "^11.11.0",
"@storybook/addon-essentials": "^7.6.17",
@@ -65,8 +64,10 @@
"eslint-plugin-storybook": "^0.8.0",
"prettier": "^3.2.5",
"prop-types": "^15.8.1",
+ "react-scripts": "5.0.1",
"storybook": "^7.6.17",
"tsconfig-paths-webpack-plugin": "^4.1.0",
+ "typescript": "^4.9.5",
"webpack": "^5.90.3"
},
"overrides": {
From 916238277c82c02d2f576fe91febb778de4a7ddd Mon Sep 17 00:00:00 2001
From: HyeonChae1104 <211063@jnu.ac.kr>
Date: Wed, 3 Jul 2024 15:51:39 +0900
Subject: [PATCH 03/21] =?UTF-8?q?feat:=20Header=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/components/Header.js | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
create mode 100644 src/components/Header.js
diff --git a/src/components/Header.js b/src/components/Header.js
new file mode 100644
index 000000000..e7c41b60d
--- /dev/null
+++ b/src/components/Header.js
@@ -0,0 +1,18 @@
+import React from 'react';
+import { Link } from 'react-router-dom';
+
+const Header = () => {
+ return (
+
+
+
+ );
+};
+
+export default Header;
\ No newline at end of file
From 2f050056333d3bb815aca409ab948e08e4b84e1a Mon Sep 17 00:00:00 2001
From: HyeonChae1104 <211063@jnu.ac.kr>
Date: Wed, 3 Jul 2024 15:51:58 +0900
Subject: [PATCH 04/21] =?UTF-8?q?feat:=20Footer=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/components/Footer.js | 11 +++++++++++
1 file changed, 11 insertions(+)
create mode 100644 src/components/Footer.js
diff --git a/src/components/Footer.js b/src/components/Footer.js
new file mode 100644
index 000000000..cd9cf120a
--- /dev/null
+++ b/src/components/Footer.js
@@ -0,0 +1,11 @@
+import React from 'react';
+
+const Footer = () => {
+ return (
+
+ );
+};
+
+export default Footer;
\ No newline at end of file
From 42cdb200512410153edc43543c80047224026e8f Mon Sep 17 00:00:00 2001
From: HyeonChae1104 <211063@jnu.ac.kr>
Date: Fri, 5 Jul 2024 13:03:42 +0900
Subject: [PATCH 05/21] =?UTF-8?q?feat:=20=EA=B3=B5=ED=86=B5=20Header=20?=
=?UTF-8?q?=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/components/common/Header.tsx | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
create mode 100644 src/components/common/Header.tsx
diff --git a/src/components/common/Header.tsx b/src/components/common/Header.tsx
new file mode 100644
index 000000000..332048f0d
--- /dev/null
+++ b/src/components/common/Header.tsx
@@ -0,0 +1,23 @@
+import './Header.css'; // 스타일링을 위한 CSS 파일
+
+import React from 'react';
+import { Link } from 'react-router-dom';
+
+const Header: React.FC = () => {
+ return (
+
+
+ 선물하기
+
+
+
+ );
+};
+
+export default Header;
From 86da641781e147347ef26a0824b7618d1f951a17 Mon Sep 17 00:00:00 2001
From: HyeonChae1104 <211063@jnu.ac.kr>
Date: Fri, 5 Jul 2024 13:04:00 +0900
Subject: [PATCH 06/21] =?UTF-8?q?feat:=20=EA=B3=B5=ED=86=B5=20footer=20?=
=?UTF-8?q?=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/components/common/Footer.tsx | 13 +++++++++++++
1 file changed, 13 insertions(+)
create mode 100644 src/components/common/Footer.tsx
diff --git a/src/components/common/Footer.tsx b/src/components/common/Footer.tsx
new file mode 100644
index 000000000..857cf606a
--- /dev/null
+++ b/src/components/common/Footer.tsx
@@ -0,0 +1,13 @@
+import './Footer.css'; // 스타일링을 위한 CSS 파일
+
+import React from 'react';
+
+const Footer: React.FC = () => {
+ return (
+
+ );
+};
+
+export default Footer;
From 786100bf3c81e54fc7e4dcf84de32253457e9ad5 Mon Sep 17 00:00:00 2001
From: HyeonChae1104 <211063@jnu.ac.kr>
Date: Fri, 5 Jul 2024 22:00:29 +0900
Subject: [PATCH 07/21] =?UTF-8?q?react-router-dom=20=EC=84=A4=EC=B9=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
package-lock.json | 36 ++++++++++++++++--------------------
package.json | 6 +++---
2 files changed, 19 insertions(+), 23 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 892a2aff0..356ef96a6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -31,8 +31,8 @@
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2",
"@types/node": "^16.18.82",
- "@types/react": "^18.2.57",
- "@types/react-dom": "^18.2.19",
+ "@types/react": "^18.3.3",
+ "@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"eslint": "^8.56.0",
@@ -53,7 +53,7 @@
"react-scripts": "5.0.1",
"storybook": "^7.6.17",
"tsconfig-paths-webpack-plugin": "^4.1.0",
- "typescript": "^4.9.5",
+ "typescript": "^5.5.3",
"webpack": "^5.90.3"
}
},
@@ -10546,21 +10546,22 @@
"dev": true
},
"node_modules/@types/react": {
- "version": "18.2.58",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.58.tgz",
- "integrity": "sha512-TaGvMNhxvG2Q0K0aYxiKfNDS5m5ZsoIBBbtfUorxdH4NGSXIlYvZxLJI+9Dd3KjeB3780bciLyAb7ylO8pLhPw==",
+ "version": "18.3.3",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz",
+ "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"@types/prop-types": "*",
- "@types/scheduler": "*",
"csstype": "^3.0.2"
}
},
"node_modules/@types/react-dom": {
- "version": "18.2.19",
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.19.tgz",
- "integrity": "sha512-aZvQL6uUbIJpjZk4U8JZGbau9KDeAwMfmhyWorxgBkqDIEf6ROjRozcmPIicqsUwPUjbkDfHKgGee1Lq65APcA==",
+ "version": "18.3.0",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz",
+ "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"@types/react": "*"
}
@@ -10577,12 +10578,6 @@
"integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==",
"dev": true
},
- "node_modules/@types/scheduler": {
- "version": "0.16.8",
- "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz",
- "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==",
- "dev": true
- },
"node_modules/@types/semver": {
"version": "7.5.7",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.7.tgz",
@@ -31687,16 +31682,17 @@
}
},
"node_modules/typescript": {
- "version": "4.9.5",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
- "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
+ "version": "5.5.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz",
+ "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==",
"dev": true,
+ "license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
- "node": ">=4.2.0"
+ "node": ">=14.17"
}
},
"node_modules/ufo": {
diff --git a/package.json b/package.json
index 21b33e338..3ef7dac31 100644
--- a/package.json
+++ b/package.json
@@ -45,8 +45,8 @@
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2",
"@types/node": "^16.18.82",
- "@types/react": "^18.2.57",
- "@types/react-dom": "^18.2.19",
+ "@types/react": "^18.3.3",
+ "@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"eslint": "^8.56.0",
@@ -67,7 +67,7 @@
"react-scripts": "5.0.1",
"storybook": "^7.6.17",
"tsconfig-paths-webpack-plugin": "^4.1.0",
- "typescript": "^4.9.5",
+ "typescript": "^5.5.3",
"webpack": "^5.90.3"
},
"overrides": {
From 0ca745fb49513b334e1f1c311cd0899fd6ffa823 Mon Sep 17 00:00:00 2001
From: HyeonChae1104 <211063@jnu.ac.kr>
Date: Fri, 5 Jul 2024 23:22:57 +0900
Subject: [PATCH 08/21] =?UTF-8?q?feat:=20Footer=20=EC=83=9D=EC=84=B1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/components/common/Footer.tsx | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/components/common/Footer.tsx b/src/components/common/Footer.tsx
index 857cf606a..df11db1c9 100644
--- a/src/components/common/Footer.tsx
+++ b/src/components/common/Footer.tsx
@@ -1,12 +1,12 @@
-import './Footer.css'; // 스타일링을 위한 CSS 파일
+import '../../styles/Footer.css'; // CSS 파일 경로를 지정합니다.
import React from 'react';
const Footer: React.FC = () => {
return (
-
+
);
};
From 6cf6093d3f8174cd6e7445ce4dbb3170e6dcd268 Mon Sep 17 00:00:00 2001
From: HyeonChae1104 <211063@jnu.ac.kr>
Date: Fri, 5 Jul 2024 23:23:17 +0900
Subject: [PATCH 09/21] =?UTF-8?q?feat:=20Header=20=20=EC=83=9D=EC=84=B1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/styles/Header.css | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
create mode 100644 src/styles/Header.css
diff --git a/src/styles/Header.css b/src/styles/Header.css
new file mode 100644
index 000000000..0099eefd2
--- /dev/null
+++ b/src/styles/Header.css
@@ -0,0 +1,17 @@
+.header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 10px 20px;
+}
+
+.giftIcon {
+ width: 61px;
+ height: 54px;
+}
+
+.login {
+ text-decoration: none;
+ color: black;
+ font-weight: bold;
+}
From aa83071177660cd31ba30991de7d6462cc7405cc Mon Sep 17 00:00:00 2001
From: HyeonChae1104 <211063@jnu.ac.kr>
Date: Fri, 5 Jul 2024 23:23:53 +0900
Subject: [PATCH 10/21] =?UTF-8?q?feat:=20Login=20=EA=B5=AC=ED=98=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/Login.tsx | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 84 insertions(+)
create mode 100644 src/Login.tsx
diff --git a/src/Login.tsx b/src/Login.tsx
new file mode 100644
index 000000000..9f7082690
--- /dev/null
+++ b/src/Login.tsx
@@ -0,0 +1,84 @@
+import styled from '@emotion/styled';
+import React, { useState } from 'react';
+
+const Login: React.FC = () => {
+ const [id, setId] = useState('');
+ const [password, setPassword] = useState('');
+ const [message, setMessage] = useState('');
+
+ const handleLogin = () => {
+ // 임의로 로그인 통과
+ setMessage('로그인 성공');
+ };
+
+ return (
+
+
+ kakao
+ setId(e.target.value)}
+ />
+ setPassword(e.target.value)}
+ />
+ 로그인
+ {message && {message}}
+
+
+ );
+};
+
+export default Login;
+
+const LoginContainer = styled.div`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100vh;
+ background-color: #fff;
+`;
+
+const LoginBox = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 20px;
+ border: 1px solid #e0e0e0;
+ border-radius: 8px;
+`;
+
+const Logo = styled.h1`
+ font-size: 24px;
+ margin-bottom: 20px;
+`;
+
+const Input = styled.input`
+ width: 300px;
+ padding: 10px;
+ margin-bottom: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+`;
+
+const LoginButton = styled.button`
+ width: 300px;
+ padding: 10px;
+ background-color: yellow;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 16px;
+ font-weight: bold;
+`;
+
+const Message = styled.p`
+ margin-top: 10px;
+ color: green;
+ font-weight: bold;
+`;
From 64e95932db8fabd9be791ad0dbc102918da2ccab Mon Sep 17 00:00:00 2001
From: HyeonChae1104 <211063@jnu.ac.kr>
Date: Fri, 5 Jul 2024 23:24:23 +0900
Subject: [PATCH 11/21] =?UTF-8?q?feat:=20=ED=99=88=20=ED=99=94=EB=A9=B4=20?=
=?UTF-8?q?=EC=9E=84=EC=9D=98=EA=B5=AC=ED=98=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/Home.tsx | 11 +++++++++++
1 file changed, 11 insertions(+)
create mode 100644 src/Home.tsx
diff --git a/src/Home.tsx b/src/Home.tsx
new file mode 100644
index 000000000..dc4666a10
--- /dev/null
+++ b/src/Home.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+
+const Home: React.FC = () => {
+ return (
+
+
Home Page
+
+ );
+};
+
+export default Home;
From ff392282fa06cae7bf3b19d9ccdab4ab3b1a32dc Mon Sep 17 00:00:00 2001
From: HyeonChae1104 <211063@jnu.ac.kr>
Date: Fri, 5 Jul 2024 23:24:50 +0900
Subject: [PATCH 12/21] =?UTF-8?q?design:=20css=20=ED=8C=8C=EC=9D=BC=20?=
=?UTF-8?q?=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/components/common/Header.tsx | 20 +++++++-------------
src/styles/Footer.css | 13 +++++++++++++
2 files changed, 20 insertions(+), 13 deletions(-)
create mode 100644 src/styles/Footer.css
diff --git a/src/components/common/Header.tsx b/src/components/common/Header.tsx
index 332048f0d..7a0720ed1 100644
--- a/src/components/common/Header.tsx
+++ b/src/components/common/Header.tsx
@@ -1,22 +1,16 @@
-import './Header.css'; // 스타일링을 위한 CSS 파일
+import '../../styles/Header.css'; // CSS 파일 경로를 지정합니다.
import React from 'react';
import { Link } from 'react-router-dom';
const Header: React.FC = () => {
return (
-
-
- 선물하기
-
-
-
+
+
+
+
+
로그인
+
);
};
diff --git a/src/styles/Footer.css b/src/styles/Footer.css
new file mode 100644
index 000000000..d938629d3
--- /dev/null
+++ b/src/styles/Footer.css
@@ -0,0 +1,13 @@
+.footer {
+ background-color: #f0f0f0;
+ padding: 20px;
+ text-align: center;
+ position: absolute;
+ bottom: 0;
+ width: 100%;
+}
+
+.footerText {
+ color: #000;
+ font-size: 14px;
+}
From ca38f4073bef2ef1af26b0f280e9be222942bde0 Mon Sep 17 00:00:00 2001
From: HyeonChae1104 <211063@jnu.ac.kr>
Date: Fri, 5 Jul 2024 23:25:25 +0900
Subject: [PATCH 13/21] =?UTF-8?q?App.tsx=20=ED=8C=8C=EC=9D=BC=20=EC=A0=84?=
=?UTF-8?q?=EC=B2=B4=20=EC=88=98=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/App.tsx | 33 +++++++++++++++++++++++++--------
1 file changed, 25 insertions(+), 8 deletions(-)
diff --git a/src/App.tsx b/src/App.tsx
index 1df5ce256..bbfefbfd7 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,18 +1,35 @@
import styled from '@emotion/styled';
+import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
-const App = () => {
- const name = 'Josh Perez';
+import Footer from './components/common/Footer'; // Footer 컴포넌트를 import 합니다.
+import Header from './components/common/Header';
+import Home from './Home';
+import Login from './Login';
+const App = () => {
return (
-
-
Hello, {name}
-
+
+
+ {/* 모든 페이지에서 공통으로 보여지는 Header */}
+
+
+ } />
+ } />
+
+
+ {/* 모든 페이지에서 공통으로 보여지는 Footer */}
+
+
);
};
export default App;
-const Title = styled.h1`
- font-size: 1.5em;
- color: gray;
+const AppContainer = styled.div`
+ position: relative;
+ min-height: 100vh;
+`;
+
+const Content = styled.div`
+ padding-bottom: 60px; /* Footer 공간 확보 */
`;
From b520b41a2f47385ea8fb6997a7d68e9a12a9262a Mon Sep 17 00:00:00 2001
From: HyeonChae1104 <211063@jnu.ac.kr>
Date: Fri, 5 Jul 2024 23:26:53 +0900
Subject: [PATCH 14/21] =?UTF-8?q?chore:=20=EB=B6=88=ED=95=84=EC=9A=94?=
=?UTF-8?q?=ED=95=9C=20=ED=8C=8C=EC=9D=BC=20=EC=82=AD=EC=A0=9C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/components/Footer.js | 11 -----------
src/components/Header.js | 18 ------------------
2 files changed, 29 deletions(-)
delete mode 100644 src/components/Footer.js
delete mode 100644 src/components/Header.js
diff --git a/src/components/Footer.js b/src/components/Footer.js
deleted file mode 100644
index cd9cf120a..000000000
--- a/src/components/Footer.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import React from 'react';
-
-const Footer = () => {
- return (
-
- );
-};
-
-export default Footer;
\ No newline at end of file
diff --git a/src/components/Header.js b/src/components/Header.js
deleted file mode 100644
index e7c41b60d..000000000
--- a/src/components/Header.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import React from 'react';
-import { Link } from 'react-router-dom';
-
-const Header = () => {
- return (
-
-
-
- );
-};
-
-export default Header;
\ No newline at end of file
From 52588c00e686811fab6c4a62201c5cbac59abffa Mon Sep 17 00:00:00 2001
From: HyeonChae1104 <211063@jnu.ac.kr>
Date: Fri, 5 Jul 2024 23:56:38 +0900
Subject: [PATCH 15/21] =?UTF-8?q?feat:=20Main-Theme=20=EA=B5=AC=ED=98=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
package-lock.json | 149 +++++++++++++++++++-----
package.json | 9 +-
src/App.tsx | 2 +
src/CategoryPage.tsx | 115 ++++++++++++++++++
src/Home.tsx | 86 +++++++++++++-
src/components/common/ProfileBanner.tsx | 40 +++++++
6 files changed, 368 insertions(+), 33 deletions(-)
create mode 100644 src/CategoryPage.tsx
create mode 100644 src/components/common/ProfileBanner.tsx
diff --git a/package-lock.json b/package-lock.json
index 356ef96a6..03de23f14 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,9 +10,11 @@
"dependencies": {
"@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0",
+ "@types/styled-components": "^5.1.34",
"react": "^18.2.0",
"react-dom": "^18.2.0",
- "react-router-dom": "^6.24.0"
+ "react-router-dom": "^6.24.0",
+ "styled-components": "^6.1.11"
},
"devDependencies": {
"@craco/craco": "^7.1.0",
@@ -53,7 +55,7 @@
"react-scripts": "5.0.1",
"storybook": "^7.6.17",
"tsconfig-paths-webpack-plugin": "^4.1.0",
- "typescript": "^5.5.3",
+ "typescript": "^4.9.5",
"webpack": "^5.90.3"
}
},
@@ -2734,9 +2736,10 @@
"integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ=="
},
"node_modules/@emotion/is-prop-valid": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz",
- "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz",
+ "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==",
+ "license": "MIT",
"dependencies": {
"@emotion/memoize": "^0.8.1"
}
@@ -10376,6 +10379,16 @@
"@types/node": "*"
}
},
+ "node_modules/@types/hoist-non-react-statics": {
+ "version": "3.3.5",
+ "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz",
+ "integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/react": "*",
+ "hoist-non-react-statics": "^3.3.0"
+ }
+ },
"node_modules/@types/html-minifier-terser": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
@@ -10524,8 +10537,7 @@
"node_modules/@types/prop-types": {
"version": "15.7.11",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz",
- "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==",
- "dev": true
+ "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng=="
},
"node_modules/@types/q": {
"version": "1.5.8",
@@ -10549,7 +10561,6 @@
"version": "18.3.3",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz",
"integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
@@ -10629,6 +10640,23 @@
"integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==",
"dev": true
},
+ "node_modules/@types/styled-components": {
+ "version": "5.1.34",
+ "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.34.tgz",
+ "integrity": "sha512-mmiVvwpYklFIv9E8qfxuPyIt/OuyIrn6gMOAMOFUO3WJfSrSE+sGUoa4PiZj77Ut7bKZpaa6o1fBKS/4TOEvnA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hoist-non-react-statics": "*",
+ "@types/react": "*",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@types/stylis": {
+ "version": "4.2.5",
+ "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz",
+ "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==",
+ "license": "MIT"
+ },
"node_modules/@types/testing-library__jest-dom": {
"version": "5.14.9",
"resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz",
@@ -13043,6 +13071,15 @@
"node": ">= 6"
}
},
+ "node_modules/camelize": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
+ "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/caniuse-api": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz",
@@ -14742,6 +14779,15 @@
"postcss": "^8.4"
}
},
+ "node_modules/css-color-keywords": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
+ "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/css-declaration-sorter": {
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz",
@@ -15015,6 +15061,17 @@
"integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==",
"dev": true
},
+ "node_modules/css-to-react-native": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
+ "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "camelize": "^1.0.0",
+ "css-color-keywords": "^1.0.0",
+ "postcss-value-parser": "^4.0.2"
+ }
+ },
"node_modules/css-tree": {
"version": "1.0.0-alpha.37",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz",
@@ -24970,7 +25027,6 @@
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
- "dev": true,
"funding": [
{
"type": "github",
@@ -25942,8 +25998,7 @@
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
- "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
- "dev": true
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
},
"node_modules/picomatch": {
"version": "2.3.1",
@@ -26094,10 +26149,9 @@
}
},
"node_modules/postcss": {
- "version": "8.4.35",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
- "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
- "dev": true,
+ "version": "8.4.38",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
+ "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
"funding": [
{
"type": "opencollective",
@@ -26112,10 +26166,11 @@
"url": "https://github.com/sponsors/ai"
}
],
+ "license": "MIT",
"dependencies": {
"nanoid": "^3.3.7",
"picocolors": "^1.0.0",
- "source-map-js": "^1.0.2"
+ "source-map-js": "^1.2.0"
},
"engines": {
"node": "^10 || ^12 || >=14"
@@ -27397,8 +27452,7 @@
"node_modules/postcss-value-parser": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
- "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
- "dev": true
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
},
"node_modules/prelude-ls": {
"version": "1.2.1",
@@ -29658,6 +29712,12 @@
"node": ">=8"
}
},
+ "node_modules/shallowequal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+ "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
+ "license": "MIT"
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -29846,10 +29906,10 @@
}
},
"node_modules/source-map-js": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
- "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
- "dev": true,
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
+ "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
+ "license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
@@ -30443,6 +30503,40 @@
"webpack": "^5.0.0"
}
},
+ "node_modules/styled-components": {
+ "version": "6.1.11",
+ "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.11.tgz",
+ "integrity": "sha512-Ui0jXPzbp1phYij90h12ksljKGqF8ncGx+pjrNPsSPhbUUjWT2tD1FwGo2LF6USCnbrsIhNngDfodhxbegfEOA==",
+ "license": "MIT",
+ "dependencies": {
+ "@emotion/is-prop-valid": "1.2.2",
+ "@emotion/unitless": "0.8.1",
+ "@types/stylis": "4.2.5",
+ "css-to-react-native": "3.2.0",
+ "csstype": "3.1.3",
+ "postcss": "8.4.38",
+ "shallowequal": "1.1.0",
+ "stylis": "4.3.2",
+ "tslib": "2.6.2"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/styled-components"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8.0",
+ "react-dom": ">= 16.8.0"
+ }
+ },
+ "node_modules/styled-components/node_modules/stylis": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz",
+ "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==",
+ "license": "MIT"
+ },
"node_modules/stylehacks": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz",
@@ -31517,8 +31611,7 @@
"node_modules/tslib": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
- "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
- "dev": true
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
},
"node_modules/tsutils": {
"version": "3.21.0",
@@ -31682,9 +31775,9 @@
}
},
"node_modules/typescript": {
- "version": "5.5.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz",
- "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==",
+ "version": "4.9.5",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
+ "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -31692,7 +31785,7 @@
"tsserver": "bin/tsserver"
},
"engines": {
- "node": ">=14.17"
+ "node": ">=4.2.0"
}
},
"node_modules/ufo": {
diff --git a/package.json b/package.json
index 3ef7dac31..93f10a66c 100644
--- a/package.json
+++ b/package.json
@@ -6,6 +6,9 @@
"start": "craco start",
"build": "craco build",
"test": "craco test",
+ "lint": "eslint \"src/**/*.{js,jsx,ts,tsx,json}\"",
+ "lint:fix": "eslint --fix \"src/**/*.{js,jsx,ts,tsx,json}\"",
+ "prettier": "prettier --write \"src/**/*.{js,jsx,ts,tsx,css,md,json}\" --config ./.prettierrc",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
},
@@ -24,9 +27,11 @@
"dependencies": {
"@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0",
+ "@types/styled-components": "^5.1.34",
"react": "^18.2.0",
"react-dom": "^18.2.0",
- "react-router-dom": "^6.24.0"
+ "react-router-dom": "^6.24.0",
+ "styled-components": "^6.1.11"
},
"devDependencies": {
"@craco/craco": "^7.1.0",
@@ -67,7 +72,7 @@
"react-scripts": "5.0.1",
"storybook": "^7.6.17",
"tsconfig-paths-webpack-plugin": "^4.1.0",
- "typescript": "^5.5.3",
+ "typescript": "^4.9.5",
"webpack": "^5.90.3"
},
"overrides": {
diff --git a/src/App.tsx b/src/App.tsx
index bbfefbfd7..b3a425353 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,6 +1,7 @@
import styled from '@emotion/styled';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
+import CategoryPage from './CategoryPage';
import Footer from './components/common/Footer'; // Footer 컴포넌트를 import 합니다.
import Header from './components/common/Header';
import Home from './Home';
@@ -15,6 +16,7 @@ const App = () => {
} />
} />
+ } />
{/* 모든 페이지에서 공통으로 보여지는 Footer */}
diff --git a/src/CategoryPage.tsx b/src/CategoryPage.tsx
new file mode 100644
index 000000000..d446f7dc6
--- /dev/null
+++ b/src/CategoryPage.tsx
@@ -0,0 +1,115 @@
+import styled from '@emotion/styled';
+import React from 'react';
+import { useParams } from 'react-router-dom';
+
+const CategoryPage: React.FC = () => {
+ const { themeKey } = useParams<{ themeKey: string }>();
+
+ return (
+
+
+ {themeKey}
+ 예산은 가볍게, 감동은 무겁게 ❤️
+ 당신의 센스를 뽐내줄 부담 없는 선물
+
+
+ {products.map((product, index) => (
+
+
+
+ {product.brand}
+ {product.name}
+ {product.price}
+
+
+ ))}
+
+
+ );
+};
+
+export default CategoryPage;
+
+const products = [
+ { brand: 'BBQ', name: 'BBQ 양념치킨+크림치즈볼+콜라1.25L', price: '29000원' },
+ { brand: 'BBQ', name: 'BBQ 양념치킨+크림치즈볼+콜라1.25L', price: '29000원' },
+ { brand: 'BBQ', name: 'BBQ 양념치킨+크림치즈볼+콜라1.25L', price: '29000원' },
+ { brand: 'BBQ', name: 'BBQ 양념치킨+크림치즈볼+콜라1.25L', price: '29000원' },
+ { brand: 'BBQ', name: 'BBQ 양념치킨+크림치즈볼+콜라1.25L', price: '29000원' },
+ { brand: 'BBQ', name: 'BBQ 양념치킨+크림치즈볼+콜라1.25L', price: '29000원' },
+ { brand: 'BBQ', name: 'BBQ 양념치킨+크림치즈볼+콜라1.25L', price: '29000원' },
+ { brand: 'BBQ', name: 'BBQ 양념치킨+크림치즈볼+콜라1.25L', price: '29000원' }
+];
+
+const CategoryContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 20px;
+`;
+
+const Banner = styled.div`
+ width: 100%;
+ background-color: #4a4a4a;
+ color: #fff;
+ padding: 40px 20px;
+ text-align: center;
+`;
+
+const BannerTitle = styled.h2`
+ font-size: 24px;
+ margin-bottom: 10px;
+`;
+
+const BannerSubtitle = styled.h1`
+ font-size: 32px;
+ margin-bottom: 10px;
+ font-weight: bold;
+`;
+
+const BannerDescription = styled.p`
+ font-size: 18px;
+`;
+
+const ProductList = styled.div`
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ gap: 20px;
+ margin-top: 20px;
+`;
+
+const ProductCard = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ width: 220px;
+ border: 1px solid #e0e0e0;
+ border-radius: 8px;
+ overflow: hidden;
+`;
+
+const ProductImage = styled.img`
+ width: 100%;
+ height: auto;
+`;
+
+const ProductInfo = styled.div`
+ padding: 10px;
+ text-align: center;
+`;
+
+const Brand = styled.div`
+ font-size: 14px;
+ color: #757575;
+`;
+
+const Name = styled.div`
+ font-size: 16px;
+ margin: 5px 0;
+`;
+
+const Price = styled.div`
+ font-size: 18px;
+ font-weight: bold;
+`;
diff --git a/src/Home.tsx b/src/Home.tsx
index dc4666a10..b47b24d9e 100644
--- a/src/Home.tsx
+++ b/src/Home.tsx
@@ -1,11 +1,91 @@
+import styled from '@emotion/styled';
import React from 'react';
+import { Link } from 'react-router-dom';
+
+import ProfileBanner from './components/common/ProfileBanner';
const Home: React.FC = () => {
return (
-
-
Home Page
-
+
+
+
+ 카테고리
+
+ {categories.map((category, index) => (
+
+
+
+ {category.label}
+
+
+ ))}
+
+
+
);
};
export default Home;
+
+const categories = [
+ { key: 'birthday', label: '생일' },
+ { key: 'graduation', label: '졸업선물' },
+ { key: 'luxury', label: '스몰럭셔리' },
+ { key: 'premium', label: '명품선물' },
+ { key: 'housewarming', label: '결혼/집들이' },
+ { key: 'warm', label: '따뜻한선물' },
+ { key: 'light', label: '가벼운선물' },
+ { key: 'fans', label: '팬심저격' },
+ { key: 'voucher', label: '교환권' },
+ { key: 'health', label: '건강/비타민' },
+ { key: 'fruits', label: '과일/한우' },
+ { key: 'baby', label: '출산/키즈' }
+];
+
+const MainContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+`;
+
+const CategorySection = styled.div`
+ width: 100%;
+ padding: 20px;
+ background-color: #fff;
+`;
+
+const CategoryTitle = styled.h3`
+ font-size: 18px;
+ color: #000;
+ margin-bottom: 20px;
+ text-align: center;
+`;
+
+const CategoryList = styled.div`
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ gap: 20px;
+ max-width: 1200px;
+ margin: 0 auto;
+`;
+
+const CategoryItem = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ width: 120px;
+`;
+
+const CategoryImage = styled.img`
+ width: 80px;
+ height: 80px;
+ border-radius: 50%;
+`;
+
+const CategoryLabel = styled.span`
+ margin-top: 10px;
+ font-size: 14px;
+ color: #000;
+ text-align: center;
+`;
diff --git a/src/components/common/ProfileBanner.tsx b/src/components/common/ProfileBanner.tsx
new file mode 100644
index 000000000..abafafef4
--- /dev/null
+++ b/src/components/common/ProfileBanner.tsx
@@ -0,0 +1,40 @@
+import styled from '@emotion/styled';
+import React from 'react';
+
+const ProfileBanner: React.FC = () => {
+ return (
+
+
+
+ 선물 받을 친구를 선택해주세요.
+
+
+ );
+};
+
+export default ProfileBanner;
+
+const BannerContainer = styled.div`
+ display: flex;
+ align-items: center;
+ padding: 20px;
+ background-color: #f7f7f7; /* 연한 회색 배경 */
+`;
+
+const ProfileImage = styled.img`
+ width: 50px;
+ height: 50px;
+ border-radius: 50%;
+ margin-right: 20px;
+`;
+
+const TextContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+`;
+
+const Title = styled.h2`
+ font-size: 18px;
+ color: #000;
+ margin: 0;
+`;
From 9adee3639e00d39afb4b84c73c78dd0dbd03fd4e Mon Sep 17 00:00:00 2001
From: HyeonChae1104 <211063@jnu.ac.kr>
Date: Sat, 6 Jul 2024 00:19:38 +0900
Subject: [PATCH 16/21] =?UTF-8?q?feat:=20yellow=20btn=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/Home.tsx | 27 +++++++++++++++++++++++++++
1 file changed, 27 insertions(+)
diff --git a/src/Home.tsx b/src/Home.tsx
index b47b24d9e..8938d06a8 100644
--- a/src/Home.tsx
+++ b/src/Home.tsx
@@ -21,6 +21,10 @@ const Home: React.FC = () => {
))}
+
+ AI가 추천하는 선물
+ 선물을 추천받고 싶은 친구를 선택해주세요.
+
);
};
@@ -89,3 +93,26 @@ const CategoryLabel = styled.span`
color: #000;
text-align: center;
`;
+
+const YellowButton = styled.button`
+ background-color: yellow;
+ padding: 20px;
+ text-align: center;
+ border: none;
+ cursor: pointer;
+ margin-top: 20px;
+ width: 100%;
+ max-width: 1200px;
+`;
+
+const ButtonTitle = styled.h2`
+ font-size: 18px;
+ color: #000;
+ margin: 0;
+`;
+
+const ButtonSubtitle = styled.p`
+ font-size: 14px;
+ color: #000;
+ margin: 5px 0 0 0;
+`;
From 8cf7c1b1030c6e2cd19f87dfe6fb77889e525bc0 Mon Sep 17 00:00:00 2001
From: HyeonChae1104 <211063@jnu.ac.kr>
Date: Sat, 6 Jul 2024 00:39:47 +0900
Subject: [PATCH 17/21] =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?=
=?UTF-8?q?=EA=B8=B0=EB=8A=A5=EB=B3=84=EB=A1=9C=20=ED=8C=8C=EC=9D=BC=20?=
=?UTF-8?q?=EB=B6=84=EB=A6=AC?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/Home.tsx | 104 ++-------------------------
src/components/Home/CategotyList.tsx | 78 ++++++++++++++++++++
src/components/Home/YellowButton.tsx | 34 +++++++++
3 files changed, 118 insertions(+), 98 deletions(-)
create mode 100644 src/components/Home/CategotyList.tsx
create mode 100644 src/components/Home/YellowButton.tsx
diff --git a/src/Home.tsx b/src/Home.tsx
index 8938d06a8..19cbeec7e 100644
--- a/src/Home.tsx
+++ b/src/Home.tsx
@@ -1,118 +1,26 @@
import styled from '@emotion/styled';
import React from 'react';
-import { Link } from 'react-router-dom';
import ProfileBanner from './components/common/ProfileBanner';
+import CategoryList from './components/Home/CategotyList';
+import TrendingGifts from './components/Home/TrendingGifts';
+import YellowButton from './components/Home/YellowButton';
const Home: React.FC = () => {
return (
-
- 카테고리
-
- {categories.map((category, index) => (
-
-
-
- {category.label}
-
-
- ))}
-
-
-
- AI가 추천하는 선물
- 선물을 추천받고 싶은 친구를 선택해주세요.
-
+
+
+
);
};
export default Home;
-const categories = [
- { key: 'birthday', label: '생일' },
- { key: 'graduation', label: '졸업선물' },
- { key: 'luxury', label: '스몰럭셔리' },
- { key: 'premium', label: '명품선물' },
- { key: 'housewarming', label: '결혼/집들이' },
- { key: 'warm', label: '따뜻한선물' },
- { key: 'light', label: '가벼운선물' },
- { key: 'fans', label: '팬심저격' },
- { key: 'voucher', label: '교환권' },
- { key: 'health', label: '건강/비타민' },
- { key: 'fruits', label: '과일/한우' },
- { key: 'baby', label: '출산/키즈' }
-];
-
const MainContainer = styled.div`
display: flex;
flex-direction: column;
align-items: center;
`;
-
-const CategorySection = styled.div`
- width: 100%;
- padding: 20px;
- background-color: #fff;
-`;
-
-const CategoryTitle = styled.h3`
- font-size: 18px;
- color: #000;
- margin-bottom: 20px;
- text-align: center;
-`;
-
-const CategoryList = styled.div`
- display: flex;
- flex-wrap: wrap;
- justify-content: center;
- gap: 20px;
- max-width: 1200px;
- margin: 0 auto;
-`;
-
-const CategoryItem = styled.div`
- display: flex;
- flex-direction: column;
- align-items: center;
- width: 120px;
-`;
-
-const CategoryImage = styled.img`
- width: 80px;
- height: 80px;
- border-radius: 50%;
-`;
-
-const CategoryLabel = styled.span`
- margin-top: 10px;
- font-size: 14px;
- color: #000;
- text-align: center;
-`;
-
-const YellowButton = styled.button`
- background-color: yellow;
- padding: 20px;
- text-align: center;
- border: none;
- cursor: pointer;
- margin-top: 20px;
- width: 100%;
- max-width: 1200px;
-`;
-
-const ButtonTitle = styled.h2`
- font-size: 18px;
- color: #000;
- margin: 0;
-`;
-
-const ButtonSubtitle = styled.p`
- font-size: 14px;
- color: #000;
- margin: 5px 0 0 0;
-`;
diff --git a/src/components/Home/CategotyList.tsx b/src/components/Home/CategotyList.tsx
new file mode 100644
index 000000000..97d166372
--- /dev/null
+++ b/src/components/Home/CategotyList.tsx
@@ -0,0 +1,78 @@
+import styled from '@emotion/styled';
+import React from 'react';
+import { Link } from 'react-router-dom';
+
+const categories = [
+ { key: 'birthday', label: '생일' },
+ { key: 'graduation', label: '졸업선물' },
+ { key: 'luxury', label: '스몰럭셔리' },
+ { key: 'premium', label: '명품선물' },
+ { key: 'housewarming', label: '결혼/집들이' },
+ { key: 'warm', label: '따뜻한선물' },
+ { key: 'light', label: '가벼운선물' },
+ { key: 'fans', label: '팬심저격' },
+ { key: 'voucher', label: '교환권' },
+ { key: 'health', label: '건강/비타민' },
+ { key: 'fruits', label: '과일/한우' },
+ { key: 'baby', label: '출산/키즈' }
+];
+
+const CategoryList: React.FC = () => (
+
+ 카테고리
+
+ {categories.map((category, index) => (
+
+
+
+ {category.label}
+
+
+ ))}
+
+
+);
+
+export default CategoryList;
+
+const CategorySection = styled.div`
+ width: 100%;
+ padding: 20px;
+ background-color: #fff;
+`;
+
+const CategoryTitle = styled.h3`
+ font-size: 18px;
+ color: #000;
+ margin-bottom: 20px;
+ text-align: center;
+`;
+
+const CategoryListContainer = styled.div`
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ gap: 20px;
+ max-width: 1200px;
+ margin: 0 auto;
+`;
+
+const CategoryItem = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ width: 120px;
+`;
+
+const CategoryImage = styled.img`
+ width: 80px;
+ height: 80px;
+ border-radius: 50%;
+`;
+
+const CategoryLabel = styled.span`
+ margin-top: 10px;
+ font-size: 14px;
+ color: #000;
+ text-align: center;
+`;
diff --git a/src/components/Home/YellowButton.tsx b/src/components/Home/YellowButton.tsx
new file mode 100644
index 000000000..831bbcd86
--- /dev/null
+++ b/src/components/Home/YellowButton.tsx
@@ -0,0 +1,34 @@
+import styled from '@emotion/styled';
+import React from 'react';
+
+const YellowButton: React.FC = () => (
+
+ AI가 추천하는 선물
+ 선물을 추천받고 싶은 친구를 선택해주세요.
+
+);
+
+export default YellowButton;
+
+const ButtonContainer = styled.button`
+ background-color: yellow;
+ padding: 20px;
+ text-align: center;
+ border: none;
+ cursor: pointer;
+ margin-top: 20px;
+ width: 100%;
+ max-width: 1200px;
+`;
+
+const ButtonTitle = styled.h2`
+ font-size: 18px;
+ color: #000;
+ margin: 0;
+`;
+
+const ButtonSubtitle = styled.p`
+ font-size: 14px;
+ color: #000;
+ margin: 5px 0 0 0;
+`;
From 5e631a1db330cb81bfc38491180a239a35073def Mon Sep 17 00:00:00 2001
From: HyeonChae1104 <211063@jnu.ac.kr>
Date: Sat, 6 Jul 2024 00:42:34 +0900
Subject: [PATCH 18/21] =?UTF-8?q?feat:=20=EC=8B=A4=EC=8B=9C=EA=B0=84=20?=
=?UTF-8?q?=EA=B8=89=EC=83=81=EC=8A=B9=20=EC=84=A0=EB=AC=BC=20=EB=9E=AD?=
=?UTF-8?q?=ED=82=B9=20=EA=B5=AC=ED=98=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/App.tsx | 2 +
src/TrendingGifts.tsx | 229 ++++++++++++++++++++++++++
src/components/Home/TrendingGifts.tsx | 229 ++++++++++++++++++++++++++
3 files changed, 460 insertions(+)
create mode 100644 src/TrendingGifts.tsx
create mode 100644 src/components/Home/TrendingGifts.tsx
diff --git a/src/App.tsx b/src/App.tsx
index b3a425353..efb32d60b 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -6,6 +6,7 @@ import Footer from './components/common/Footer'; // Footer 컴포넌트를 impor
import Header from './components/common/Header';
import Home from './Home';
import Login from './Login';
+import TrendingGifts from './TrendingGifts';
const App = () => {
return (
@@ -17,6 +18,7 @@ const App = () => {
} />
} />
} />
+ } />
{/* 모든 페이지에서 공통으로 보여지는 Footer */}
diff --git a/src/TrendingGifts.tsx b/src/TrendingGifts.tsx
new file mode 100644
index 000000000..18be66c42
--- /dev/null
+++ b/src/TrendingGifts.tsx
@@ -0,0 +1,229 @@
+import styled from '@emotion/styled';
+import React, { useState } from 'react';
+
+const categories = [
+ { id: 'all', label: '전체', backgroundColor: '#4684E9', icon: 'ALL' },
+ { id: 'women', label: '여성이', backgroundColor: '#E6F1FF', icon: '👩' },
+ { id: 'men', label: '남성이', backgroundColor: '#E6F1FF', icon: '👨' },
+ { id: 'teenagers', label: '청소년이', backgroundColor: '#E6F1FF', icon: '👶' },
+];
+
+const filters = [
+ { id: 'wanted', label: '받고 싶어한' },
+ { id: 'gifted', label: '많이 선물한' },
+ { id: 'received', label: '위시로 받은' },
+];
+
+const TrendingGifts: React.FC = () => {
+ const [selectedCategory, setSelectedCategory] = useState('all');
+ const [selectedFilter, setSelectedFilter] = useState('wanted');
+ const [visibleProducts, setVisibleProducts] = useState(6);
+
+ const handleCategoryClick = (id: string) => {
+ setSelectedCategory(id);
+ };
+
+ const handleFilterClick = (id: string) => {
+ setSelectedFilter(id);
+ };
+
+ const handleLoadMore = () => {
+ setVisibleProducts(visibleProducts + 6);
+ };
+
+ const handleReset = () => {
+ setVisibleProducts(6);
+ };
+
+ return (
+
+ 실시간 급상승 선물랭킹
+
+ {categories.map((category) => (
+ handleCategoryClick(category.id)}
+ selected={selectedCategory === category.id}
+ backgroundColor={category.backgroundColor}
+ >
+ {category.icon}
+ {category.label}
+
+ ))}
+
+
+ {filters.map((filter) => (
+ handleFilterClick(filter.id)}
+ selected={selectedFilter === filter.id}
+ >
+ {filter.label}
+
+ ))}
+
+
+ {products.slice(0, visibleProducts).map((product, index) => (
+
+
+ {index + 1}
+
+ {product.brand}
+ {product.name}
+ {product.price}
+
+
+ ))}
+
+ {visibleProducts < products.length && (
+ 더보기
+ )}
+ {visibleProducts > 6 && (
+ 접기
+ )}
+
+ );
+};
+
+export default TrendingGifts;
+
+const products = [
+ { brand: 'BBQ', name: 'BBQ 양념치킨+크림치즈볼+콜라1.25L', price: '29000원' },
+ { brand: 'BBQ', name: 'BBQ 양념치킨+크림치즈볼+콜라1.25L', price: '29000원' },
+ { brand: 'BBQ', name: 'BBQ 양념치킨+크림치즈볼+콜라1.25L', price: '29000원' },
+ { brand: 'BBQ', name: 'BBQ 양념치킨+크림치즈볼+콜라1.25L', price: '29000원' },
+ { brand: 'BBQ', name: 'BBQ 양념치킨+크림치즈볼+콜라1.25L', price: '29000원' },
+ { brand: 'BBQ', name: 'BBQ 양념치킨+크림치즈볼+콜라1.25L', price: '29000원' },
+ { brand: 'BBQ', name: 'BBQ 양념치킨+크림치즈볼+콜라1.25L', price: '29000원' },
+ { brand: 'BBQ', name: 'BBQ 양념치킨+크림치즈볼+콜라1.25L', price: '29000원' },
+];
+
+const TrendingGiftsContainer = styled.div`
+ padding: 20px;
+ text-align: center;
+`;
+
+const Title = styled.h2`
+ font-size: 24px;
+ font-weight: bold;
+ margin-bottom: 20px;
+`;
+
+const CategoryList = styled.div`
+ display: flex;
+ justify-content: center;
+ gap: 10px;
+ margin-bottom: 20px;
+`;
+
+const CategoryItem = styled.div<{ selected: boolean; backgroundColor: string }>`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ width: 60px;
+ height: 60px;
+ border-radius: 30px;
+ background-color: ${(props) => (props.selected ? '#4684E9' : props.backgroundColor)};
+ color: #fff;
+ font-size: 20px;
+ cursor: pointer;
+`;
+
+const CategoryIcon = styled.div`
+ font-size: 24px;
+`;
+
+const CategoryLabel = styled.div`
+ font-size: 14px;
+ margin-top: 5px;
+`;
+
+const FilterList = styled.div`
+ display: flex;
+ justify-content: center;
+ gap: 10px;
+ margin-bottom: 20px;
+`;
+
+const FilterItem = styled.div<{ selected: boolean }>`
+ padding: 10px 20px;
+ border-radius: 20px;
+ background-color: ${(props) => (props.selected ? '#4684E9' : '#E6F1FF')};
+ color: ${(props) => (props.selected ? '#fff' : '#000')};
+ cursor: pointer;
+`;
+
+const ProductList = styled.div`
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ gap: 20px;
+`;
+
+const ProductCard = styled.div`
+ position: relative;
+ width: 200px;
+ border: 1px solid #e0e0e0;
+ border-radius: 8px;
+ overflow: hidden;
+ text-align: center;
+`;
+
+const ProductImage = styled.img`
+ width: 100%;
+ height: auto;
+`;
+
+const ProductRank = styled.div`
+ position: absolute;
+ top: 10px;
+ left: 10px;
+ background-color: #4684E9;
+ color: #fff;
+ padding: 5px 10px;
+ border-radius: 5px;
+ font-size: 14px;
+ font-weight: bold;
+`;
+
+const ProductInfo = styled.div`
+ padding: 10px;
+`;
+
+const Brand = styled.div`
+ font-size: 14px;
+ color: #757575;
+`;
+
+const Name = styled.div`
+ font-size: 16px;
+ margin: 5px 0;
+`;
+
+const Price = styled.div`
+ font-size: 18px;
+ font-weight: bold;
+`;
+
+const LoadMoreButton = styled.button`
+ margin-top: 20px;
+ padding: 10px 20px;
+ font-size: 16px;
+ background-color: #4684E9;
+ color: #fff;
+ border: none;
+ border-radius: 5px;
+ cursor: pointer;
+`;
+
+const ResetButton = styled.button`
+ margin-top: 10px;
+ padding: 10px 20px;
+ font-size: 16px;
+ background-color: #757575;
+ color: #fff;
+ border: none;
+ border-radius: 5px;
+ cursor: pointer;
+`;
diff --git a/src/components/Home/TrendingGifts.tsx b/src/components/Home/TrendingGifts.tsx
new file mode 100644
index 000000000..18be66c42
--- /dev/null
+++ b/src/components/Home/TrendingGifts.tsx
@@ -0,0 +1,229 @@
+import styled from '@emotion/styled';
+import React, { useState } from 'react';
+
+const categories = [
+ { id: 'all', label: '전체', backgroundColor: '#4684E9', icon: 'ALL' },
+ { id: 'women', label: '여성이', backgroundColor: '#E6F1FF', icon: '👩' },
+ { id: 'men', label: '남성이', backgroundColor: '#E6F1FF', icon: '👨' },
+ { id: 'teenagers', label: '청소년이', backgroundColor: '#E6F1FF', icon: '👶' },
+];
+
+const filters = [
+ { id: 'wanted', label: '받고 싶어한' },
+ { id: 'gifted', label: '많이 선물한' },
+ { id: 'received', label: '위시로 받은' },
+];
+
+const TrendingGifts: React.FC = () => {
+ const [selectedCategory, setSelectedCategory] = useState('all');
+ const [selectedFilter, setSelectedFilter] = useState('wanted');
+ const [visibleProducts, setVisibleProducts] = useState(6);
+
+ const handleCategoryClick = (id: string) => {
+ setSelectedCategory(id);
+ };
+
+ const handleFilterClick = (id: string) => {
+ setSelectedFilter(id);
+ };
+
+ const handleLoadMore = () => {
+ setVisibleProducts(visibleProducts + 6);
+ };
+
+ const handleReset = () => {
+ setVisibleProducts(6);
+ };
+
+ return (
+
+ 실시간 급상승 선물랭킹
+
+ {categories.map((category) => (
+ handleCategoryClick(category.id)}
+ selected={selectedCategory === category.id}
+ backgroundColor={category.backgroundColor}
+ >
+ {category.icon}
+ {category.label}
+
+ ))}
+
+
+ {filters.map((filter) => (
+ handleFilterClick(filter.id)}
+ selected={selectedFilter === filter.id}
+ >
+ {filter.label}
+
+ ))}
+
+
+ {products.slice(0, visibleProducts).map((product, index) => (
+
+
+ {index + 1}
+
+ {product.brand}
+ {product.name}
+ {product.price}
+
+
+ ))}
+
+ {visibleProducts < products.length && (
+ 더보기
+ )}
+ {visibleProducts > 6 && (
+ 접기
+ )}
+
+ );
+};
+
+export default TrendingGifts;
+
+const products = [
+ { brand: 'BBQ', name: 'BBQ 양념치킨+크림치즈볼+콜라1.25L', price: '29000원' },
+ { brand: 'BBQ', name: 'BBQ 양념치킨+크림치즈볼+콜라1.25L', price: '29000원' },
+ { brand: 'BBQ', name: 'BBQ 양념치킨+크림치즈볼+콜라1.25L', price: '29000원' },
+ { brand: 'BBQ', name: 'BBQ 양념치킨+크림치즈볼+콜라1.25L', price: '29000원' },
+ { brand: 'BBQ', name: 'BBQ 양념치킨+크림치즈볼+콜라1.25L', price: '29000원' },
+ { brand: 'BBQ', name: 'BBQ 양념치킨+크림치즈볼+콜라1.25L', price: '29000원' },
+ { brand: 'BBQ', name: 'BBQ 양념치킨+크림치즈볼+콜라1.25L', price: '29000원' },
+ { brand: 'BBQ', name: 'BBQ 양념치킨+크림치즈볼+콜라1.25L', price: '29000원' },
+];
+
+const TrendingGiftsContainer = styled.div`
+ padding: 20px;
+ text-align: center;
+`;
+
+const Title = styled.h2`
+ font-size: 24px;
+ font-weight: bold;
+ margin-bottom: 20px;
+`;
+
+const CategoryList = styled.div`
+ display: flex;
+ justify-content: center;
+ gap: 10px;
+ margin-bottom: 20px;
+`;
+
+const CategoryItem = styled.div<{ selected: boolean; backgroundColor: string }>`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ width: 60px;
+ height: 60px;
+ border-radius: 30px;
+ background-color: ${(props) => (props.selected ? '#4684E9' : props.backgroundColor)};
+ color: #fff;
+ font-size: 20px;
+ cursor: pointer;
+`;
+
+const CategoryIcon = styled.div`
+ font-size: 24px;
+`;
+
+const CategoryLabel = styled.div`
+ font-size: 14px;
+ margin-top: 5px;
+`;
+
+const FilterList = styled.div`
+ display: flex;
+ justify-content: center;
+ gap: 10px;
+ margin-bottom: 20px;
+`;
+
+const FilterItem = styled.div<{ selected: boolean }>`
+ padding: 10px 20px;
+ border-radius: 20px;
+ background-color: ${(props) => (props.selected ? '#4684E9' : '#E6F1FF')};
+ color: ${(props) => (props.selected ? '#fff' : '#000')};
+ cursor: pointer;
+`;
+
+const ProductList = styled.div`
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ gap: 20px;
+`;
+
+const ProductCard = styled.div`
+ position: relative;
+ width: 200px;
+ border: 1px solid #e0e0e0;
+ border-radius: 8px;
+ overflow: hidden;
+ text-align: center;
+`;
+
+const ProductImage = styled.img`
+ width: 100%;
+ height: auto;
+`;
+
+const ProductRank = styled.div`
+ position: absolute;
+ top: 10px;
+ left: 10px;
+ background-color: #4684E9;
+ color: #fff;
+ padding: 5px 10px;
+ border-radius: 5px;
+ font-size: 14px;
+ font-weight: bold;
+`;
+
+const ProductInfo = styled.div`
+ padding: 10px;
+`;
+
+const Brand = styled.div`
+ font-size: 14px;
+ color: #757575;
+`;
+
+const Name = styled.div`
+ font-size: 16px;
+ margin: 5px 0;
+`;
+
+const Price = styled.div`
+ font-size: 18px;
+ font-weight: bold;
+`;
+
+const LoadMoreButton = styled.button`
+ margin-top: 20px;
+ padding: 10px 20px;
+ font-size: 16px;
+ background-color: #4684E9;
+ color: #fff;
+ border: none;
+ border-radius: 5px;
+ cursor: pointer;
+`;
+
+const ResetButton = styled.button`
+ margin-top: 10px;
+ padding: 10px 20px;
+ font-size: 16px;
+ background-color: #757575;
+ color: #fff;
+ border: none;
+ border-radius: 5px;
+ cursor: pointer;
+`;
From f3929371f5484b5c93b52aad885e9d2ec4aae6d9 Mon Sep 17 00:00:00 2001
From: HyeonChae1104 <211063@jnu.ac.kr>
Date: Sat, 6 Jul 2024 00:45:33 +0900
Subject: [PATCH 19/21] =?UTF-8?q?Step1=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?=
=?UTF-8?q?=ED=98=84=20=EC=99=84=EB=A3=8C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/components/Home/CategotyList.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/Home/CategotyList.tsx b/src/components/Home/CategotyList.tsx
index 97d166372..f62e43fcd 100644
--- a/src/components/Home/CategotyList.tsx
+++ b/src/components/Home/CategotyList.tsx
@@ -19,7 +19,7 @@ const categories = [
const CategoryList: React.FC = () => (
- 카테고리
+
{categories.map((category, index) => (
From d6354907ba168b8e94fce0e67cfec630fd43d6dd Mon Sep 17 00:00:00 2001
From: HyeonChae1104 <211063@jnu.ac.kr>
Date: Sat, 6 Jul 2024 01:01:23 +0900
Subject: [PATCH 20/21] =?UTF-8?q?feat:=20Step2=20=EC=9D=B8=EC=A6=9D=20?=
=?UTF-8?q?=ED=94=84=EB=A1=9C=EC=84=B8=EC=8A=A4=20=EA=B5=AC=ED=98=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/App.tsx | 56 +++++++++++--------
src/components/common/Header.tsx | 8 ++-
src/contexts/AuthContext.tsx | 38 +++++++++++++
src/pages/Login.tsx | 92 ++++++++++++++++++++++++++++++++
src/pages/MyAccount.tsx | 45 ++++++++++++++++
5 files changed, 215 insertions(+), 24 deletions(-)
create mode 100644 src/contexts/AuthContext.tsx
create mode 100644 src/pages/Login.tsx
create mode 100644 src/pages/MyAccount.tsx
diff --git a/src/App.tsx b/src/App.tsx
index efb32d60b..e4216fa81 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,39 +1,49 @@
import styled from '@emotion/styled';
-import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
+import { BrowserRouter as Router, Navigate,Route, Routes } from 'react-router-dom';
import CategoryPage from './CategoryPage';
-import Footer from './components/common/Footer'; // Footer 컴포넌트를 import 합니다.
+import Footer from './components/common/Footer';
import Header from './components/common/Header';
+import { AuthProvider, useAuth } from './contexts/AuthContext';
import Home from './Home';
-import Login from './Login';
-import TrendingGifts from './TrendingGifts';
+import Login from './pages/Login';
+import MyAccount from './pages/MyAccount';
const App = () => {
- return (
-
-
- {/* 모든 페이지에서 공통으로 보여지는 Header */}
-
-
- } />
- } />
- } />
- } />
-
-
- {/* 모든 페이지에서 공통으로 보여지는 Footer */}
-
-
- );
+ return (
+
+
+
+ {/* 모든 페이지에서 공통으로 보여지는 Header */}
+
+
+ } />
+ } />
+ } />
+ }>
+ } />
+
+
+
+ {/* 모든 페이지에서 공통으로 보여지는 Footer */}
+
+
+
+ );
+};
+
+const PrivateRoute = () => {
+ const { authToken } = useAuth();
+ return authToken ? : ;
};
export default App;
const AppContainer = styled.div`
- position: relative;
- min-height: 100vh;
+ position: relative;
+ min-height: 100vh;
`;
const Content = styled.div`
- padding-bottom: 60px; /* Footer 공간 확보 */
+ padding-bottom: 60px; /* Footer 공간 확보 */
`;
diff --git a/src/components/common/Header.tsx b/src/components/common/Header.tsx
index 7a0720ed1..c84e87acc 100644
--- a/src/components/common/Header.tsx
+++ b/src/components/common/Header.tsx
@@ -3,13 +3,19 @@ import '../../styles/Header.css'; // CSS 파일 경로를 지정합니다.
import React from 'react';
import { Link } from 'react-router-dom';
+import { useAuth } from '../../contexts/AuthContext';
+
const Header: React.FC = () => {
+ const { authToken } = useAuth();
+
return (
-
로그인
+
+ {authToken ? "내 계정" : "로그인"}
+
);
};
diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx
new file mode 100644
index 000000000..c23201fbd
--- /dev/null
+++ b/src/contexts/AuthContext.tsx
@@ -0,0 +1,38 @@
+import type { ReactNode } from 'react';
+import React, { createContext, useContext, useState } from 'react';
+
+interface AuthContextType {
+ authToken: string | null;
+ login: (token: string) => void;
+ logout: () => void;
+}
+
+const AuthContext = createContext(undefined);
+
+export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
+ const [authToken, setAuthToken] = useState(sessionStorage.getItem('authToken'));
+
+ const login = (token: string) => {
+ setAuthToken(token);
+ sessionStorage.setItem('authToken', token);
+ };
+
+ const logout = () => {
+ setAuthToken(null);
+ sessionStorage.removeItem('authToken');
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useAuth = (): AuthContextType => {
+ const context = useContext(AuthContext);
+ if (context === undefined) {
+ throw new Error('useAuth must be used within an AuthProvider');
+ }
+ return context;
+};
diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx
new file mode 100644
index 000000000..e50d46e60
--- /dev/null
+++ b/src/pages/Login.tsx
@@ -0,0 +1,92 @@
+import styled from '@emotion/styled';
+import React, { useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+
+import { useAuth } from '../contexts/AuthContext';
+
+const Login: React.FC = () => {
+ const [id, setId] = useState('');
+ const [password, setPassword] = useState('');
+ const [message, setMessage] = useState('');
+ const { login } = useAuth();
+ const navigate = useNavigate();
+
+ const handleLogin = (e: React.FormEvent) => {
+ e.preventDefault();
+ // 임의로 로그인 통과
+ login(id);
+ setMessage('로그인 성공');
+ navigate('/');
+ };
+
+ return (
+
+
+ kakao
+ setId(e.target.value)}
+ />
+ setPassword(e.target.value)}
+ />
+ 로그인
+ {message && {message}}
+
+
+ );
+};
+
+export default Login;
+
+const LoginContainer = styled.div`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100vh;
+ background-color: #fff;
+`;
+
+const LoginBox = styled.form`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 20px;
+ border: 1px solid #e0e0e0;
+ border-radius: 8px;
+`;
+
+const Logo = styled.h1`
+ font-size: 24px;
+ margin-bottom: 20px;
+`;
+
+const Input = styled.input`
+ width: 300px;
+ padding: 10px;
+ margin-bottom: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+`;
+
+const LoginButton = styled.button`
+ width: 300px;
+ padding: 10px;
+ background-color: yellow;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 16px;
+ font-weight: bold;
+`;
+
+const Message = styled.p`
+ margin-top: 10px;
+ color: green;
+ font-weight: bold;
+`;
diff --git a/src/pages/MyAccount.tsx b/src/pages/MyAccount.tsx
new file mode 100644
index 000000000..3dbbf7a76
--- /dev/null
+++ b/src/pages/MyAccount.tsx
@@ -0,0 +1,45 @@
+import styled from '@emotion/styled';
+import React from 'react';
+import { useNavigate } from 'react-router-dom';
+
+import { useAuth } from '../contexts/AuthContext';
+
+const MyAccount: React.FC = () => {
+ const { authToken, logout } = useAuth();
+ const navigate = useNavigate();
+
+ const handleLogout = () => {
+ logout();
+ navigate('/');
+ };
+
+ return (
+
+ {authToken}님 안녕하세요!
+ 로그아웃
+
+ );
+};
+
+export default MyAccount;
+
+const AccountContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 100vh;
+`;
+
+const WelcomeMessage = styled.h1`
+ margin-bottom: 20px;
+`;
+
+const LogoutButton = styled.button`
+ background-color: #757575;
+ padding: 10px 20px;
+ font-size: 16px;
+ color: #fff;
+ border: none;
+ cursor: pointer;
+`;
From bb86d089368afd2247e6fccb616b7da94d8c9b1c Mon Sep 17 00:00:00 2001
From: HyeonChae1104 <211063@jnu.ac.kr>
Date: Sat, 6 Jul 2024 01:14:57 +0900
Subject: [PATCH 21/21] =?UTF-8?q?Docs:=20ReadME.md=20=EC=88=98=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 40 ++++++++++++++++++++++++++++++++++++++--
1 file changed, 38 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 1ee7843ff..ec1d2661b 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,8 @@
# 카카오 테크 캠퍼스 - 프론트엔드 카카오 선물하기 편
-### 2주차 Step1 구현 기능 정리
+## 2주차 Step1 구현 기능 정리
1. Header, Footer와 같은 공통 컴포넌트를 만들어요. (모든 페이지에서 Header와 Footer는 보여질 수 있게 적용)
+
2. 각 Url Path별로 페이지 만들기
3. 메인 페이지 (/)
@@ -20,4 +21,39 @@
ID와 PW를 입력하면 로그인이 되도록 구현해요. (ID와 PW는 아무 값을 입력해도 통과되도록 해요.)
6. 나의 페이지(/my-account)
-로그아웃을 할 수있는 버튼을 추가해요.
\ No newline at end of file
+로그아웃을 할 수있는 버튼을 추가해요.
+
+## Step2 인증 프로세스 구현
+### 로그인 페이지에서 ID와 PW를 입력하면 직전 페이지로 Redirect 되도록 구현
+- Fake 로그인 기능을 구현
+1. 로그인 페이지에서 ID와 PW를 입력하면 ID를 sessionStorage의 authToken key에 저장
+2. 모든 페이지 진입 시 authToken을 토대로 로그인 여부 판단 로직 추가(ContextAPI 활용)
+3. Header에서 로그인 한 경우 내 계정을 로그인 하지 않은 경우 로그인 버튼을 추가
+4. 내 계정(/my-account) 페이지는 로그인 한 사람만 접근 가능하게 구현 (로그인 하지 않은 유저는 로그인 페이지로 연결)
+5. 내 계정 페이지에서 로그아웃을 할 수 있도록 구현 (로그아웃 후 메인 페이지(/) 로 Redirect 되도록 해요)
+
+## Step3
+### 질문 1. CRA 기반의 SPA프로젝트에서 React Router를 사용하지 않는다면 어떤 문제가 발생하나요?
+1. SPA에서 페이지 간 이동을 구현하려면 URL 경로를 사용해야하는데, React Router 없이 관리하기 어렵습니다. 따라서 페이지 간 이동이 불편해지는 단점이 발생합니다.
+2. 경로 변경에 따른 컴포넌트 상태 관리가 어렵습니다.
+3. 뒤로 가기, 앞으로 가기 등의 브라우저 네비게이션 기능을 구현하기 어렵습니다.
+
+### 질문 2. 리액트 Context 나 Redux는 언제 사용하면 좋을까요? (로그인을 제외한 예시와 이유를 함께 적어주세요.)
+1. 리액트 Context
+- Context를 사용하면 테마 상태를 최상위 컴포넌트에서 관리하고 하위 컴포넌트에서 쉽게 접근할 수 있습니다.
+
+2. Redux
+많은 컴포넌트에서 상태를 공유하고 업데이트할 필요가 있을 때 Redux를 사용하면 중앙 집중식으로 상태를 관리할 수 있어 유용합니다. 예를 들어, 쇼핑몰의 장바구니 기능이나 제품 필터링 기능을 관리할 때 Redux를 사용하면 상태 관리가 용이합니다.
+
+### 질문 3. Local Storage, Session Storage 와 Cookies의 차이가 무엇이며 각각 어떨때 사용하면 좋을까요?
+1. Local Storage:
+- 특징: 브라우저에 영구적으로 데이터를 저장하며, 브라우저를 닫아도 데이터가 유지됩니다. 용량 제한은 보통 5MB입니다.
+- 사용 예시: 사용자 설정(테마, 언어 등)이나 비영구적인 사용자 데이터(장바구니 아이템)를 저장할 때 유용합니다.
+
+2. Session Storage:
+- 특징: 세션 당 데이터를 저장하며, 브라우저 탭이나 창을 닫으면 데이터가 삭제됩니다. 용량 제한은 Local Storage와 비슷하게 5MB입니다.
+- 사용 예시: 탭 간에 유지되어야 하는 임시 데이터(폼 입력 값, 페이지 간 임시 상태)를 저장할 때 적합합니다.
+
+3. Cookies:
+- 특징: 서버와의 통신에 사용되며, 만료 기간을 설정할 수 있습니다. 용량 제한은 약 4KB로 작습니다. HttpOnly 속성을 설정하면 JavaScript로 접근할 수 없으며, 보안이 필요할 때 사용됩니다.
+- 사용 예시: 사용자 인증 정보(세션 토큰), 트래킹 정보, 사용자 식별 정보를 저장할 때 사용됩니다. 서버와의 통신에 필수적인 데이터를 저장할 때 적합합니다.
\ No newline at end of file