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 ( -
-
- logo -

- Edit src/App.js and save to reload. -

- - Learn React - -
-
+ <> + + } /> + } /> + } /> + } /> + + ); } 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.id}번째 +
+

{element.name}

+

{element.price.toLocaleString()}원

+
+ {`${element.id}번째 + {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' && ( + {slotAlt} + )} + + {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) => ( + + ))} + + ) : ( + <> + )} +
+
+
+
+

전체 상품

+
+ + + + + {!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 ( +
+
+
+ 로그인 페이지 판다마켓 로고 +
+
+ + {!validate.email.state && ( +
+ {validate.email.message} +
+ )} + + {!validate.nickname.state && ( +
+ {validate.nickname.message} +
+ )} + + {!validate.pw.state && ( +
+ {validate.pw.message} +
+ )} + + {!pwValid && ( +
+ 비밀번호가 일치하지 않습니다. +
+ )} +
+ +
+
+ 간편 로그인하기 +
+ + 구글 간편 로그인 + + + 구글 간편 로그인 + +
+
+
+ 이미 회원이신가요? + 로그인 +
+
+
+
+ ) +} + +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; +}