diff --git a/README.md b/README.md
index 9fa347d00..48f6d97cd 100644
--- a/README.md
+++ b/README.md
@@ -13,3 +13,23 @@
## Week 2. 1단계 - 페이지 만들기
[🔗 link](https://edu.nextstep.camp/s/hazAC9xa/ls/QzV1ncxk)
+
+# STEP3
+📝질문 1. CRA 기반의 SPA프로젝트에서 React Router를 사용하지 않는다면 어떤 문제가 발생하나요?
+---
+SPA는 하나의 페이지에서 모든 내용을 제공하는 웹 어플리케이션인데, 경로에 따라 다른 화면을 보여주기 위해 React Router를 사용한다. 그렇기에 React Router를 사용하지 않는다면 특정 페이지로 이동한든 것이 쉽지 않을 것이며 상태관리도 어렵고 페이지별로 컴포넌트를 분리하여 사용하는 것 또한 어려워질 것 이다.
+
+📝질문 2. 리액트 Context 나 Redux는 언제 사용하면 좋을까요? (로그인을 제외한 예시와 이유를 함께 적어주세요.)
+---
+ Context 나 Redux는 상태 관리 라이브러리 이다. 그렇기에
+ ConText는 테마의 상태를 전역적으로 관리하는데 유용하게 쓰일 수 있을 것이다. 테마는 애플리케이션 전반적으로 영향을 미치고 여러 컴포넌트에서 접근할 필요가 있기 때문에 context를 사용한다면 쉽게 접근할 수 있을 것이다.
+Redux는 전자상거래에 유용하게 쓰일 수 잇을 것이다.
+대규모의 쇼핑몰을 운영하게 된다면 상태가 매우 복잡해질 것이고 여러 컴포넌트가 서로 다른 상태를 공유하고 업데이트 해야 하기에 Redux를 사용한다면 상태의 일관성을 유지하고 디버깅을 편하게 할 수 있을 것이다.
+
+📝질문 3. Local Storage, Session Storage 와 Cookies의 차이가 무엇이며 각각 어떨때 사용하면 좋을까요?
+---
+- Local Storage
Local Storage는 영구적인 저장소로 브라우저가 닫히고 다시 열려도 데이터가 유지된다. 영구적인 저장소이기에 사용자 개개인의 설정이나 아이디 비밀번호 기억 등의 기능에 사용면 좋을 것이다
+- Session Storage
Session Storage는 브라우저 창이나 탭이 닫히면 데이터도 함께 삭제 되는 저장소이다.
+유튜브의 검색창에 입력한 내용 등 일시적인 입력 데이터 등에 사용하면 좋을 것이다.
+- Cookies
Cookies는 만료 날짜를 설정할 수 있는 저장소로 설정 기간 동안에만 유지되는 특성이 있다.
+쿠키는 특정 기간만 이용할 수 있는 구독 서비스 등에 사용자 인증 정보를 부여하는 곳에 사용한다면 좋을 것이다.
diff --git a/package-lock.json b/package-lock.json
index 8f100a3a8..581b61b33 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,8 +10,9 @@
"dependencies": {
"@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0",
- "react": "^18.2.0",
- "react-dom": "^18.2.0"
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "react-router-dom": "^6.24.1"
},
"devDependencies": {
"@craco/craco": "^7.1.0",
@@ -6135,6 +6136,14 @@
"@babel/runtime": "^7.13.10"
}
},
+ "node_modules/@remix-run/router": {
+ "version": "1.17.1",
+ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.17.1.tgz",
+ "integrity": "sha512-mCOMec4BKd6BRGBZeSnGiIgwsbLGp3yhVqAD8H+PxiRNEHgDpZb8J1TnrSDlg97t0ySKMQJTHCWBCmBpSmkF6Q==",
+ "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",
@@ -27856,9 +27865,9 @@
}
},
"node_modules/react": {
- "version": "18.2.0",
- "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
- "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"dependencies": {
"loose-envify": "^1.1.0"
},
@@ -28219,15 +28228,15 @@
"dev": true
},
"node_modules/react-dom": {
- "version": "18.2.0",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
- "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"dependencies": {
"loose-envify": "^1.1.0",
- "scheduler": "^0.23.0"
+ "scheduler": "^0.23.2"
},
"peerDependencies": {
- "react": "^18.2.0"
+ "react": "^18.3.1"
}
},
"node_modules/react-element-to-jsx-string": {
@@ -28318,6 +28327,36 @@
}
}
},
+ "node_modules/react-router": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.24.1.tgz",
+ "integrity": "sha512-PTXFXGK2pyXpHzVo3rR9H7ip4lSPZZc0bHG5CARmj65fTT6qG7sTngmb6lcYu1gf3y/8KxORoy9yn59pGpCnpg==",
+ "dependencies": {
+ "@remix-run/router": "1.17.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8"
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.24.1.tgz",
+ "integrity": "sha512-U19KtXqooqw967Vw0Qcn5cOvrX5Ejo9ORmOtJMzYWtCT4/WOfFLIZGGsVLxcd9UkBO0mSTZtXqhZBsWlHr7+Sg==",
+ "dependencies": {
+ "@remix-run/router": "1.17.1",
+ "react-router": "6.24.1"
+ },
+ "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",
@@ -29371,9 +29410,9 @@
}
},
"node_modules/scheduler": {
- "version": "0.23.0",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
- "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
"dependencies": {
"loose-envify": "^1.1.0"
}
diff --git a/package.json b/package.json
index 8a3e091c7..29331e112 100644
--- a/package.json
+++ b/package.json
@@ -24,12 +24,11 @@
"dependencies": {
"@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0",
- "react": "^18.2.0",
- "react-dom": "^18.2.0"
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "react-router-dom": "^6.24.1"
},
"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": {
diff --git a/src/App.tsx b/src/App.tsx
index 1df5ce256..9b6f05533 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,18 +1,17 @@
-import styled from '@emotion/styled';
+import React from 'react';
+import { BrowserRouter as Router } from 'react-router-dom';
-const App = () => {
- const name = 'Josh Perez';
+import { AuthProvider } from './context/AuthContext';
+import RoutesPage from './router/Router';
+const App: React.FC = () => {
return (
-
-
Hello, {name}
-
+
+
+
+
+
);
};
export default App;
-
-const Title = styled.h1`
- font-size: 1.5em;
- color: gray;
-`;
diff --git a/src/Layout/Footer/index.tsx b/src/Layout/Footer/index.tsx
new file mode 100644
index 000000000..66a1808a0
--- /dev/null
+++ b/src/Layout/Footer/index.tsx
@@ -0,0 +1,25 @@
+import styled from '@emotion/styled';
+import React from 'react';
+
+export const Footer: React.FC = () => {
+ return (
+
+ 카카오톡 선물하기
+
+ );
+};
+
+export default Footer;
+
+const Wrapper = styled.footer`
+ padding: 28px 16px 88px;
+ width: 80%;
+ margin: 0 auto;
+ max-width: 100vw;
+ background-color: #fafafc;
+`;
+
+const Content = styled.p`
+ font-size: 20px;
+ font-weight: bold;
+`;
diff --git a/src/Layout/Header/index.tsx b/src/Layout/Header/index.tsx
new file mode 100644
index 000000000..668b15c41
--- /dev/null
+++ b/src/Layout/Header/index.tsx
@@ -0,0 +1,54 @@
+import styled from '@emotion/styled';
+import React from 'react';
+import { useNavigate } from 'react-router-dom';
+
+interface HeaderProps {
+ isLoggedIn: boolean;
+}
+
+const Header: React.FC = ({ isLoggedIn }) => {
+ const navigate = useNavigate();
+
+ const handleLoginClick = () => {
+ navigate('/login');
+ };
+
+ const handleAccountClick = () => {
+ navigate('/my-account');
+ };
+
+ return (
+
+ 선물하기
+ {isLoggedIn ? (
+
+ ) : (
+
+ )}
+
+ );
+};
+
+export default Header;
+
+const HeaderWrapper = styled.header`
+ display: flex;
+ width: 80%;
+ margin: 0 auto;
+ justify-content: space-between;
+ align-items: center;
+ padding: 16px;
+ background-color: #ffffff;
+`;
+
+const Title = styled.h1`
+ font-size: 30px;
+ font-weight: bold;
+`;
+
+const Button = styled.button`
+ background: none;
+ border: none;
+ font-size: 16px;
+ cursor: pointer;
+`;
diff --git a/src/Layout/index.tsx b/src/Layout/index.tsx
new file mode 100644
index 000000000..21ca46deb
--- /dev/null
+++ b/src/Layout/index.tsx
@@ -0,0 +1,21 @@
+import React from 'react';
+import { Outlet } from 'react-router-dom';
+
+import Footer from '@/Layout/Footer';
+import Header from '@/Layout/Header';
+
+interface LayoutProps {
+ isLoggedIn: boolean;
+}
+
+const Layout: React.FC = ({ isLoggedIn }) => {
+ return (
+
+
+
+
+
+ );
+};
+
+export default Layout;
diff --git a/src/Pages/Home/index.tsx b/src/Pages/Home/index.tsx
new file mode 100644
index 000000000..b9fba68fb
--- /dev/null
+++ b/src/Pages/Home/index.tsx
@@ -0,0 +1,17 @@
+import AiReference from "@/components/common/HomeComponents/AiReference";
+import Banner from "@/components/common/HomeComponents/Banner";
+import Ranking from "@/components/common/HomeComponents/Ranking";
+import ThemeCategory from "@/components/common/HomeComponents/ThemeCategory";
+
+export const Home = () => {
+ return(
+ <>
+
+
+
+
+ >
+ )
+}
+
+export default Home
\ No newline at end of file
diff --git a/src/Pages/Login/index.tsx b/src/Pages/Login/index.tsx
new file mode 100644
index 000000000..7ccfe892b
--- /dev/null
+++ b/src/Pages/Login/index.tsx
@@ -0,0 +1,114 @@
+import styled from '@emotion/styled';
+import React, { useState } from 'react';
+import { useLocation,useNavigate } from 'react-router-dom';
+
+import { useAuth } from '@/context/AuthContext';
+
+const Login: React.FC = () => {
+ const [username, setUsername] = useState('');
+ const [password, setPassword] = useState('');
+ const navigate = useNavigate();
+ const location = useLocation();
+ const { login } = useAuth();
+
+ const from = location.state?.from?.pathname || '/'; // 이전 경로 또는 기본 경로
+
+ const handleSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+ if (!username || !password) {
+ alert('아이디와 비밀번호를 입력하세요');
+ return;
+ }
+ login(username);
+ navigate(from, { replace: true }); // 로그인 후 이전 경로로 리디렉션
+ };
+
+ return (
+
+
+ kakao
+
+
+
+
+
+ );
+};
+
+export default Login;
+
+const Wrapper = styled.div`
+ height: 500px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background-color: white;
+`;
+
+const ContentWrapper = styled.div`
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+`;
+
+const Title = styled.h1`
+ margin-bottom: 20px;
+ font-size: 32px;
+ font-weight: bold;
+`;
+
+const FormWrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ background-color: white;
+ width: 500px;
+ height: 250px;
+ padding: 40px;
+ border: 1px solid black;
+`;
+
+const Form = styled.form`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ width: 100%;
+ padding: 10px;
+`;
+
+const Input = styled.input`
+ margin-bottom: 10px;
+ padding: 10px;
+ width: 100%;
+ border: none;
+ border-bottom: 1px solid #dee2e6;
+ border-radius: 0;
+ font-size: 16px;
+`;
+
+const Button = styled.button`
+ padding: 10px;
+ width: 100%;
+ background-color: #ffeb00;
+ border: none;
+ border-radius: 5px;
+ cursor: pointer;
+ font-size: 16px;
+ font-weight: bold;
+`;
\ No newline at end of file
diff --git a/src/Pages/MyAccount/index.tsx b/src/Pages/MyAccount/index.tsx
new file mode 100644
index 000000000..8b6d1b6a1
--- /dev/null
+++ b/src/Pages/MyAccount/index.tsx
@@ -0,0 +1,66 @@
+import styled from '@emotion/styled';
+import React, { useEffect, useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+
+// 세션 스토리지에서 사용자 ID를 가져오는 함수
+export const getAuthToken = (): string | null => {
+ return sessionStorage.getItem('authToken');
+};
+
+interface MyAccountProps {
+ onLogout: () => void;
+}
+
+const MyAccount: React.FC = ({ onLogout }) => {
+ const navigate = useNavigate();
+ const [username, setUsername] = useState(null);
+
+ useEffect(() => {
+ const token = getAuthToken();
+ setUsername(token);
+ }, []);
+
+ const handleLogoutClick = () => {
+ onLogout();
+ navigate('/');
+ };
+
+ return (
+
+ {username ? `${username}님 반갑습니다!` : '반갑습니다!'}
+ 로그아웃
+
+ );
+};
+
+export default MyAccount;
+
+const Wrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 500px;
+ background-color: white;
+`;
+
+const Title = styled.h1`
+ margin-bottom: 20px;
+ font-size: 32px;
+ font-weight: bold;
+ text-align: center;
+`;
+
+const LogoutButton = styled.button`
+ padding: 10px;
+ width: 100%;
+ max-width: 400px;
+ background-color: #ffeb00;
+ border: none;
+ border-radius: 5px;
+ cursor: pointer;
+ font-size: 16px;
+ font-weight: bold;
+ text-align: center;
+ margin: 0 auto;
+`;
diff --git a/src/Pages/Theme/Goods.tsx b/src/Pages/Theme/Goods.tsx
new file mode 100644
index 000000000..d669708a7
--- /dev/null
+++ b/src/Pages/Theme/Goods.tsx
@@ -0,0 +1,31 @@
+import styled from '@emotion/styled';
+
+import {DefaultGoodsItems} from "@/components/common/GoodsItem/Default"
+import {itemList} from "@/components/common/HomeComponents/Ranking/ItemList/Items"
+import {Grid} from "@/components/common/layouts/Grid"
+
+
+export const ItemList = () =>{
+
+ return(
+
+
+ {itemList.map((item) => (
+
+ ))}
+
+
+ )
+
+}
+
+export default ItemList;
+
+const Wrapper = styled.div`
+ width: "80%";
+`
\ No newline at end of file
diff --git a/src/Pages/Theme/Header.tsx b/src/Pages/Theme/Header.tsx
new file mode 100644
index 000000000..a69f5b30e
--- /dev/null
+++ b/src/Pages/Theme/Header.tsx
@@ -0,0 +1,42 @@
+import styled from '@emotion/styled';
+
+export const Header = () => {
+ return (
+
+
+
+ 예산은 가볍게, 감동은 무겁게❤️
+ 당신의 센스를 뽐내줄 부담 없는 선물
+
+
+ );
+};
+
+export default Header;
+
+const Wrapper = styled.header`
+ background-color: rgb(75, 77, 80);
+ padding: 50px 20px;
+`;
+const Container = styled.div`
+ max-width: 80%;
+ margin: 0 auto;
+ display: flex;
+ flex-direction: column;
+`;
+const Label = styled.p`
+ font-size: 20px;
+ margin-bottom: 10px;
+ font-weight: bold;
+ color: rgba(255, 255, 255, 0.7);
+`;
+const Title = styled.h1`
+ font-size: 40px;
+ margin-bottom: 10px;
+ font-weight: bold;
+ color: rgb(255, 255, 255);
+`;
+const Description = styled.p`
+ font-size: 20px;
+ color:rgba(255, 255, 255, 0.55);
+`;
\ No newline at end of file
diff --git a/src/Pages/Theme/index.tsx b/src/Pages/Theme/index.tsx
new file mode 100644
index 000000000..9e8077505
--- /dev/null
+++ b/src/Pages/Theme/index.tsx
@@ -0,0 +1,15 @@
+// import React from 'react';
+import Goods from './Goods';
+import {Header} from "./Header";
+
+const Theme = () => {
+ return (
+ <>
+
+
+ >
+ );
+
+};
+
+export default Theme;
diff --git a/src/components/common/HomeComponents/AiReference/index.tsx b/src/components/common/HomeComponents/AiReference/index.tsx
new file mode 100644
index 000000000..5915dff7f
--- /dev/null
+++ b/src/components/common/HomeComponents/AiReference/index.tsx
@@ -0,0 +1,41 @@
+import styled from '@emotion/styled';
+export const AiReference = () => {
+ return (
+
+
+
+ 선물을 추천받고 싶은 친구를 선택해주세요.
+
+
+ );
+};
+
+export default AiReference;
+const Wrapper = styled.section`
+ padding: 20px;
+`;
+
+const Banner = styled.div`
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ background-color: #feeb00;
+ padding: 16px;
+ border-radius: 8px;
+
+`;
+
+const Label = styled.p`
+ font-size: 15px;
+ font-weight: 500;
+ color: rgba(0, 0, 0, 0.4);
+ padding-bottom: 3px;
+
+`;
+
+const Title = styled.h1`
+ font-size: 20px;
+ font-weight: 700;
+ color: #000;
+`;
diff --git a/src/components/common/HomeComponents/Banner/index.tsx b/src/components/common/HomeComponents/Banner/index.tsx
new file mode 100644
index 000000000..b71469f98
--- /dev/null
+++ b/src/components/common/HomeComponents/Banner/index.tsx
@@ -0,0 +1,41 @@
+import styled from '@emotion/styled';
+import React from 'react';
+
+import { Image } from '@/components/common/Image';
+
+export const Banner: React.FC = () => {
+ return (
+
+
+ {alert("선물 받을 친구 선택하기")}}
+ />
+ 선물 받을 친구를 선택해주세요.
+
+
+ );
+};
+
+export default Banner;
+
+const Wrapper = styled.section`
+ padding: 16px;
+ background-color: rgb(250, 250, 250);
+`;
+
+const Content = styled.div`
+ display: flex;
+ align-items: center;
+ width: 80%;
+ margin: 0 auto;
+`;
+
+const Text = styled.p`
+ padding-left: 16px;
+ margin: 0;
+`;
diff --git a/src/components/common/HomeComponents/Ranking/Filter/index.tsx b/src/components/common/HomeComponents/Ranking/Filter/index.tsx
new file mode 100644
index 000000000..3af19f2d0
--- /dev/null
+++ b/src/components/common/HomeComponents/Ranking/Filter/index.tsx
@@ -0,0 +1,80 @@
+import styled from '@emotion/styled';
+import { useState } from 'react';
+
+import PersonButton from '@/components/common/HomeComponents/Ranking/PersonButton';
+import RankTypeButton from '@/components/common/HomeComponents/Ranking/RankButton';
+
+const initialFilterOption = {
+ targetType: 'ALL',
+ rankType: 'MANY_WISH',
+};
+
+export const Filter = () => {
+ const [filterOption, setFilterOption] = useState(initialFilterOption);
+
+ return (
+
+
+ setFilterOption({ ...filterOption, targetType: 'ALL' })}
+ />
+ setFilterOption({ ...filterOption, targetType: 'FEMALE' })}
+ />
+ setFilterOption({ ...filterOption, targetType: 'MALE' })}
+ />
+ setFilterOption({ ...filterOption, targetType: 'TEEN' })}
+ />
+
+
+ setFilterOption({ ...filterOption, rankType: 'MANY_WISH' })}
+ />
+ setFilterOption({ ...filterOption, rankType: 'MANY_RECEIVE' })}
+ />
+ setFilterOption({ ...filterOption, rankType: 'MANY_WISH_RECEIVE' })}
+ />
+
+
+
+ );
+};
+
+const Wrapper = styled.div`
+ padding: 20px;
+`;
+const RankTypeButtons = styled.div`
+ display: flex;
+ justify-content: space-around;
+ background-color:#e6f1ff;
+ border-radius: 20px;
+ `;
+const PersonButtons = styled.div`
+ margin-bottom: 10px;
+ display: flex;
+ justify-content: space-around;
+`;
+
+export default Filter;
diff --git a/src/components/common/HomeComponents/Ranking/ItemList/Items.ts b/src/components/common/HomeComponents/Ranking/ItemList/Items.ts
new file mode 100644
index 000000000..d2e18956c
--- /dev/null
+++ b/src/components/common/HomeComponents/Ranking/ItemList/Items.ts
@@ -0,0 +1,15 @@
+type Data= {
+ imageSrc: string;
+ subtitle: string;
+ title: string;
+ amount: number;
+};
+
+const item: Data = {
+ imageSrc: "https://st.kakaocdn.net/product/gift/product/20231030175450_53e90ee9708f45ffa45b3f7b4bc01c7c.jpg",
+ subtitle: "BBQ",
+ title: "BBQ 양념치킨+크림치즈볼+콜라1.25L",
+ amount: 29000
+};
+
+export const itemList:Data[] = Array.from({ length: 21 }, () => item);
\ No newline at end of file
diff --git a/src/components/common/HomeComponents/Ranking/ItemList/index.tsx b/src/components/common/HomeComponents/Ranking/ItemList/index.tsx
new file mode 100644
index 000000000..b0c246b2d
--- /dev/null
+++ b/src/components/common/HomeComponents/Ranking/ItemList/index.tsx
@@ -0,0 +1,67 @@
+import styled from '@emotion/styled';
+import { useState } from 'react';
+
+import {Button} from "@/components/common/Button"
+import {RankingGoodsItems} from "@/components/common/GoodsItem/Ranking"
+import {Grid} from "@/components/common/layouts/Grid"
+
+import {itemList} from "./Items"
+
+
+export const ItemList = () =>{
+
+ const [All, setAll] = useState(false);
+
+ const handleToggleShowAll = () => {
+ setAll(!All);
+ };
+
+ const itemsToShow = All ? itemList : itemList.slice(0, 6);
+
+ return(
+
+
+ {itemsToShow.map((item, index) => (
+
+ ))}
+
+
+ {All? '접기' : '더보기'}
+
+
+ )
+
+}
+
+export default ItemList
+const Wrapper = styled.div`
+ width: "100%";
+`
+
+const TogleButton = styled(Button)`
+width: 800px;
+position: relative;
+margin: 20px auto;
+background-color: white;
+border: none;
+
+ &:hover {
+ background-color:rgb(174, 174, 174);
+ border: none;
+ box-shadow: none;
+ outline: none;
+ }
+
+ &:focus {
+ border: none;
+ box-shadow: none;
+ outline: none;
+ }
+`
diff --git a/src/components/common/HomeComponents/Ranking/PersonButton/index.tsx b/src/components/common/HomeComponents/Ranking/PersonButton/index.tsx
new file mode 100644
index 000000000..ac5eb5497
--- /dev/null
+++ b/src/components/common/HomeComponents/Ranking/PersonButton/index.tsx
@@ -0,0 +1,53 @@
+import styled from '@emotion/styled';
+import React from 'react';
+
+type Props = {
+ label: string;
+ icon: React.ReactNode;
+ selected: boolean;
+ onClick: () => void;
+};
+
+export const PersonButton = ({ selected, label, icon, onClick }: Props) => {
+ return (
+
+ {icon}
+
+
+ );
+};
+
+const Wrapper = styled.button`
+ width: 100%;
+ min-width: 58px;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ &:focus {
+ outline: none;
+`;
+
+const Icon = styled.div>`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 50px;
+ height: 50px;
+ color: #fff;
+ background-color: ${({ selected }) => (selected ? '#4684e9' : '#e6f1ff')};
+ border-radius: 20px;
+
+ }
+`;
+
+const Label = styled.p>`
+ padding-top: 5px;
+ font-size: 20px;
+ line-height: 16px;
+ color: ${({ selected }) => (selected ? '#4684e9' : '#666')};
+ font-weight: ${({ selected }) => (selected ? 700 : 400)};
+ transition: color 200ms, font-weight 200ms;
+`;
+
+export default PersonButton;
diff --git a/src/components/common/HomeComponents/Ranking/RankButton/index.tsx b/src/components/common/HomeComponents/Ranking/RankButton/index.tsx
new file mode 100644
index 000000000..4db25d060
--- /dev/null
+++ b/src/components/common/HomeComponents/Ranking/RankButton/index.tsx
@@ -0,0 +1,30 @@
+import styled from '@emotion/styled';
+
+type Props = {
+ label: string;
+ selected: boolean;
+ onClick: () => void;
+};
+
+export const RankTypeButton = ({ selected, label, onClick }: Props) => {
+ return (
+
+ {label}
+
+ );
+};
+
+export default RankTypeButton
+
+const Wrapper = styled.button>`
+ padding: 13px 20px;
+ font-size: 20px;
+ line-height: 16px;
+ color: ${({ selected }) => (selected ? '#4684e9' : 'rgba(70, 132, 233, 0.7)')};
+ font-weight: ${({ selected }) => (selected ? 700 : 400)};
+ transition: color 200ms, font-weight 200ms;
+ &:focus {
+ outline: none;
+ }
+`;
+export {};
\ No newline at end of file
diff --git a/src/components/common/HomeComponents/Ranking/index.tsx b/src/components/common/HomeComponents/Ranking/index.tsx
new file mode 100644
index 000000000..d71f66183
--- /dev/null
+++ b/src/components/common/HomeComponents/Ranking/index.tsx
@@ -0,0 +1,27 @@
+import styled from '@emotion/styled';
+
+import Filter from '@/components/common/HomeComponents/Ranking/Filter';
+import ItemList from '@/components/common/HomeComponents/Ranking/ItemList';
+
+
+
+export const Ranking = () =>{
+ return(
+
+
+ 실시간 급상승 선물랭킹
+
+
+
+
+ );
+}
+export default Ranking;
+
+const Wrapper = styled.div`
+ width: 100%;
+`;
+const Title = styled.h2`
+ width: 100%;
+ font-size: 24px;
+ text-align: center;`
\ No newline at end of file
diff --git a/src/components/common/HomeComponents/ThemeCategory/ItemImage.tsx b/src/components/common/HomeComponents/ThemeCategory/ItemImage.tsx
new file mode 100644
index 000000000..e33d137e7
--- /dev/null
+++ b/src/components/common/HomeComponents/ThemeCategory/ItemImage.tsx
@@ -0,0 +1,41 @@
+import styled from '@emotion/styled';
+import { Link } from 'react-router-dom';
+
+import { Image } from '@/components/common/Image';
+type Props = {
+ imageSrc: string;
+ label: string;
+ to: string;
+} & React.HTMLAttributes;
+
+export const ItemImage = ({ imageSrc, label, to }: Props) => (
+
+
+
+
+
+
+);
+
+const Wrapper = styled.div`
+ width: 100%;
+ padding: 13px 0 12px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+`;
+
+const StyledImage = styled(Image)`
+ width: 100%;
+ height: 100%;
+ max-width: 100px;
+ max-height: 100px;
+`;
+
+const Label = styled.p`
+ padding-top: 5px;
+ font-size: 13px;
+ line-height: 17px;
+ color: #333;
+`;
diff --git a/src/components/common/HomeComponents/ThemeCategory/index.tsx b/src/components/common/HomeComponents/ThemeCategory/index.tsx
new file mode 100644
index 000000000..04b90fb10
--- /dev/null
+++ b/src/components/common/HomeComponents/ThemeCategory/index.tsx
@@ -0,0 +1,52 @@
+import styled from '@emotion/styled';
+
+import { Container } from '@/components/common/layouts/Container';
+import { Grid } from '@/components/common/layouts/Grid';
+import { getDynamicPath, ROUTE_PATHS } from '@/router/paths';
+
+import { ItemImage } from './ItemImage';
+
+export const ThemeCategory = () => {
+ const themes = [
+ { label: '생일', key: 'birthday' },
+ { label: '졸업선물', key: 'graduation' },
+ { label: '스몰럭셔리', key: 'small-luxury' },
+ { label: '명품선물', key: 'luxury' },
+ { label: '결혼/집들이', key: 'wedding' },
+ { label: '따뜻한선물', key: 'warm-gift' },
+ { label: '가벼운선물', key: 'light-gift' },
+ { label: '팬심저격', key: 'fan-favorite' },
+ { label: '교환권', key: 'exchange' },
+ { label: '건강/비타민', key: 'health' },
+ { label: '과일/한우', key: 'fruits-meat' },
+ { label: '출산/키즈', key: 'kids' },
+ ];
+
+ return (
+
+
+
+ {themes.map((theme) => (
+
+ ))}
+
+
+
+ );
+};
+
+export default ThemeCategory;
+
+const Wrapper = styled.div`
+ margin-top: 30px;
+`;
diff --git a/src/components/common/layouts/Container/index.tsx b/src/components/common/layouts/Container/index.tsx
index 06bfca43e..e1181b9aa 100644
--- a/src/components/common/layouts/Container/index.tsx
+++ b/src/components/common/layouts/Container/index.tsx
@@ -21,7 +21,6 @@ export const Container: React.FC = forwardRef(
const Wrapper = styled.div`
width: 100%;
-
display: flex;
justify-content: center;
align-items: center;
diff --git a/src/context/AuthContext.tsx b/src/context/AuthContext.tsx
new file mode 100644
index 000000000..5a0617cf6
--- /dev/null
+++ b/src/context/AuthContext.tsx
@@ -0,0 +1,49 @@
+import type { ReactNode } from 'react';
+import React, { createContext, useContext, useEffect, useState } from 'react';
+
+interface AuthContextProps {
+ isLoggedIn: boolean;
+ login: (username: string) => void;
+ logout: () => void;
+}
+
+const AuthContext = createContext(undefined);
+
+interface AuthProviderProps {
+ children: ReactNode;
+}
+
+export const AuthProvider: React.FC = ({ children }) => {
+ const [isLoggedIn, setIsLoggedIn] = useState(false);
+
+ useEffect(() => {
+ const authToken = sessionStorage.getItem('authToken');
+ if (authToken) {
+ setIsLoggedIn(true);
+ }
+ }, []);
+
+ const login = (username: string) => {
+ sessionStorage.setItem('authToken', username);
+ setIsLoggedIn(true);
+ };
+
+ const logout = () => {
+ sessionStorage.removeItem('authToken');
+ setIsLoggedIn(false);
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useAuth = (): AuthContextProps => {
+ const context = useContext(AuthContext);
+ if (!context) {
+ throw new Error('useAuth must be used within an AuthProvider');
+ }
+ return context;
+};
diff --git a/src/index.tsx b/src/index.tsx
index ab5f7ad6a..59dc14467 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -1,13 +1,12 @@
-import '@/styles';
import React from 'react';
-import ReactDOM from 'react-dom/client';
+import ReactDOM from 'react-dom';
-import App from '@/App';
+import App from './App';
-const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
-root.render(
+ReactDOM.render(
-
+
,
+ document.getElementById('root')
);
diff --git a/src/router/Router.tsx b/src/router/Router.tsx
new file mode 100644
index 000000000..721f682bf
--- /dev/null
+++ b/src/router/Router.tsx
@@ -0,0 +1,40 @@
+import React from 'react';
+import { Navigate, Route, Routes, useLocation } from 'react-router-dom';
+
+import { useAuth } from '@/context/AuthContext';
+import Layout from '@/Layout';
+import Home from '@/Pages/Home';
+import Login from '@/Pages/Login';
+import MyAccount from '@/Pages/MyAccount';
+import Theme from '@/Pages/Theme';
+
+import { ROUTE_PATHS } from './paths';
+
+interface ProtectedRouteProps {
+ element: React.ReactElement;
+ onLogout?: () => void; // 추가적인 props를 허용
+}
+
+const ProtectedRoute: React.FC = ({ element, ...rest }) => {
+ const { isLoggedIn } = useAuth();
+ const location = useLocation();
+ return isLoggedIn ? React.cloneElement(element, { ...rest }) : ;
+};
+
+const RoutesPage: React.FC = () => {
+ const { isLoggedIn, logout } = useAuth();
+
+ return (
+
+ }>
+ } />
+ } />
+ } />
+ } />} />
+
+ } />
+
+ );
+};
+
+export default RoutesPage;
\ No newline at end of file
diff --git a/src/router/paths.tsx b/src/router/paths.tsx
new file mode 100644
index 000000000..aa75806e4
--- /dev/null
+++ b/src/router/paths.tsx
@@ -0,0 +1,11 @@
+export const ROUTE_PATHS = {
+ ROOT: '/',
+ HOME: '/',
+ THEME: '/theme/:themeKey',
+ MYPAGE: '/my-account',
+ LOGIN: '/login',
+};
+
+export const getDynamicPath = (path: string, params: { [key: string]: string }): string => {
+ return path.replace(/:(\w+)/g, (_, key) => params[key]);
+};