Skip to content

Commit e2ebdd7

Browse files
Merge pull request #207 from 626-ju/React-김성주-sprint7
[김성주] sprint7
2 parents 6bf1b65 + 66ffe08 commit e2ebdd7

49 files changed

Lines changed: 1268 additions & 456 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

package-lock.json

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"dependencies": {
1515
"react": "^19.1.0",
1616
"react-dom": "^19.1.0",
17+
"react-hook-form": "^7.59.0",
1718
"react-router-dom": "^7.6.0",
1819
"sass": "^1.88.0",
1920
"styled-components": "^6.1.18",

src/App.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Login from './pages/Auth/Login';
55
import SignUp from './pages/Auth/SignUp';
66
import Home from './pages/Home/Home';
77
import Items from './pages/Items/Items';
8+
import ProductDetail from './pages/ProductDetail/ProductDetail';
89
import './styles/reset.css';
910
import './styles/global.css';
1011
import './App.css';
@@ -16,6 +17,7 @@ function App() {
1617
<Route element={<Header />}>
1718
<Route path="/" element={<Home />} />
1819
<Route path="/items" element={<Items />} />
20+
<Route path="/items/:id" element={<ProductDetail />} />
1921
<Route path="/additem" element={<AddItem />} />
2022
</Route>
2123
<Route path="/login" element={<Login />} />

src/assets/icon/ic_kebab.png

187 Bytes
Loading

src/components/BackLink.jsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import backLinkIcon from '@assets/icon/ic_back.png';
2+
import { useNavigate } from 'react-router-dom';
3+
import styles from '../styles/BackLink.module.css';
4+
5+
function BackLink() {
6+
const navigator = useNavigate();
7+
8+
return (
9+
<button className={styles.backLink} onClick={() => navigator('/items')}>
10+
목록으로 돌아가기 <img src={backLinkIcon} alt="목록으로 가기 아이콘" />
11+
</button>
12+
);
13+
}
14+
15+
export default BackLink;

src/components/KebabMenu.jsx

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { useEffect, useRef, useState } from 'react';
2+
import kebabIcon from '@assets/icon/ic_kebab.png';
3+
import styles from '../styles/KebabMenu.module.css';
4+
5+
function makeHtmlAttr(selector) {
6+
const dropDownHtmlAttr = {
7+
['role']: 'option',
8+
['data-value']: selector,
9+
};
10+
11+
return dropDownHtmlAttr;
12+
}
13+
14+
function KebabMenu({ dropdownList, onSelect = () => {} }) {
15+
const [isOpen, setIsOpen] = useState(false);
16+
const kebabRef = useRef(null);
17+
18+
function handleToggleDropDown() {
19+
setIsOpen(!isOpen);
20+
}
21+
22+
function handleClick(selector) {
23+
onSelect(selector);
24+
setIsOpen(false);
25+
}
26+
27+
useEffect(() => {
28+
function closeDropDown(e) {
29+
if (!kebabRef.current.contains(e.target)) {
30+
setIsOpen(false);
31+
}
32+
}
33+
34+
window.addEventListener('click', closeDropDown);
35+
36+
return () => window.removeEventListener('click', closeDropDown);
37+
}, []);
38+
39+
return (
40+
<div className={styles.kebab}>
41+
<button ref={kebabRef} onClick={handleToggleDropDown}>
42+
<img src={kebabIcon} alt="케밥 아이콘" />
43+
</button>
44+
{isOpen && (
45+
<ul role="listbox">
46+
{dropdownList?.map((selector) => (
47+
<li
48+
key={`${selector}`}
49+
{...makeHtmlAttr(selector)}
50+
onClick={() => handleClick(selector)}
51+
>
52+
{selector}
53+
</li>
54+
))}
55+
</ul>
56+
)}
57+
</div>
58+
);
59+
}
60+
61+
export default KebabMenu;

src/components/SocialLogin.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import googleIcon from '@assets/icon/login_google.png';
22
import kakaoIcon from '@assets/icon/login_kakao.png';
3-
import styles from '@pages/Auth/styles/Login.module.css';
3+
import styles from '@pages/Auth/styles/Auth.module.css';
44

55
function SocialLogin() {
66
return (

src/components/UserProfileCard.jsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import profileImage from '@assets/icon/ic_user.png';
2+
import styles from '../styles/UserProfilCard.module.css';
3+
4+
const classNameByauthority = {
5+
post: styles.post,
6+
comment: styles.comment,
7+
};
8+
9+
function UserProfileCard({ authority, name, time, image }) {
10+
const userImage = image || profileImage;
11+
12+
return (
13+
<div className={classNameByauthority[authority]}>
14+
<div className={styles.userInfo}>
15+
<img src={userImage} alt="사용자 프로필 사진" />
16+
<div>
17+
<div className={styles.userName}>{name}</div>
18+
<div className={styles.postTime}>{time}</div>
19+
</div>
20+
</div>
21+
</div>
22+
);
23+
}
24+
25+
export default UserProfileCard;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { useEffect, useRef } from 'react';
2+
3+
export function useIntersectionObserver(targetRef, onIntersect = () => {}) {
4+
const observerRef = useRef(null);
5+
6+
useEffect(() => {
7+
if (!targetRef.current) return;
8+
9+
if (!observerRef.current) {
10+
observerRef.current = new IntersectionObserver(
11+
(entries) => {
12+
if (entries[0].isIntersecting) onIntersect();
13+
},
14+
{ threshold: 1 },
15+
);
16+
}
17+
18+
observerRef.current.observe(targetRef.current);
19+
20+
return () => observerRef.current.disconnect();
21+
}, []);
22+
}

src/hooks/useLoadItems.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export function useLoadItems(queryStrings) {
1414
setResult(result);
1515
setLoadFailed(false);
1616
} catch (err) {
17+
console.log('아이템 가져오기 실패', err);
1718
setLoadFailed(true);
1819
}
1920
}

0 commit comments

Comments
 (0)