diff --git a/package-lock.json b/package-lock.json index b550accc..34847e98 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,16 +8,20 @@ "name": "sprint5", "version": "0.0.0", "dependencies": { + "@hookform/resolvers": "^5.1.1", + "clsx": "^2.1.1", "react": "^19.1.0", "react-dom": "^19.1.0", "react-hook-form": "^7.59.0", "react-router-dom": "^7.6.0", "sass": "^1.88.0", "styled-components": "^6.1.18", + "zod": "^4.0.5", "zustand": "^5.0.5" }, "devDependencies": { "@eslint/js": "^9.25.0", + "@types/node": "^24.0.10", "@types/react": "^19.1.2", "@types/react-dom": "^19.1.2", "@vitejs/plugin-react": "^4.4.1", @@ -31,6 +35,7 @@ "eslint-plugin-react-refresh": "^0.4.19", "globals": "^16.0.0", "prettier": "^3.5.3", + "typescript": "^5.8.3", "vite": "^6.3.5" } }, @@ -806,6 +811,18 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@hookform/resolvers": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.1.1.tgz", + "integrity": "sha512-J/NVING3LMAEvexJkyTLjruSm7aOFx7QX21pzkiJfMoNG0wl5aFEjLTl7ay7IQb9EWY6AkrBy7tHL2Alijpdcg==", + "license": "MIT", + "dependencies": { + "@standard-schema/utils": "^0.3.0" + }, + "peerDependencies": { + "react-hook-form": "^7.55.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -1490,6 +1507,12 @@ "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", "dev": true }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1543,6 +1566,15 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/node": { + "version": "24.0.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.10.tgz", + "integrity": "sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA==", + "dev": true, + "dependencies": { + "undici-types": "~7.8.0" + } + }, "node_modules/@types/react": { "version": "19.1.4", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.4.tgz", @@ -2050,6 +2082,14 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -5468,6 +5508,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", @@ -5486,6 +5539,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "dev": true + }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", @@ -5737,6 +5796,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zod": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.0.5.tgz", + "integrity": "sha512-/5UuuRPStvHXu7RS+gmvRf4NXrNxpSllGwDnCBcJZtQsKrviYXm54yDGV2KYNLT5kq0lHGcl7lqWJLgSaG+tgA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/zustand": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.5.tgz", diff --git a/package.json b/package.json index c34aa4a2..fa2974a9 100644 --- a/package.json +++ b/package.json @@ -12,16 +12,20 @@ "format": "prettier --write \"src/**/*.{js,jsx,css,json}\"" }, "dependencies": { + "@hookform/resolvers": "^5.1.1", + "clsx": "^2.1.1", "react": "^19.1.0", "react-dom": "^19.1.0", "react-hook-form": "^7.59.0", "react-router-dom": "^7.6.0", "sass": "^1.88.0", "styled-components": "^6.1.18", + "zod": "^4.0.5", "zustand": "^5.0.5" }, "devDependencies": { "@eslint/js": "^9.25.0", + "@types/node": "^24.0.10", "@types/react": "^19.1.2", "@types/react-dom": "^19.1.2", "@vitejs/plugin-react": "^4.4.1", @@ -35,6 +39,7 @@ "eslint-plugin-react-refresh": "^0.4.19", "globals": "^16.0.0", "prettier": "^3.5.3", + "typescript": "^5.8.3", "vite": "^6.3.5" } } diff --git a/src/App.jsx b/src/App.tsx similarity index 83% rename from src/App.jsx rename to src/App.tsx index d94204d5..35fba58f 100644 --- a/src/App.jsx +++ b/src/App.tsx @@ -1,11 +1,11 @@ import { BrowserRouter, Routes, Route } from 'react-router-dom'; import Header from './components/Header/Header'; -import AddItem from './pages/AddItem/AddItem'; +import AddItem from './pages/AddItem/AddItem.jsx'; import Login from './pages/Auth/Login'; import SignUp from './pages/Auth/SignUp'; import Home from './pages/Home/Home'; -import Items from './pages/Items/Items'; -import ProductDetail from './pages/ProductDetail/ProductDetail'; +import Items from './pages/Items/Items.jsx'; +import ProductDetail from './pages/ProductDetail/ProductDetail.jsx'; import './styles/reset.css'; import './styles/global.css'; import './App.css'; diff --git a/src/components/Header/Header.jsx b/src/components/Header/Header.tsx similarity index 92% rename from src/components/Header/Header.jsx rename to src/components/Header/Header.tsx index 14075edf..742eb0a6 100644 --- a/src/components/Header/Header.jsx +++ b/src/components/Header/Header.tsx @@ -7,7 +7,7 @@ import HeaderAuth from './HeaderAuth'; import Navigations from './Navigations'; function Header() { - const [isLoggedIn] = useState(sessionStorage.getItem('loggedIn')); + const [isLoggedIn] = useState(!!sessionStorage.getItem('loggedIn')); return ( <> diff --git a/src/components/Header/HeaderAuth.jsx b/src/components/Header/HeaderAuth.tsx similarity index 83% rename from src/components/Header/HeaderAuth.jsx rename to src/components/Header/HeaderAuth.tsx index 9b676607..cef81965 100644 --- a/src/components/Header/HeaderAuth.jsx +++ b/src/components/Header/HeaderAuth.tsx @@ -2,7 +2,11 @@ import userImg from '@assets/icon/ic_user.png'; import styles from '@styles/Header.module.css'; import { Link } from 'react-router-dom'; -function HeaderAuth({ isLoggedIn }) { +interface Props { + isLoggedIn: boolean; +} + +function HeaderAuth({ isLoggedIn }: Props) { return ( <> {isLoggedIn ? ( diff --git a/src/components/Header/Navigations.jsx b/src/components/Header/Navigations.tsx similarity index 77% rename from src/components/Header/Navigations.jsx rename to src/components/Header/Navigations.tsx index e307ae46..787495a8 100644 --- a/src/components/Header/Navigations.jsx +++ b/src/components/Header/Navigations.tsx @@ -1,5 +1,6 @@ import styles from '@styles/Header.module.css'; import { Link, NavLink, useLocation } from 'react-router-dom'; +import clsx from 'clsx'; function Navigations() { const location = useLocation(); @@ -9,10 +10,10 @@ function Navigations() { return (
- 자유게시판 + 자유게시판 (isMarketLocation ? `${styles.isActive}` : null)} + className={clsx({ [styles.isActive]: isMarketLocation })} > 중고마켓 diff --git a/src/components/KebabMenu.jsx b/src/components/KebabMenu.jsx index f529fa84..1f8f515a 100644 --- a/src/components/KebabMenu.jsx +++ b/src/components/KebabMenu.jsx @@ -1,5 +1,6 @@ -import { useEffect, useRef, useState } from 'react'; +import { useRef, useState } from 'react'; import kebabIcon from '@assets/icon/ic_kebab.png'; +import useOutsideClick from '../hooks/useOutsideClick'; import styles from '../styles/KebabMenu.module.css'; function makeHtmlAttr(selector) { @@ -24,28 +25,22 @@ function KebabMenu({ dropdownList, onSelect = () => {} }) { setIsOpen(false); } - useEffect(() => { - function closeDropDown(e) { - if (!kebabRef.current.contains(e.target)) { - setIsOpen(false); - } - } - - window.addEventListener('click', closeDropDown); + function onCloseDropdown() { + setIsOpen(false); + } - return () => window.removeEventListener('click', closeDropDown); - }, []); + useOutsideClick(kebabRef, onCloseDropdown); return ( -
- {isOpen && (