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