diff --git a/package-lock.json b/package-lock.json
index 3c80b3e3..06a4aba8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,8 +8,9 @@
"name": "15-sprint-mission",
"version": "0.0.0",
"dependencies": {
- "react": "^19.0.0",
- "react-dom": "^19.0.0",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "react-helmet-async": "^2.0.5",
"react-router-dom": "^7.5.0"
},
"devDependencies": {
@@ -1353,12 +1354,6 @@
"@babel/types": "^7.20.7"
}
},
- "node_modules/@types/cookie": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
- "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
- "license": "MIT"
- },
"node_modules/@types/estree": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
@@ -1935,6 +1930,21 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/fdir": {
+ "version": "6.4.4",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
+ "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
"node_modules/file-entry-cache": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -2084,6 +2094,15 @@
"node": ">=0.8.19"
}
},
+ "node_modules/invariant": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.0.0"
+ }
+ },
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -2118,7 +2137,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "dev": true,
"license": "MIT"
},
"node_modules/js-yaml": {
@@ -2228,6 +2246,18 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -2381,6 +2411,19 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/picomatch": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
+ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
"node_modules/postcss": {
"version": "8.5.3",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
@@ -2431,24 +2474,48 @@
}
},
"node_modules/react": {
- "version": "19.1.0",
- "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
- "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
+ "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==",
"license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
- "version": "19.1.0",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
- "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT",
"dependencies": {
- "scheduler": "^0.26.0"
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/react-fast-compare": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz",
+ "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==",
+ "license": "MIT"
+ },
+ "node_modules/react-helmet-async": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/react-helmet-async/-/react-helmet-async-2.0.5.tgz",
+ "integrity": "sha512-rYUYHeus+i27MvFE+Jaa4WsyBKGkL6qVgbJvSBoX8mbsWoABJXdEO0bZyi0F6i+4f0NuIb8AvqPMj3iXFHkMwg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "invariant": "^2.2.4",
+ "react-fast-compare": "^3.2.2",
+ "shallowequal": "^1.1.0"
},
"peerDependencies": {
- "react": "^19.1.0"
+ "react": "^16.6.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-refresh": {
@@ -2462,15 +2529,13 @@
}
},
"node_modules/react-router": {
- "version": "7.5.0",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.5.0.tgz",
- "integrity": "sha512-estOHrRlDMKdlQa6Mj32gIks4J+AxNsYoE0DbTTxiMy2mPzZuWSDU+N85/r1IlNR7kGfznF3VCUlvc5IUO+B9g==",
+ "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": {
- "@types/cookie": "^0.6.0",
"cookie": "^1.0.1",
- "set-cookie-parser": "^2.6.0",
- "turbo-stream": "2.4.0"
+ "set-cookie-parser": "^2.6.0"
},
"engines": {
"node": ">=20.0.0"
@@ -2486,12 +2551,12 @@
}
},
"node_modules/react-router-dom": {
- "version": "7.5.0",
- "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.5.0.tgz",
- "integrity": "sha512-fFhGFCULy4vIseTtH5PNcY/VvDJK5gvOWcwJVHQp8JQcWVr85ENhJ3UpuF/zP1tQOIFYNRJHzXtyhU1Bdgw0RA==",
+ "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.5.0"
+ "react-router": "7.6.0"
},
"engines": {
"node": ">=20.0.0"
@@ -2552,10 +2617,13 @@
}
},
"node_modules/scheduler": {
- "version": "0.26.0",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
- "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
- "license": "MIT"
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
},
"node_modules/semver": {
"version": "6.3.1",
@@ -2573,6 +2641,12 @@
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
"license": "MIT"
},
+ "node_modules/shallowequal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+ "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
+ "license": "MIT"
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -2632,11 +2706,22 @@
"node": ">=8"
}
},
- "node_modules/turbo-stream": {
- "version": "2.4.0",
- "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz",
- "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==",
- "license": "ISC"
+ "node_modules/tinyglobby": {
+ "version": "0.2.13",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz",
+ "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.4.4",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
},
"node_modules/type-check": {
"version": "0.4.0",
@@ -2693,15 +2778,18 @@
}
},
"node_modules/vite": {
- "version": "6.2.6",
- "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.6.tgz",
- "integrity": "sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw==",
+ "version": "6.3.5",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
+ "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.25.0",
+ "fdir": "^6.4.4",
+ "picomatch": "^4.0.2",
"postcss": "^8.5.3",
- "rollup": "^4.30.1"
+ "rollup": "^4.34.9",
+ "tinyglobby": "^0.2.13"
},
"bin": {
"vite": "bin/vite.js"
diff --git a/package.json b/package.json
index bbb09b73..5ecaeaf4 100644
--- a/package.json
+++ b/package.json
@@ -10,8 +10,9 @@
"preview": "vite preview"
},
"dependencies": {
- "react": "^19.0.0",
- "react-dom": "^19.0.0",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "react-helmet-async": "^2.0.5",
"react-router-dom": "^7.5.0"
},
"devDependencies": {
diff --git a/src/App.jsx b/src/App.jsx
new file mode 100644
index 00000000..4d5dfaa5
--- /dev/null
+++ b/src/App.jsx
@@ -0,0 +1,33 @@
+import { BrowserRouter, Routes, Route } from 'react-router-dom';
+import { HelmetProvider } from 'react-helmet-async';
+import Index from './pages/Index.jsx';
+import Items from './pages/Items.jsx';
+import AddItem from './pages/AddItem.jsx';
+import Auth from './pages/Auth.jsx';
+import Login from './pages/Login.jsx';
+import Signup from './pages/Signup.jsx';
+import './reset.css';
+import './color.css';
+
+function App() {
+ return (
+
+
+
+ } />
+
+ } />
+
+ } />
+
+ }>
+ } />
+ } />
+
+
+
+
+ );
+}
+
+export default App;
diff --git a/src/assets/ic_X.svg b/src/assets/ic_X.svg
new file mode 100644
index 00000000..f6674f7f
--- /dev/null
+++ b/src/assets/ic_X.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/assets/ic_plus.svg b/src/assets/ic_plus.svg
new file mode 100644
index 00000000..5bb9abf5
--- /dev/null
+++ b/src/assets/ic_plus.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/color.css b/src/color.css
index 1d55bfe6..9b27e3ab 100644
--- a/src/color.css
+++ b/src/color.css
@@ -1,5 +1,7 @@
:root {
- --blue: #3692FF;
+ --blue100: #3692FF;
+ --blue200: #1967D6;
+ --blue300: #1251AA;
--red: #F74747;
--gray900: #111827;
--gray800: #1F2937;
diff --git a/src/components/AuthInput.css b/src/components/AuthInput.css
new file mode 100644
index 00000000..4b6822fb
--- /dev/null
+++ b/src/components/AuthInput.css
@@ -0,0 +1,40 @@
+.auth-input-box * {
+ border: none;
+}
+
+.auth-input-box input {
+ margin-bottom: 24px;
+}
+
+.auth-input-box input.wrong {
+ margin-bottom: 8px;
+ border: solid 1px var(--red);
+}
+
+.auth-input-box input.correct {
+ border: solid 1px var(--blue100);
+}
+
+.auth-input-box .wrong-message {
+ margin: 8px 16px 18px;
+ color: var(--red);
+ font-weight: 600;
+ font-size: 14px;
+ line-height: 24px;
+}
+
+.auth-input-box.password {
+ position: relative;
+}
+
+.auth-input-box.password .eye-btn {
+ width: 24px;
+ padding: 0;
+ position: absolute;
+ top: 58px;
+ right: 24px;
+}
+
+.auth-input-box.password .eye-btn img {
+ width: 100%;
+}
\ No newline at end of file
diff --git a/src/components/AuthInput.jsx b/src/components/AuthInput.jsx
new file mode 100644
index 00000000..954e1222
--- /dev/null
+++ b/src/components/AuthInput.jsx
@@ -0,0 +1,70 @@
+import { useState } from "react";
+import Input from "./Input";
+import icEyeVisible from "../assets/ic_eye_visible.svg";
+import icEyeInvisible from "../assets/ic_eye_invisible.svg";
+import "./AuthInput.css";
+
+function PasswordInput ({ name, className, onFocusout, valid, wrongMessage, ...props }) {
+ const [isVisible, setIsVisible] = useState(false);
+ const onClick = () => {
+ setIsVisible((prev) => !prev);
+ }
+ const passwordMatch = () => {
+ try {
+ const password = document.querySelector("input#password");
+ const passwordCheck = document.querySelector("input#password-check");
+ if (password.value === passwordCheck.value) {
+ passwordCheck.classList.add("correct");
+ passwordCheck.classList.remove("wrong");
+ passwordCheck.nextElementSibling.nextElementSibling.textContent = null;
+ } else if (passwordCheck.value) {
+ passwordCheck.classList.add("wrong");
+ passwordCheck.classList.remove("correct");
+ passwordCheck.nextElementSibling.nextElementSibling.textContent = "비밀번호가 일치하지 않습니다.";
+ }
+ } catch (e) {}
+ }
+
+ return (
+
+
+
{wrongMessage}
+
+ );
+}
+
+export default function AuthInput ({ label, name, type="text", placeholder='', emptyWrongMessage='', invalidWrongMessage='' }) {
+ const [valid, setValid] = useState('');
+ const [wrongMessage, setWrongMessage] = useState(null);
+
+ const onFocusout = (e) => {
+ if (!e.target.value) {
+ setValid("wrong");
+ setWrongMessage(emptyWrongMessage);
+ } else if (!e.target.validity.valid) {
+ setValid("wrong");
+ setWrongMessage(invalidWrongMessage);
+ } else {
+ setValid("correct");
+ setWrongMessage(null);
+ }
+ }
+
+ if (type === "password") {
+ return ();
+ }
+ return (
+ {wrongMessage}
+ );
+}
\ No newline at end of file
diff --git a/src/components/Button.jsx b/src/components/Button.jsx
new file mode 100644
index 00000000..917d8585
--- /dev/null
+++ b/src/components/Button.jsx
@@ -0,0 +1,19 @@
+import { useNavigate } from "react-router-dom";
+import styles from "./Button.module.css";
+
+//styleType은 small, medium, large로 나뉜다
+function Button ({ styleType="small", className, onClick, to, children, ...props }) {
+ const navigate = useNavigate();
+ const handleClick = (e) => {
+ if (onClick) onClick(e);
+ if (to) setTimeout(() => navigate(to), 0);
+ }
+
+ return (
+
+ );
+}
+
+export default Button;
\ No newline at end of file
diff --git a/src/components/Button.module.css b/src/components/Button.module.css
new file mode 100644
index 00000000..85d59458
--- /dev/null
+++ b/src/components/Button.module.css
@@ -0,0 +1,39 @@
+.btn {
+ padding: 0;
+ border: none;
+ background-color: var(--blue100);
+ color: var(--gray100);
+ font-weight: 600;
+ text-align: center;
+ align-content: center;
+}
+
+.btn:hover {
+ background-color: var(--blue200);
+}
+
+.btn:active {
+ background-color: var(--blue300);
+}
+
+.btn:disabled {
+ background-color: var(--gray400);
+}
+
+.btn.small {
+ border-radius: 8px;
+ font-size: 16px;
+ line-height: 26px;
+}
+
+.btn.medium {
+ border-radius: 9999px;
+ font-size: 18px;
+ line-height: 26px;
+}
+
+.btn.large {
+ border-radius: 9999px;
+ font-size: 20px;
+ line-height: 32px;
+}
\ No newline at end of file
diff --git a/src/components/Dropdown.jsx b/src/components/Dropdown.jsx
index e369af27..74297635 100644
--- a/src/components/Dropdown.jsx
+++ b/src/components/Dropdown.jsx
@@ -1,26 +1,36 @@
-import { useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
import icArrowDown from '../assets/ic_arrow_down.svg';
import icSort from '../assets/ic_sort.svg';
import styles from './Dropdown.module.css';
-const option = { favorite: "좋아요순", recent: "최신순" }
+const option = { recent: "최신순", favorite: "좋아요순", }
function Dropdown({ state, setState, mode }) {
const [isOpen, setIsOpen] = useState(false);
+ const dropdownRef = useRef(null);
+
+ useEffect(() => {
+ const handleClickOutside = (e) => {
+ if (dropdownRef.current && !dropdownRef.current.contains(e.target)) setIsOpen(false);
+ };
+ document.addEventListener("click", handleClickOutside);
+ return () => document.removeEventListener("click", handleClickOutside);;
+ }, []);
return (
);
diff --git a/src/components/Dropdown.module.css b/src/components/Dropdown.module.css
index 7a903704..98117e5b 100644
--- a/src/components/Dropdown.module.css
+++ b/src/components/Dropdown.module.css
@@ -29,19 +29,23 @@
border: 1px solid var(--gray200);
border-radius: 12px;
background-color: white;
- color: var(--gray800);
- font-weight: 400;
- font-size: 16px;
- line-height: 26px;
}
-.dropdownList li {
+.dropdownList li input {
+ display: block;
cursor: pointer;
- height: 42px;
- padding-top: 9px;
+ width: 100%;
+ height: 41px;
+ padding: 8px 0 6px;
+ border: none;
border-bottom: 1px solid var(--gray200);
+ background-color: transparent;
+ color: var(--gray800);
+ font-weight: 400;
+ font-size: 16px;
+ line-height: 26px;
}
-.dropdownList li:last-child {
+.dropdownList li:last-child input {
border: none;
}
\ No newline at end of file
diff --git a/src/components/Input.jsx b/src/components/Input.jsx
index 448d85c7..e0e836b2 100644
--- a/src/components/Input.jsx
+++ b/src/components/Input.jsx
@@ -1,88 +1,13 @@
-import { useState } from "react";
-import icEyeVisible from "../assets/ic_eye_visible.svg";
-import icEyeInvisible from "../assets/ic_eye_invisible.svg";
+import styles from "./Input.module.css";
-function NormalInput ({ name, type, placeholder, onFocusout, valid, wrongMessage }) {
- return (<>
-
- {wrongMessage}
- >)
-}
-
-function PasswordInput ({ name, placeholder, onFocusout, valid, wrongMessage }) {
- const [isVisible, setIsVisible] = useState(false);
- const onClick = () => {
- setIsVisible((prev) => !prev);
- }
- const passwordMatch = () => {
- try {
- const password = document.querySelector("input#password");
- const passwordCheck = document.querySelector("input#password-check");
- if (password.value === passwordCheck.value) {
- passwordCheck.classList.add("correct");
- passwordCheck.classList.remove("wrong");
- passwordCheck.nextElementSibling.textContent = null;
- } else if (passwordCheck.value) {
- passwordCheck.classList.add("wrong");
- passwordCheck.classList.remove("correct");
- passwordCheck.nextElementSibling.textContent = "비밀번호가 일치하지 않습니다.";
- }
- } catch (e) {}
- }
-
- if (name === "password-check") {
- return (
-
-
-
-
);
- }
- return (
-
-
{wrongMessage}
-
+function Input ({ label, name, className, inputClassName, type, children, ...props }) {
+ return (
+
+ {type === "textarea"
+ ?
+ : }
+ {children}
);
-}
-
-export default function Input ({ label, name, type="text", placeholder='', emptyWrongMessage='', invalidWrongMessage='' }) {
- const [valid, setValid] = useState('');
- const [wrongMessage, setWrongMessage] = useState(null);
-
- const onFocusout = (e) => {
- if (!e.target.value) {
- setValid("wrong");
- setWrongMessage(emptyWrongMessage);
- } else if (!e.target.validity.valid) {
- setValid("wrong");
- setWrongMessage(invalidWrongMessage);
- } else {
- setValid("correct");
- setWrongMessage(null);
- }
- }
+}
- if (type === "password") {
- return (<>
-
-
- >);
- }
- return (<>
-
-
- >);
-}
\ No newline at end of file
+export default Input;
\ No newline at end of file
diff --git a/src/components/Input.module.css b/src/components/Input.module.css
new file mode 100644
index 00000000..f6c3a2ff
--- /dev/null
+++ b/src/components/Input.module.css
@@ -0,0 +1,31 @@
+.box > * {
+ display: block;
+}
+
+.box label {
+ margin-bottom: 16px;
+ font-weight: 700;
+ font-size: 18px;
+ line-height: 26px;
+}
+
+.box input,
+.box textarea {
+ width: 100%;
+ padding: 15px 24px ;
+ border: none;
+ border-radius: 12px;
+ background-color: var(--gray100);
+ font-weight: 400;
+ font-size: 16px;
+ line-height: 26px;
+}
+
+.box textarea {
+ resize: none;
+}
+
+.box input::placeholder,
+.box textarea::placeholder {
+ color: var(--gray400);
+}
\ No newline at end of file
diff --git a/src/components/ItemList.jsx b/src/components/ItemList.jsx
index 4ffcf5da..4904eb85 100644
--- a/src/components/ItemList.jsx
+++ b/src/components/ItemList.jsx
@@ -1,23 +1,9 @@
-import { useEffect, useState } from "react";
-import { getProducts } from "../api/api";
import icHeart from "../assets/ic_heart.svg";
import styles from "./ItemList.module.css";
-export default function ItemList ( { rows, columns, itemCount, page, orderBy, setTotalCount } ) {
- const [items, setItems] = useState([]);
- const getItems = async () => {
- const { totalCount, list } = await getProducts({ page, pageSize: (itemCount || rows*columns), orderBy });
- setTotalCount(totalCount);
- setItems(list);
- }
-
- useEffect( () => {
- getItems();
- return () => { if (itemCount) return; }
- }, [rows, columns, page, orderBy]);
-
+export default function ItemList ( { rows, columns, items } ) {
return (
- {items.slice(0,rows*columns).map((e) => (
+ {items.slice(0, rows*columns).map((e) => (
))}
);
diff --git a/src/components/Nav.jsx b/src/components/Nav.jsx
new file mode 100644
index 00000000..336becc2
--- /dev/null
+++ b/src/components/Nav.jsx
@@ -0,0 +1,28 @@
+import { Link, NavLink } from 'react-router-dom';
+import Button from './Button';
+import logoBig from '../assets/logo.png';
+import logoSmall from '../assets/logo_text.png';
+import icProfile from '../assets/ic_profile.svg';
+import styles from "./Nav.module.css";
+
+//type: default, tab, profile
+export default function Nav({ type = "default" }) {
+ return (
);
+}
\ No newline at end of file
diff --git a/src/components/Nav.module.css b/src/components/Nav.module.css
new file mode 100644
index 00000000..70b0631f
--- /dev/null
+++ b/src/components/Nav.module.css
@@ -0,0 +1,96 @@
+.nav {
+ z-index: 1;
+ height: 70px;
+ background-color: white;
+ border-bottom: 1px solid #dfdfdf;
+ padding: 0 200px;
+ justify-content: center;
+ align-content: center;
+ position: sticky;
+ top: 0;
+}
+
+.nav .container {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.nav #logo {
+ height: 51px;
+ margin-right: 32px;
+ font-size: 0;
+}
+
+.nav #logo img {
+ height: 100%;
+}
+
+.nav #logo .small {
+ display: none;
+}
+
+.nav .menu > a {
+ margin: 0 15px;
+ color: var(--gray600);
+ font-size: 18px;
+ font-weight: 700;
+ line-height: 26px;
+}
+
+.nav .menu > a.selected {
+ color: var(--blue100);
+}
+
+.nav img.profile {
+ height: 40px;
+}
+
+.nav button.default {
+ width: 128px;
+ height: 48px;
+}
+
+.nav button.tab {
+ width: 88px;
+ height: 42px;
+}
+
+/* Tablet */
+@media (max-width: 1199px) {
+ .nav {
+ padding: 0 24px;
+ }
+
+ .nav #logo {
+ margin-right: 20px;
+ }
+}
+
+/* Mobile */
+@media (max-width: 767px) {
+ .nav {
+ padding: 0 16px;
+ }
+
+ .nav #logo {
+ margin-right: 8px;
+ }
+
+ .nav #logo .small {
+ display: unset;
+ }
+
+ .nav #logo .big {
+ display: none;
+ }
+
+ .nav .menu {
+ gap: 8px;
+ }
+
+ .nav .menu > a {
+ margin: 0;
+ font-size: 16px;
+ }
+}
\ No newline at end of file
diff --git a/src/components/Pagenation.jsx b/src/components/Pagenation.jsx
index 492746f2..d44cee7d 100644
--- a/src/components/Pagenation.jsx
+++ b/src/components/Pagenation.jsx
@@ -21,6 +21,7 @@ export default function Pagenation({ page, setPage, pageSize, totalCount }) {
return (