diff --git a/.gitignore b/.gitignore index 4d29575de..5fa505c01 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /node_modules /.pnp .pnp.js +.env # testing /coverage diff --git a/package-lock.json b/package-lock.json index 53e5be8fb..62543c436 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-native-dotenv": "^3.4.11", "react-router-dom": "^6.23.1", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" @@ -14672,6 +14673,28 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "node_modules/react-native-dotenv": { + "version": "3.4.11", + "resolved": "https://registry.npmjs.org/react-native-dotenv/-/react-native-dotenv-3.4.11.tgz", + "integrity": "sha512-6vnIE+WHABSeHCaYP6l3O1BOEhWxKH6nHAdV7n/wKn/sciZ64zPPp2NUdEUf1m7g4uuzlLbjgr+6uDt89q2DOg==", + "dependencies": { + "dotenv": "^16.4.5" + }, + "peerDependencies": { + "@babel/runtime": "^7.20.6" + } + }, + "node_modules/react-native-dotenv/node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", diff --git a/package.json b/package.json index 3c25955f5..417f780a5 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-native-dotenv": "^3.4.11", "react-router-dom": "^6.23.1", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" diff --git a/public/images/i-close.png b/public/images/i-close.png new file mode 100644 index 000000000..d051a94a2 Binary files /dev/null and b/public/images/i-close.png differ diff --git a/public/images/i-plus.png b/public/images/i-plus.png new file mode 100644 index 000000000..0c58d8f19 Binary files /dev/null and b/public/images/i-plus.png differ diff --git a/public/images/i-profile.png b/public/images/i-profile.png new file mode 100644 index 000000000..f44d9b71c Binary files /dev/null and b/public/images/i-profile.png differ diff --git a/public/index.html b/public/index.html index f7ad5023c..24d6b0710 100644 --- a/public/index.html +++ b/public/index.html @@ -1,5 +1,5 @@ - + diff --git a/src/api.js b/src/api.js index ee5f7e3e2..cb50f5505 100644 --- a/src/api.js +++ b/src/api.js @@ -1,8 +1,7 @@ -export async function getProducts({ pageSize, order }) { - const query = `pageSize=${pageSize}&orderBy=${order}`; - const response = await fetch( - `https://panda-market-api.vercel.app/products?page=1&${query}` - ); +export async function getProducts({ page, pageSize, order }) { + const apiUrl = process.env.REACT_APP_API_URL; + const query = `page=${page}&pageSize=${pageSize}&orderBy=${order}`; + const response = await fetch(`${apiUrl}.vercel.app/products?${query}`); const body = await response.json(); return body; } diff --git a/src/components/AddItem.js b/src/components/AddItem.js new file mode 100644 index 000000000..d54c9720c --- /dev/null +++ b/src/components/AddItem.js @@ -0,0 +1,157 @@ +import { useEffect, useRef, useState } from "react"; + +function AddItem() { + const [formData, setFormDate] = useState({ + name: "", + info: "", + price: "", + tag: "", + }); + const [addItemBtn, setAddItemBtn] = useState(""); + const [btnDisabled, setBtnDisabled] = useState(true); + const [inputFileUrl, setInputFileUrl] = useState(null); + const inputFileImg = useRef(null); + const imgAccept = ".jpg, .jpeg, .png"; + + const inputChange = (e) => { + // input value 상태 변경 + const { name, value } = e.target; + setFormDate({ + ...formData, + [name]: value, + }); + }; + + const inputFileChange = () => { + const inputFile = inputFileImg.current.files[0]; + console.log(inputFile); + + if (inputFile) { + const fileUrl = URL.createObjectURL(inputFile); + setInputFileUrl(fileUrl); + } + }; + + const addImgDelete = () => { + setInputFileUrl(null); + }; + + const validAddItem = () => { + // 유효성 검사 + if ( + formData.name !== "" && + formData.info !== "" && + formData.price !== "" && + formData.tag !== "" + ) { + setAddItemBtn("on"); + setBtnDisabled(false); + } else { + setAddItemBtn(""); + setBtnDisabled(true); + } + }; + + useEffect(() => { + // input 데이터 변경 될때마다 유효성 검사 실행 + validAddItem(); + }, [formData]); + + return ( +
+
+
+

상품 등록하기

+ +
+
+

상품 이미지

+
+ + + {inputFileUrl ? ( +
+ 상품 등록 이미지 + +
+ ) : ( + "" + )} +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
    +
  • + 티셔츠 +
  • +
  • + 상의 +
  • +
+
+
+
+ ); +} + +export default AddItem; diff --git a/src/components/AllProduct.js b/src/components/AllProduct.js index c017377db..1a5c4a9e7 100644 --- a/src/components/AllProduct.js +++ b/src/components/AllProduct.js @@ -1,3 +1,4 @@ +import { Link } from "react-router-dom"; import { getProducts } from "../api.js"; import { useEffect, useState } from "react"; @@ -15,23 +16,27 @@ const responsivePageSize = () => { } }; +const recent = "recent"; +const favorite = "favorite"; + function AllProduct() { const [products, setProducts] = useState([]); + const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(10); - const [order, setOrder] = useState("recent"); + const [order, setOrder] = useState(recent); const [dropArrow, setDropArrow] = useState(""); const [dropDisplay, setDropDisplay] = useState("none"); const [orderTxt, setOrderTxt] = useState("최신순"); + const [pagesNum, setPagesNum] = useState([1, 2]); const handleLoad = async (options) => { let { list } = await getProducts(options); setProducts(list); - // console.log(list); }; const handleDropClick = () => { - dropArrow === "" ? setDropArrow("on") : setDropArrow(""); - dropDisplay === "none" ? setDropDisplay("block") : setDropDisplay("none"); + setDropArrow(dropArrow === "" ? "on" : ""); + setDropDisplay(dropDisplay === "none" ? "block" : "none"); }; const handleNewsOrder = (e) => { @@ -39,7 +44,7 @@ function AllProduct() { setOrderTxt(menuTxt); setDropArrow(""); setDropDisplay("none"); - setOrder("recent"); + setOrder(recent); }; const handleBestOrder = (e) => { @@ -47,18 +52,34 @@ function AllProduct() { setOrderTxt(menuTxt); setDropArrow(""); setDropDisplay("none"); - setOrder("favorite"); + setOrder(favorite); + }; + + const pageNumClick = (page) => { + setPage(page); + }; + + const prevPageBtn = () => { + if (page !== 1) { + setPage(page - 1); + } + }; + + const nextPageBtn = () => { + if (page !== pagesNum.length) { + setPage(page + 1); + } }; useEffect(() => { - handleLoad({ pageSize, order }); + handleLoad({ page, pageSize, order }); const handleResize = () => { setPageSize(responsivePageSize()); }; window.addEventListener("resize", handleResize); - }, [order, pageSize]); + }, [page, order, pageSize]); return ( <> @@ -67,7 +88,9 @@ function AllProduct() {

판매중인 상품

- + + 상품 등록하기 +

{orderTxt} @@ -118,27 +141,26 @@ function AllProduct() {

  • -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • + {pagesNum.map((pageNum) => { + return ( +
  • + +
  • + ); + })}
  • -
  • diff --git a/src/components/App.js b/src/components/App.js index ff32f4afa..6b55f1e83 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -4,37 +4,29 @@ import Home from "./Home"; import Login from "./Login"; import Signup from "./Signup"; import Items from "./Items"; -import { getProducts } from "../api.js"; +import AddItem from "./AddItem"; import "../css/reset.css"; import "../css/style.css"; -import { useEffect, useState } from "react"; +import { BrowserRouter, Route, Routes } from "react-router-dom"; +import NotFoundPage from "./NotFoundPage"; function App() { - const [products, setProducts] = useState([]); - const [pageSize, setPageSize] = useState(4); - const [order, setOrder] = useState("favorite"); - // const [order, setOrder] = useState("favoriteCount"); - - const handleLoad = async (options) => { - let { list } = await getProducts(options); - setProducts(list); - console.log(list); - }; - - useEffect(() => { - handleLoad({ pageSize, order }); - }, []); - return (
    -
    -
    - {/* */} - {/* */} - {/* */} - -
    -
    + +
    +
    + + } /> + } /> + } /> + } /> + } /> + } /> + +
    +
    +
    ); } diff --git a/src/components/BestProduct.js b/src/components/BestProduct.js index 02c5c2e3c..1302d01ba 100644 --- a/src/components/BestProduct.js +++ b/src/components/BestProduct.js @@ -17,24 +17,24 @@ const responsivePageSize = () => { function BestProduct() { const [products, setProducts] = useState([]); + const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(4); const [order, setOrder] = useState("favorite"); const handleLoad = async (options) => { - let { list } = await getProducts(options); + const { list } = await getProducts(options); setProducts(list); - // console.log(list); }; useEffect(() => { - handleLoad({ pageSize, order }); + handleLoad({ page, pageSize, order }); const handleResize = () => { setPageSize(responsivePageSize()); }; window.addEventListener("resize", handleResize); - }, [order, pageSize]); + }, [page, order, pageSize]); return ( <> diff --git a/src/components/Header.js b/src/components/Header.js index c1209ab08..10f94e732 100644 --- a/src/components/Header.js +++ b/src/components/Header.js @@ -1,34 +1,51 @@ +import { Link, useLocation } from "react-router-dom"; + function Header() { + const location = useLocation(); return ( <>
    - - 로그인 - + {location.pathname === "/AddItem" ? ( + + ) : ( + + 로그인 + + )}
    diff --git a/src/components/Home.js b/src/components/Home.js index 1a45bed1b..d87a7f459 100644 --- a/src/components/Home.js +++ b/src/components/Home.js @@ -1,3 +1,5 @@ +import { Link } from "react-router-dom"; + function Home() { return ( <> @@ -10,9 +12,9 @@ function Home() {
    거래해 보세요 - + 구경하러 가기 - +
diff --git a/src/components/Items.js b/src/components/Items.js index a6e525046..ee5e625c7 100644 --- a/src/components/Items.js +++ b/src/components/Items.js @@ -1,12 +1,27 @@ +import { useEffect, useState } from "react"; import AllProduct from "./AllProduct"; import BestProduct from "./BestProduct"; +import { getProducts } from "../api.js"; function Items() { + const [products, setProducts] = useState([]); + const [pageSize, setPageSize] = useState(4); + const [order, setOrder] = useState("favorite"); + + const handleLoad = async (options) => { + let { list } = await getProducts(options); + setProducts(list); + }; + + useEffect(() => { + handleLoad({ pageSize, order }); + }, []); + return ( <>
- - + +
); diff --git a/src/components/ItemsPage.js b/src/components/ItemsPage.js deleted file mode 100644 index 7bf5f787a..000000000 --- a/src/components/ItemsPage.js +++ /dev/null @@ -1,3 +0,0 @@ -function ItemsPage() { - return <>; -} diff --git a/src/components/Login.js b/src/components/Login.js index 82d2fbadd..64407229d 100644 --- a/src/components/Login.js +++ b/src/components/Login.js @@ -1,3 +1,5 @@ +import { Link } from "react-router-dom"; + function Login() { return ( <> @@ -72,7 +74,7 @@ function Login() {

- 판다마켓이 처음이신가요?회원가입 + 판다마켓이 처음이신가요?회원가입

diff --git a/src/components/NotFoundPage.js b/src/components/NotFoundPage.js new file mode 100644 index 000000000..bea0b0a57 --- /dev/null +++ b/src/components/NotFoundPage.js @@ -0,0 +1,13 @@ +import { Link } from "react-router-dom"; + +function NotFoundPage() { + return ( +
+

존재하지 않는 페이지 입니다.

+

주소를 다시 한번 확인해주세요.

+ 메인 페이지로 가기 +
+ ); +} + +export default NotFoundPage; diff --git a/src/components/Signup.js b/src/components/Signup.js index 4345dab64..c2c951a21 100644 --- a/src/components/Signup.js +++ b/src/components/Signup.js @@ -1,3 +1,5 @@ +import { Link } from "react-router-dom"; + function Signup() { return ( <> @@ -110,7 +112,7 @@ function Signup() {

- 이미 회원이신가요?로그인 + 이미 회원이신가요?로그인

diff --git a/src/css/reset.css b/src/css/reset.css index f12305bf2..40b6ec17b 100644 --- a/src/css/reset.css +++ b/src/css/reset.css @@ -30,3 +30,17 @@ ul, ol { list-style: none; } + +input:focus { + outline: none; +} + +textarea:focus { + outline: none; +} + +input::-webkit-outer-spin-button, +input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} diff --git a/src/css/style.css b/src/css/style.css index 1ab588cd3..a8a997294 100644 --- a/src/css/style.css +++ b/src/css/style.css @@ -119,6 +119,12 @@ header .header-wrap h1 a.mo-logo { display: none; } +header .header-wrap .header-button .profile-btn { + border: none; + border-radius: 100%; + cursor: pointer; +} + main { margin-top: 70px; } @@ -220,7 +226,7 @@ main .main-wrap .banner .banner-wrap h2 { /* 로그인/회원가입 영역 */ .login-join-wrap { max-width: 640px; - margin: 70px auto 164px; + margin: 140px auto 164px; } .login-join-wrap > .login-join-logo { @@ -422,7 +428,10 @@ main .main-wrap .banner .banner-wrap h2 { text-indent: 30px; } -.product-wrap .product-title .product-info-menu button { +.product-wrap .product-title .product-info-menu .item-add-btn { + display: flex; + align-items: center; + justify-content: center; width: 130px; height: 40px; font-size: 16px; @@ -528,10 +537,6 @@ main .main-wrap .banner .banner-wrap h2 { cursor: pointer; } -.product-list-wrap .product-list.best-list li { - /* max-width: 280px; */ -} - .product-list-wrap .product-list li .product-img { width: 100%; margin-bottom: 15px; @@ -621,6 +626,170 @@ main .main-wrap .banner .banner-wrap h2 { } /* //중고마켓 */ +/* 상품 등록하기 */ +.add-item-wrap { + padding-top: 40px; + margin: 0 auto 170px; +} + +.add-item-wrap .add-item-title { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 15px; +} + +.add-item-wrap .add-item-title h2 { + font-size: 28px; + color: var(--gray800-color); +} + +.add-item-wrap .add-item-title button { + width: 90px; + height: 40px; + font-size: 16px; + font-weight: 600; + border: none; + border-radius: 10px; + color: #ffffff; + background-color: var(--gray400-color); + cursor: pointer; +} + +.add-item-wrap .add-item-title button.on { + background-color: var(--blue-color); +} + +.add-item-wrap form .add-input { + position: relative; + margin-bottom: 25px; +} + +.add-item-wrap form .add-input input[type="file"] { + position: absolute; + top: 0; + left: 0; + width: 0; + height: 0; + opacity: 0; +} + +.add-item-wrap form .add-input .add-img-area { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 280px; + height: 280px; + font-size: 16px; + font-weight: 400; + border-radius: 10px; + color: var(--gray400-color); + background-color: var(--gray100-color); + cursor: pointer; +} + +.add-item-wrap form .add-input .add-img-area img { + max-width: 50px; + margin-bottom: 10px; +} + +.add-item-wrap form .add-input .add-img-list { + display: flex; + gap: 20px; +} + +.add-item-wrap form .add-input .add-img-list .add-img-item { + position: relative; + max-width: 280px; + max-height: 280px; + border: 1px solid var(--gray200-color); + border-radius: 10px; + overflow: hidden; +} + +.add-item-wrap form .add-input .add-img-list .add-img-item img { + width: 100%; + min-width: 280px; + min-height: 280px; + aspect-ratio: 1 / 1; +} + +.add-item-wrap form .add-input .add-img-list .add-img-item button { + position: absolute; + top: 10px; + right: 10px; + width: 20px; + height: 20px; + line-height: 1px; + border: none; + border-radius: 100%; + color: #ffffff; + background-color: var(--blue-color); + background-image: url("../../public/images/i-close.png"); + background-repeat: no-repeat; + background-position: center; + cursor: pointer; +} + +.add-item-wrap form .add-input .add-input-title { + display: block; + font-size: 18px; + font-weight: 700; + margin-bottom: 10px; + color: var(--gray800-color); +} + +.add-item-wrap form .add-input input[type="text"], +.add-item-wrap form .add-input input[type="number"] { + width: 100%; + height: 55px; + padding: 0 25px; + border: none; + border-radius: 10px; + background-color: var(--gray100-color); +} + +.add-item-wrap form .add-input textarea { + width: 100%; + height: 200px; + padding: 15px 25px; + border: none; + border-radius: 10px; + background-color: var(--gray100-color); + resize: none; +} + +.add-item-wrap form .add-input .tag-list { + display: flex; + align-items: center; + gap: 15px; + margin-top: 15px; +} + +.add-item-wrap form .add-input .tag-list li { + display: flex; + align-items: center; + height: 50px; + padding: 0 15px; + border-radius: 25px; + color: var(--gray800-color); + background-color: #f9fafb; +} + +.add-item-wrap form .add-input .tag-list li button { + width: 20px; + height: 20px; + margin-left: 10px; + border-radius: 100%; + border: none; + background-color: var(--gray400-color); + background-image: url("../../public/images/i-close.png"); + background-repeat: no-repeat; + background-position: center; +} +/* //상품 등록하기 */ + footer { height: 160px; padding: 32px 24px 0 24px; @@ -662,6 +831,36 @@ footer .footer-wrap ul.sns { gap: 12px; } +.not-found-page { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: calc(100vh - 230px); + background-color: var(--gray200-color); +} +.not-found-page h2 { + margin: 30px 0; + font-size: 40px; +} + +.not-found-page p { + margin-bottom: 50px; + font-size: 24px; +} + +.not-found-page a { + display: flex; + align-items: center; + justify-content: center; + height: 50px; + padding: 0 20px; + border-radius: 10px; + font-size: 18px; + color: #ffffff; + background-color: var(--blue-color); +} + @media screen and (max-width: 1199px) { header { padding: 0 24px; @@ -777,6 +976,33 @@ footer .footer-wrap ul.sns { } /* //중고판매 */ + /* 상품 등록하기 */ + .add-item-wrap { + padding-left: 25px; + padding-right: 25px; + } + + .add-item-wrap form .add-input .add-img-area { + width: 160px; + height: 160px; + } + + .add-item-wrap form .add-input .add-img-area img { + max-width: 50px; + margin-bottom: 10px; + } + + .add-item-wrap form .add-input .add-img-list .add-img-item { + max-width: 160px; + max-height: 160px; + } + + .add-item-wrap form .add-input .add-img-list .add-img-item img { + min-width: 160px; + min-height: 160px; + } + /* //상품 등록하기 */ + footer { padding: 32px 104px 0 104px; } @@ -804,6 +1030,18 @@ footer .footer-wrap ul.sns { align-items: center; } + header .header-wrap .header-left { + gap: 15px; + } + + header .header-wrap .header-left .gnb ul { + gap: 15px; + } + + header .header-wrap .header-left .gnb ul li a { + font-size: 16px; + } + .item-btn { font-size: 16px; width: 154px; @@ -857,7 +1095,7 @@ footer .footer-wrap ul.sns { .login-join-wrap { max-width: 432px; padding: 0 16px; - margin: 24px auto 164px; + margin: 120px auto 100px; } .login-join-wrap > .login-join-logo { @@ -905,6 +1143,25 @@ footer .footer-wrap ul.sns { } /* //중고판매 */ + /* 상품 등록하기 */ + .add-item-wrap { + padding-top: 20px; + margin-bottom: 120px; + } + + .add-item-wrap .add-item-title h2 { + font-size: 20px; + } + + .add-item-wrap form .add-input { + margin-bottom: 15px; + } + + .add-item-wrap form .add-input .add-input-title { + font-size: 14px; + } + /* //상품 등록하기 */ + footer { padding: 32px; } diff --git a/src/index.js b/src/index.js index 96955fb93..55ef995c2 100644 --- a/src/index.js +++ b/src/index.js @@ -2,14 +2,11 @@ import React from "react"; import ReactDOM from "react-dom/client"; import reportWebVitals from "./reportWebVitals"; import App from "./components/App"; -import { BrowserRouter } from "react-router-dom"; const root = ReactDOM.createRoot(document.getElementById("root")); root.render( - - - + );