diff --git a/.env b/.env
new file mode 100644
index 00000000..ca303784
--- /dev/null
+++ b/.env
@@ -0,0 +1 @@
+REACT_APP_BASE_URL=https://panda-market-api.vercel.app
diff --git a/package-lock.json b/package-lock.json
index a1e590ee..ba8d278b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,6 +13,7 @@
"@testing-library/user-event": "^13.5.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
+ "react-router-dom": "^7.6.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
}
@@ -6981,14 +6982,6 @@
"tslib": "^2.0.3"
}
},
- "node_modules/dotenv": {
- "version": "10.0.0",
- "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz",
- "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==",
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/dotenv-expand": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz",
@@ -14671,6 +14664,53 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-router": {
+ "version": "7.6.0",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.0.tgz",
+ "integrity": "sha512-GGufuHIVCJDbnIAXP3P9Sxzq3UUsddG3rrI3ut1q6m0FI6vxVBF3JoPQ38+W/blslLH4a5Yutp8drkEpXoddGQ==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "7.6.0",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.6.0.tgz",
+ "integrity": "sha512-DYgm6RDEuKdopSyGOWZGtDfSm7Aofb8CCzgkliTjtu/eDuB0gcsv6qdFhhi8HdtmA+KHkt5MfZ5K2PdzjugYsA==",
+ "license": "MIT",
+ "dependencies": {
+ "react-router": "7.6.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ }
+ },
+ "node_modules/react-router/node_modules/cookie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
+ "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/react-scripts": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
@@ -14743,6 +14783,15 @@
}
}
},
+ "node_modules/react-scripts/node_modules/dotenv": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz",
+ "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -15480,6 +15529,12 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
+ "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
+ "license": "MIT"
+ },
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
diff --git a/package.json b/package.json
index 7ff0d6b5..cc88c710 100644
--- a/package.json
+++ b/package.json
@@ -8,6 +8,7 @@
"@testing-library/user-event": "^13.5.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
+ "react-router-dom": "^7.6.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
diff --git a/public/favicon.ico b/public/favicon.ico
deleted file mode 100644
index a11777cc..00000000
Binary files a/public/favicon.ico and /dev/null differ
diff --git a/public/logo192.png b/public/logo192.png
deleted file mode 100644
index fc44b0a3..00000000
Binary files a/public/logo192.png and /dev/null differ
diff --git a/public/logo512.png b/public/logo512.png
deleted file mode 100644
index a4e47a65..00000000
Binary files a/public/logo512.png and /dev/null differ
diff --git a/src/App.css b/src/App.css
index 74b5e053..e69de29b 100644
--- a/src/App.css
+++ b/src/App.css
@@ -1,38 +0,0 @@
-.App {
- text-align: center;
-}
-
-.App-logo {
- height: 40vmin;
- pointer-events: none;
-}
-
-@media (prefers-reduced-motion: no-preference) {
- .App-logo {
- animation: App-logo-spin infinite 20s linear;
- }
-}
-
-.App-header {
- background-color: #282c34;
- min-height: 100vh;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- font-size: calc(10px + 2vmin);
- color: white;
-}
-
-.App-link {
- color: #61dafb;
-}
-
-@keyframes App-logo-spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
-}
diff --git a/src/App.js b/src/App.js
index 37845757..01454318 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,24 +1,20 @@
-import logo from './logo.svg';
+import { Route, Routes } from 'react-router-dom';
import './App.css';
+import Root from './pages/root.jsx';
+import Login from './pages/login.jsx';
+import Register from './pages/register.jsx';
+import Items from './pages/items.jsx';
function App() {
return (
-
+ <>
+
+ } />
+ } />
+ } />
+ } />
+
+ >
);
}
diff --git a/src/api/fetchCardList.js b/src/api/fetchCardList.js
new file mode 100644
index 00000000..f97bbe1f
--- /dev/null
+++ b/src/api/fetchCardList.js
@@ -0,0 +1,12 @@
+/**
+ * params: quantity, page, orderBy
+*/
+export default async function fetchLists(quantity, page, orderBy) {
+ try {
+ const res = await fetch(`${process.env.REACT_APP_BASE_URL}/products?page=${page}&pageSize=${quantity}&orderBy=${orderBy}`)
+ const data = await res.json();
+ return { totalCount: data.totalCount, list: data.list };
+ } catch (err) {
+ console.error(err)
+ }
+}
diff --git a/src/components/button.css b/src/components/button.css
new file mode 100644
index 00000000..08af0a63
--- /dev/null
+++ b/src/components/button.css
@@ -0,0 +1,30 @@
+@import '../theme.css';
+
+.button-big {
+ border: 0;
+ width: 100%;
+ height: 56px;
+ border-radius: 40px;
+ background-color: var(--primary-100);
+ color: white;
+ font-weight: 600;
+ font-size: 20px;
+ cursor: pointer;
+}
+
+.button-small {
+ border: 0;
+ width: fit-content;
+ height: 40px;
+ padding: 12px 23px;
+ border-radius: 8px;
+ background-color: var(--primary-100);
+ color: white;
+ font-weight: 600;
+ font-size: 16px;
+ cursor: pointer;
+}
+
+.button:disabled {
+ background-color: var(--secondary-400);
+}
diff --git a/src/components/button.jsx b/src/components/button.jsx
new file mode 100644
index 00000000..f72b25b5
--- /dev/null
+++ b/src/components/button.jsx
@@ -0,0 +1,14 @@
+import { Link } from 'react-router-dom';
+import './button.css';
+
+function Button({ children, disabled, link, radius, size }) {
+ return (
+
+
+
+ )
+}
+
+export default Button;
diff --git a/src/components/card.css b/src/components/card.css
new file mode 100644
index 00000000..ceace4a0
--- /dev/null
+++ b/src/components/card.css
@@ -0,0 +1,51 @@
+@import '../theme.css';
+
+.wrapper {
+ width: 100%;
+ height: fit-content;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 16px;
+}
+
+.card-image {
+ width: 100%;
+ height: 100%;
+ aspect-ratio: 1;
+ object-fit: cover;
+ border-radius: 16px;
+}
+
+.card-text-container {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.card-text-container>h2 {
+ font-weight: 500;
+ font-size: 14px;
+ margin: 0;
+}
+
+.card-text-container>h3 {
+ font-weight: 700;
+ font-size: 16px;
+ margin: 0;
+}
+
+.card-favorite-container {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ color: var(--secondary-600);
+ font-size: 12px;
+ font-weight: 500;
+}
+
+.card-favorite-container>img {
+ width: 16px;
+ height: 16px;
+}
diff --git a/src/components/card.jsx b/src/components/card.jsx
new file mode 100644
index 00000000..53b0af83
--- /dev/null
+++ b/src/components/card.jsx
@@ -0,0 +1,24 @@
+import './card.css';
+import Heart from '../public/items/ic_heart.png';
+
+function Card({ element }) {
+ return (
+
+

+
+
{element.name}
+
{element.price.toLocaleString()}원
+
+

+
{element.favoriteCount}
+
+
+
+ );
+}
+
+export default Card;
diff --git a/src/components/input.css b/src/components/input.css
new file mode 100644
index 00000000..2554ad8a
--- /dev/null
+++ b/src/components/input.css
@@ -0,0 +1,46 @@
+@import '../theme.css';
+
+.input-container {
+ position: relative;
+ height: 100%;
+}
+
+.input {
+ width: 100%;
+ height: 100%;
+ max-width: 640px;
+ box-sizing: border-box;
+ background-color: var(--gray-100);
+ border: 0;
+ border-radius: 12px;
+ font-size: 16px;
+ color: var(--secondary-800);
+ padding: 0px 10px;
+}
+
+.input-slanted {
+ width: 100%;
+ height: 100%;
+ max-width: 640px;
+ box-sizing: border-box;
+ background-color: var(--gray-100);
+ border: 0;
+ border-radius: 12px;
+ font-size: 16px;
+ color: var(--secondary-800);
+ padding: 0px 10px 0px 40px;
+}
+
+.input-button {
+ position: absolute;
+ right: 20px;
+ top: 16px;
+ background-color: transparent;
+ border: 0;
+}
+
+.slot-left-image {
+ position: absolute;
+ left: 10px;
+ top: calc((100% / 2) - 10px);
+}
diff --git a/src/components/input.jsx b/src/components/input.jsx
new file mode 100644
index 00000000..eb93d325
--- /dev/null
+++ b/src/components/input.jsx
@@ -0,0 +1,19 @@
+import './input.css';
+
+function Input({ slot, slotDirection, slotAlt, onChange, onBlur, type, name, placeholder }) {
+ return (
+
+ {slot && slotDirection === 'left' && (
+

+ )}
+
+ {slot && slotDirection === 'right' && (
+
+ )}
+
+ )
+}
+
+export default Input;
diff --git a/src/components/pagination.jsx b/src/components/pagination.jsx
new file mode 100644
index 00000000..d3e4f161
--- /dev/null
+++ b/src/components/pagination.jsx
@@ -0,0 +1,36 @@
+import './paginaton.css';
+import { ReactComponent as ChevronLeft } from '../public/components/chevron-left.svg';
+import { ReactComponent as ChevronRight } from '../public/components/chevron-right.svg';
+
+function Circle({ className, children, onClick }) {
+ return (
+
+ )
+}
+
+function Pagination({ currentPage, totalPage, callback }) {
+ const pageNumberArr = Array.from({ length: totalPage }, (v, i) => i + 1);
+ let slicedArr = [];
+
+ if (currentPage % 5 === 0) {
+ slicedArr = pageNumberArr.slice(currentPage - 5, currentPage);
+ } else {
+ slicedArr = pageNumberArr.slice(currentPage - currentPage % 5, Math.ceil(currentPage / 5) * 5)
+ }
+
+ console.log(currentPage)
+
+ return (
+
+ )
+}
+
+export default Pagination;
diff --git a/src/components/paginaton.css b/src/components/paginaton.css
new file mode 100644
index 00000000..1cc5894a
--- /dev/null
+++ b/src/components/paginaton.css
@@ -0,0 +1,46 @@
+@import '../theme.css';
+
+.footer-wrapper {
+ width: 100%;
+ margin: 16px 0px;
+ display: flex;
+ justify-content: center;
+ gap: 8px;
+}
+
+.circle {
+ width: 40px;
+ height: 40px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background-color: #fff;
+ border: 1px solid var(--secondary-200);
+ border-radius: 9999px;
+
+ &:hover {
+ background-color: var(--secondary-200);
+ }
+
+ &:active {
+ background-color: var(--secondary-400);
+ }
+
+ &:disabled {
+ background-color: var(--gray-100);
+ }
+}
+
+.circle-active {
+ border: 0;
+ background-color: var(--primary-200);
+ color: var(--gray-100);
+
+ &:hover {
+ background-color: var(--primary-100);
+ }
+
+ &:active {
+ background-color: var(--primary-300);
+ }
+}
diff --git a/src/components/select.css b/src/components/select.css
new file mode 100644
index 00000000..2b3eb3aa
--- /dev/null
+++ b/src/components/select.css
@@ -0,0 +1,39 @@
+@import './select.css';
+
+.select {
+ position: relative;
+ width: 130px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ box-sizing: border-box;
+ border: 1px solid var(--secondary-200);
+ border-radius: 12px;
+ background-color: transparent;
+ padding: 12px 20px;
+}
+
+.option {
+ position: absolute;
+ top: 40px;
+ left: 0;
+ width: inherit;
+ box-sizing: border-box;
+ border: 1px solid var(--secondary-200);
+ border-radius: 12px;
+ padding: 6px;
+ background-color: #fff;
+ list-style: none;
+}
+
+.option>li {
+ padding: 8px 0px;
+
+ &:hover {
+ background-color: var(--secondary-200)
+ }
+
+ &:not(:last-child) {
+ border-bottom: 1px solid var(--secondary-200);
+ }
+}
diff --git a/src/components/select.jsx b/src/components/select.jsx
new file mode 100644
index 00000000..ce45592e
--- /dev/null
+++ b/src/components/select.jsx
@@ -0,0 +1,29 @@
+import { useState } from 'react';
+import './select.css';
+import { ReactComponent as DownArrow } from '../public/components/down-arrow.svg';
+
+function Select({ select, callback }) {
+ const [open, setOpen] = useState(false);
+ const [selectedName, setSelectedName] = useState(select[0].name);
+
+ const handleChange = (el) => {
+ setSelectedName(el.name);
+ callback(el.value);
+ }
+
+ return (
+
+ )
+}
+
+export default Select;
diff --git a/src/functions/validator.js b/src/functions/validator.js
new file mode 100644
index 00000000..1933639b
--- /dev/null
+++ b/src/functions/validator.js
@@ -0,0 +1,31 @@
+export default function inputValidator(value, validateCase) {
+ switch (validateCase) {
+ case 'pw':
+ if (value === '') {
+ return { state: false, message: '비밀번호를 입력해주세요.' };
+ } else {
+ if (value.length < 8) {
+ return { state: false, message: '비밀번호를 8자 이상 입력해주세요.' }
+ } else {
+ return { state: true, message: '' };
+ }
+ }
+ case 'email':
+ const regex = new RegExp(/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/);
+ if (value === "") {
+ return { state: false, message: '이메일을 입력해주세요.' };
+ } else {
+ if (!regex.test(value)) {
+ return { state: false, message: '잘못된 이메일 형식입니다.' };
+ } else {
+ return { state: true, message: '' };
+ }
+ }
+ case 'nickname':
+ if (value === '') {
+ return { state: false, message: '닉네임을 입력해주세요.' };
+ } else {
+ return { state: true, message: '' };
+ }
+ }
+}
diff --git a/src/index.css b/src/index.css
index ec2585e8..c82d880e 100644
--- a/src/index.css
+++ b/src/index.css
@@ -1,5 +1,6 @@
body {
margin: 0;
+ width: 100vw;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
diff --git a/src/index.js b/src/index.js
index d563c0fb..7a6abde6 100644
--- a/src/index.js
+++ b/src/index.js
@@ -3,11 +3,14 @@ import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
+import { BrowserRouter } from 'react-router-dom';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
-
+
+
+
);
diff --git a/src/logo.svg b/src/logo.svg
deleted file mode 100644
index 9dfc1c05..00000000
--- a/src/logo.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/pages/items.css b/src/pages/items.css
new file mode 100644
index 00000000..40ad2c0b
--- /dev/null
+++ b/src/pages/items.css
@@ -0,0 +1,133 @@
+@import '../theme.css';
+
+.header {
+ width: 100%;
+ height: 70px;
+ display: flex;
+ justify-content: center;
+ border-bottom: 1px solid #dfdfdf;
+}
+
+.navbar {
+ width: 80%;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.navbar-image {
+ margin-right: 32px;
+}
+
+.navbar-left-container {
+ display: flex;
+ align-items: center;
+}
+
+.navbar-button {
+ padding: 21px 15px;
+ font-weight: 700;
+ font-size: 18px;
+ color: var(--secondary-600);
+}
+
+.navbar-avatar {
+ height: 40px;
+}
+
+.main {
+ width: 100%;
+ margin-top: 16px;
+}
+
+.best-section {
+ width: 1200px;
+ margin-top: 16px;
+}
+
+.best-section>h1 {
+ font-weight: 700;
+ font-size: 20px;
+ margin-top: 0;
+ margin-bottom: 16px;
+}
+
+.best-card-section {
+ display: grid;
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ gap: 24px;
+}
+
+.all-section {
+ width: 1200px;
+}
+
+.all-section-titlebar {
+ width: 100%;
+ height: 42px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 16px;
+}
+
+.all-section-toolbar {
+ height: inherit;
+ display: flex;
+ align-items: center;
+ gap: 16px;
+}
+
+.all-card-section {
+ display: grid;
+ grid-template-columns: repeat(5, minmax(0, 1fr));
+ gap: 24px;
+}
+
+@media (768px <=width <=1199px) {
+
+ .navbar,
+ .main {
+ width: 100%;
+ padding: 0px 16px;
+ }
+
+ .best-section,
+ .all-section {
+ width: 100%;
+ }
+
+ .best-card-section {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+
+ .all-card-section {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ }
+}
+
+@media (375px <=width <=767px) {
+
+ .navbar,
+ .main {
+ width: 100%;
+ padding: 0px 16px;
+ }
+
+ .navbar-image {
+ margin-right: 8px;
+ }
+
+ .best-section,
+ .all-section {
+ width: 100%;
+ }
+
+ .best-card-section {
+ grid-template-columns: repeat(1, minmax(0, 1fr));
+ }
+
+ .all-card-section {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+}
diff --git a/src/pages/items.jsx b/src/pages/items.jsx
new file mode 100644
index 00000000..8ec37217
--- /dev/null
+++ b/src/pages/items.jsx
@@ -0,0 +1,127 @@
+import { useEffect, useRef, useState } from 'react';
+import { Link } from "react-router-dom";
+import fetchLists from '../api/fetchCardList';
+import Card from '../components/card';
+import Button from '../components/button';
+import Input from '../components/input';
+import Avatar from '../public/items/avatar.png';
+import NavBarLogo from '../public/navbar-button.png';
+import SearchIcon from '../public/items/ic_search.png';
+import './items.css';
+import Select from '../components/select';
+import Pagination from '../components/pagination';
+
+const orderSelect = [
+ {
+ name: "최신순",
+ value: 'recent'
+ },
+ {
+ name: "좋아요순",
+ value: 'favorite'
+ }
+]
+
+function Items() {
+ const [bestCardData, setBestCardData] = useState([]);
+ const [cardData, setCardData] = useState([]);
+ const [sort, setSort] = useState('recent');
+ const [currentPage, setCurrentPage] = useState(1);
+ const totalCount = useRef(0);
+ const totalPage = useRef(0);
+
+ const ITEMS_PER_PAGE = 10;
+
+ const handleChange = (el) => {
+ if (el === 'favorite') {
+ setSort('favorite');
+ fetchLists(ITEMS_PER_PAGE, 1, el).then((res) => setCardData(res.list));
+ } else if (el === 'recent') {
+ setSort('recent');
+ fetchLists(ITEMS_PER_PAGE, 1, el).then((res) => setCardData(res.list));
+ }
+ }
+
+ const handlePaginationClick = (e) => {
+ switch (e) {
+ case 'next':
+ setCurrentPage((prevPage) => prevPage >= prevPage % 5 * 5 ? prevPage + 1 : prevPage);
+ fetchLists(ITEMS_PER_PAGE, currentPage + 1, sort).then((res) => setCardData(res.list));
+ break;
+ case 'prev':
+ setCurrentPage((prevPage) => prevPage >= prevPage % 5 * 5 ? prevPage - 1 : prevPage);
+ fetchLists(ITEMS_PER_PAGE, currentPage - 1, sort).then((res) => setCardData(res.list));
+ break;
+ default:
+ setCurrentPage(e);
+ fetchLists(ITEMS_PER_PAGE, e, sort).then((res) => setCardData(res.list));
+ }
+
+ }
+
+ useEffect(() => {
+ fetchLists(4, 1, 'favorite').then((res) => setBestCardData(res.list));
+ fetchLists(ITEMS_PER_PAGE, 1, 'recent').then((res) => {
+ setCardData(res.list);
+ totalCount.current = res.totalCount;
+ totalPage.current = Math.round(res.totalCount / ITEMS_PER_PAGE);
+ });
+ }, [])
+
+ return (
+ <>
+
+
+
+
+
+ 베스트 상품
+
+ {bestCardData ? (
+ <>
+ {bestCardData.map((el) => (
+
+ ))}
+ >
+ ) : (
+ <>>
+ )}
+
+
+
+
+
전체 상품
+
+
+
+
+
+
+ {cardData ? (
+ <>
+ {cardData.map((el) => (
+
+ ))}
+ >
+ ) : (
+ <>>
+ )}
+
+
+
+
+ >
+ );
+}
+
+export default Items;
diff --git a/src/pages/login.css b/src/pages/login.css
new file mode 100644
index 00000000..f78ed67c
--- /dev/null
+++ b/src/pages/login.css
@@ -0,0 +1,74 @@
+@import '../theme.css';
+
+.wrapper {
+ width: 100vw;
+ height: 100vh;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.container {
+ width: 640px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 40px;
+}
+
+header {
+ width: 100%;
+ height: fit-content;
+}
+
+.main {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 24px;
+ box-sizing: border-box;
+}
+
+.label {
+ width: 100%;
+ height: 98px;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ line-height: 26px;
+ font-weight: 700;
+ font-size: 18px;
+}
+
+.error-message-container {
+ width: 100%;
+ color: red;
+}
+
+.easy-login {
+ width: 100%;
+ height: 74px;
+ border-radius: 8px;
+ box-sizing: border-box;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 16px 23px;
+ background-color: #e6f2ff;
+}
+
+.easy-login-logo {
+ display: flex;
+ gap: 16px;
+}
+
+.register-link {
+ margin-left: 4px;
+}
+
+@media (max-width: 768px) {
+ .main {
+ padding: 0px 32px;
+ }
+}
diff --git a/src/pages/login.jsx b/src/pages/login.jsx
new file mode 100644
index 00000000..6272b960
--- /dev/null
+++ b/src/pages/login.jsx
@@ -0,0 +1,92 @@
+import { useState } from 'react';
+import { Link } from 'react-router-dom';
+import Button from '../components/button.jsx';
+import Input from "../components/input.jsx";
+import inputValidator from '../functions/validator.js';
+import Visibility from '../public/login/btn_visibility_on_24px.png';
+import GoogleLogo from '../public/login/Component 2.png';
+import KakaoLogo from '../public/login/Component 3.png';
+import HeaderLogo from '../public/login/logo.png';
+import './login.css';
+
+function Login() {
+ const [login, setLogin] = useState({ email: "", pw: "" });
+ const [validate, setValidate] = useState({
+ email: {
+ state: false,
+ message: '',
+ },
+ pw: {
+ state: false,
+ message: ''
+ }
+ });
+
+ const handleChange = (e) => {
+ const { name, value } = e.target;
+ setLogin((prevLogin) => ({ ...prevLogin, [name]: value }))
+ }
+
+ const handleBlur = (e) => {
+ const { name, value } = e.target;
+ const validateResult = inputValidator(value, name);
+ setValidate((prevValidate) => ({ ...prevValidate, [name]: validateResult }))
+ }
+
+ return (
+
+
+
+
+
+
+
+ {!validate.email.state && (
+
+ {validate.email.message}
+
+ )}
+
+ {!validate.pw.state && (
+
+ {validate.pw.message}
+
+ )}
+
+
+
+
+
+
+
+
+ )
+}
+
+export default Login;
diff --git a/src/pages/register.css b/src/pages/register.css
new file mode 100644
index 00000000..0c8a6a58
--- /dev/null
+++ b/src/pages/register.css
@@ -0,0 +1,73 @@
+@import '../theme.css';
+
+.wrapper {
+ width: 100vw;
+ height: 100vh;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.container {
+ width: 640px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 40px;
+}
+
+header {
+ width: 100%;
+ height: fit-content;
+}
+
+.main {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 24px;
+ box-sizing: border-box;
+}
+
+.label {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ line-height: 26px;
+ font-weight: 700;
+ font-size: 18px;
+}
+
+.error-message-container {
+ width: 100%;
+ color: red;
+}
+
+.easy-login {
+ width: 100%;
+ height: 74px;
+ border-radius: 8px;
+ box-sizing: border-box;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 16px 23px;
+ background-color: #e6f2ff;
+}
+
+.easy-login-logo {
+ display: flex;
+ gap: 16px;
+}
+
+.register-link {
+ margin-left: 4px;
+}
+
+@media (max-width: 768px) {
+ .main {
+ padding: 0px 16px;
+ }
+}
diff --git a/src/pages/register.jsx b/src/pages/register.jsx
new file mode 100644
index 00000000..77c1d197
--- /dev/null
+++ b/src/pages/register.jsx
@@ -0,0 +1,128 @@
+import { useState, useRef } from 'react';
+import { Link } from 'react-router-dom';
+import Button from '../components/button.jsx';
+import Input from "../components/input.jsx";
+import Visibility from '../public/login/btn_visibility_on_24px.png';
+import GoogleLogo from '../public/login/Component 2.png';
+import KakaoLogo from '../public/login/Component 3.png';
+import HeaderLogo from '../public/login/logo.png';
+import './register.css';
+import inputValidator from '../functions/validator.js';
+
+function Register() {
+ const [validate, setValidate] = useState({
+ email: {
+ state: false,
+ message: '',
+ },
+ pw: {
+ state: false,
+ message: '',
+ },
+ nickname: {
+ state: false,
+ message: '',
+ }
+ });
+ const [pwValid, setPwValid] = useState(false);
+ const pwValidate = useRef({ pw: '', pwConfirm: '' });
+
+ const handleChange = (e) => {
+ const { name, value } = e.target;
+ pwValidate.current = { ...pwValidate.current, [name]: value };
+ if (pwValidate.current.pw !== pwValidate.current.pwConfirm) setPwValid(false);
+ else setPwValid(true);
+ }
+
+ const handleBlur = (e) => {
+ const { name, value } = e.target;
+ const validateResult = inputValidator(value, name);
+ setValidate((prevValidate) => ({ ...prevValidate, [name]: validateResult }))
+ }
+
+ return (
+
+ )
+}
+
+export default Register;
diff --git a/src/pages/root.css b/src/pages/root.css
new file mode 100644
index 00000000..0a6f6cb3
--- /dev/null
+++ b/src/pages/root.css
@@ -0,0 +1,185 @@
+@import '../theme.css';
+
+header {
+ width: inherit;
+ height: 70px;
+ display: flex;
+ justify-content: center;
+}
+
+.nav {
+ width: 1120px;
+ height: 70px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.login-button {
+ border: 0;
+ width: 128px;
+ height: 48px;
+ border-radius: 8px;
+ background-color: var(--primary-100);
+ color: white;
+ cursor: pointer;
+}
+
+.section-with-background {
+ width: inherit;
+ height: 540px;
+ background-color: #cfe5ff;
+ display: flex;
+ justify-content: center;
+ align-items: end;
+}
+
+.section-with-innerdiv {
+ display: flex;
+ align-items: center;
+}
+
+.hero-content {
+ width: 357px;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+}
+
+.hero-content-text {
+ font-size: 40px;
+ line-height: 140%;
+ color: var(--secondary-700);
+}
+
+.section {
+ width: inherit;
+ height: 720px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.card {
+ width: 988px;
+ height: 444px;
+ display: flex;
+ justify-content: space-around;
+ align-items: center;
+ gap: 12px;
+ border-radius: 12px;
+ background-color: #fcfcfc;
+}
+
+.card-text {
+ width: 298px;
+ display: flex;
+ flex-direction: column;
+}
+
+.card-text>span {
+ font-weight: 800;
+ font-size: 18px;
+ color: var(--primary-100);
+}
+
+.card-text>h2 {
+ font-weight: 700;
+ font-size: 40px;
+ color: var(--secondary-700);
+ line-height: 140%;
+}
+
+.card-text>p {
+ font-size: 500;
+ font-size: 24px;
+ line-height: 32px;
+}
+
+.card-reverse {
+ flex-direction: row-reverse;
+}
+
+.card-text-reverse {
+ text-align: end;
+}
+
+.footer-text {
+ font-weight: 700;
+ font-size: 40px;
+ width: 295px;
+ padding-right: 69px;
+}
+
+.footer-nav {
+ display: flex;
+ justify-content: center;
+ height: 160px;
+ background-color: var(--secondary-900);
+ padding-top: 32px;
+}
+
+.footer-nav-text {
+ color: var(--secondary-400);
+}
+
+.footer-nav-link-container {
+ display: flex;
+ gap: 30px;
+}
+
+.footer-nav-link {
+ text-decoration: none;
+ color: var(--secondary-200);
+}
+
+.footer-nav-icon-container {
+ display: flex;
+ gap: 12px;
+}
+
+@media (max-width: 1120px) {
+ .nav {
+ width: 100%;
+ padding: 0px 16px;
+ }
+
+ .section-with-background {
+ height: 771px;
+ }
+
+ .section-with-innerdiv {
+ height: inherit;
+ flex-direction: column;
+ justify-content: space-between;
+ }
+
+ .hero-content {
+ width: 512px;
+ }
+
+ .section {
+ width: 100%;
+ height: 708px;
+ }
+
+ .card {
+ width: 696px;
+ height: 708px;
+ flex-direction: column;
+ justify-content: space-between;
+ }
+
+ .card-text {
+ width: 100%;
+ }
+
+ .footer-text {
+ width: fit-content;
+ padding-right: 0px;
+ }
+
+ .footer-nav {
+ padding: 32px 16px 0px 16px;
+ }
+}
diff --git a/src/pages/root.jsx b/src/pages/root.jsx
new file mode 100644
index 00000000..c0a5da39
--- /dev/null
+++ b/src/pages/root.jsx
@@ -0,0 +1,97 @@
+import './root.css';
+
+import navbarLogo from '../public/navbar-button.png';
+import heroImg from '../public/Img_home_top.png';
+import cardOneImg from '../public/Img_home_01.png';
+import cardTwoImg from '../public/Img_home_02.png';
+import cardThreeImg from '../public/Img_home_03.png';
+import footerImg from '../public/Img_home_bottom.png';
+import youtubeIcon from '../public/ic_youtube.png';
+import twitterIcon from '../public/ic_twitter.png';
+import facebookIcon from '../public/ic_facebook.png';
+import instagramIcon from '../public/ic_instagram.png';
+import { Link } from 'react-router-dom';
+import Button from '../components/button';
+
+function Root() {
+ return (
+ <>
+
+
+
+
+
+
+
+ 일상의 모든 물건을 거래해보세요
+
+
+
+

+
+
+
+
+

+
+
Hot Item
+
인기상품을 확인해보세요
+
가장 HOT한 중고거래 물품을 판다 마켓에서 확인해보세요
+
+
+
+
+
+

+
+
Search
+
구매를 원하는 상품은 검색하세요
+
구매하고 싶은 물품은 검색해서 쉽게 찾아보세요
+
+
+
+
+
+

+
+
Register
+
판매를 원하는 상품을 등록하세요
+
어떤 물건이든 판매하고 싶은 상품을 쉽게 등록하세요
+
+
+
+
+
+
믿을 수 있는 판다마켓 중고거래
+

+
+
+
+ >
+ )
+}
+
+export default Root;
diff --git a/src/public/Img_home_01.png b/src/public/Img_home_01.png
new file mode 100644
index 00000000..1d97bc53
Binary files /dev/null and b/src/public/Img_home_01.png differ
diff --git a/src/public/Img_home_02.png b/src/public/Img_home_02.png
new file mode 100644
index 00000000..e319c098
Binary files /dev/null and b/src/public/Img_home_02.png differ
diff --git a/src/public/Img_home_03.png b/src/public/Img_home_03.png
new file mode 100644
index 00000000..fd69fd94
Binary files /dev/null and b/src/public/Img_home_03.png differ
diff --git a/src/public/Img_home_bottom.png b/src/public/Img_home_bottom.png
new file mode 100644
index 00000000..654c32cf
Binary files /dev/null and b/src/public/Img_home_bottom.png differ
diff --git a/src/public/Img_home_top.png b/src/public/Img_home_top.png
new file mode 100644
index 00000000..c6fcaf67
Binary files /dev/null and b/src/public/Img_home_top.png differ
diff --git a/src/public/components/chevron-left.svg b/src/public/components/chevron-left.svg
new file mode 100644
index 00000000..4828b06a
--- /dev/null
+++ b/src/public/components/chevron-left.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/public/components/chevron-right.svg b/src/public/components/chevron-right.svg
new file mode 100644
index 00000000..5db5d6ee
--- /dev/null
+++ b/src/public/components/chevron-right.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/public/components/down-arrow.svg b/src/public/components/down-arrow.svg
new file mode 100644
index 00000000..75730f59
--- /dev/null
+++ b/src/public/components/down-arrow.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/src/public/ic_facebook.png b/src/public/ic_facebook.png
new file mode 100644
index 00000000..a3e343c7
Binary files /dev/null and b/src/public/ic_facebook.png differ
diff --git a/src/public/ic_instagram.png b/src/public/ic_instagram.png
new file mode 100644
index 00000000..c47e044d
Binary files /dev/null and b/src/public/ic_instagram.png differ
diff --git a/src/public/ic_twitter.png b/src/public/ic_twitter.png
new file mode 100644
index 00000000..3f74b730
Binary files /dev/null and b/src/public/ic_twitter.png differ
diff --git a/src/public/ic_youtube.png b/src/public/ic_youtube.png
new file mode 100644
index 00000000..874150b0
Binary files /dev/null and b/src/public/ic_youtube.png differ
diff --git a/src/public/items/avatar.png b/src/public/items/avatar.png
new file mode 100644
index 00000000..0844dd1d
Binary files /dev/null and b/src/public/items/avatar.png differ
diff --git a/src/public/items/ic_heart.png b/src/public/items/ic_heart.png
new file mode 100644
index 00000000..bd7e864d
Binary files /dev/null and b/src/public/items/ic_heart.png differ
diff --git a/src/public/items/ic_search.png b/src/public/items/ic_search.png
new file mode 100644
index 00000000..5f98a65a
Binary files /dev/null and b/src/public/items/ic_search.png differ
diff --git a/src/public/login/Component 2.png b/src/public/login/Component 2.png
new file mode 100644
index 00000000..f75dc761
Binary files /dev/null and b/src/public/login/Component 2.png differ
diff --git a/src/public/login/Component 3.png b/src/public/login/Component 3.png
new file mode 100644
index 00000000..a63a0e30
Binary files /dev/null and b/src/public/login/Component 3.png differ
diff --git a/src/public/login/btn_visibility_on_24px.png b/src/public/login/btn_visibility_on_24px.png
new file mode 100644
index 00000000..5dc09fbe
Binary files /dev/null and b/src/public/login/btn_visibility_on_24px.png differ
diff --git a/src/public/login/logo.png b/src/public/login/logo.png
new file mode 100644
index 00000000..8248f602
Binary files /dev/null and b/src/public/login/logo.png differ
diff --git a/src/public/navbar-button.png b/src/public/navbar-button.png
new file mode 100644
index 00000000..9b5b0af2
Binary files /dev/null and b/src/public/navbar-button.png differ
diff --git a/src/theme.css b/src/theme.css
new file mode 100644
index 00000000..bfac139c
--- /dev/null
+++ b/src/theme.css
@@ -0,0 +1,14 @@
+:root {
+ --primary-100: #3692ff;
+ --primary-200: #1967d6;
+ --primary-300: #1251aa;
+
+ --secondary-200: #e5e7eb;
+ --secondary-400: #9ca3af;
+ --secondary-600: #4b5563;
+ --secondary-700: #374151;
+ --secondary-800: #1f2937;
+ --secondary-900: #111827;
+
+ --gray-100: #f3f4f6;
+}