Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
d9e4e50
Feat: signup페이지 구현
bhoh032019 Dec 5, 2024
98be613
Feat: localstorage에 accessToken이 존재할때 로그인,회원가입 페이지 접근시 홈으로 리다이렉트
bhoh032019 Dec 5, 2024
5f79558
Feat: localStorage의 accessToken의 여부에 따른 navbar 변경
bhoh032019 Dec 5, 2024
20903f7
Fix: console.log 제거
bhoh032019 Dec 5, 2024
6cbabff
Feat: context API 이용시 불필요한 렌더링을 방지하기 위한 useMemo이용
bhoh032019 Dec 9, 2024
3f956dc
Fix: 댓글 리스트를 map을 사용해 UI로 표시할 수 있는 부분 CommentList컴포넌트로 분리해 관리
bhoh032019 Dec 9, 2024
7b64147
Fix: 많은 useState를 객체를 이용해 관리
bhoh032019 Dec 9, 2024
ab71e60
Fix: signup페이지 객체를 이용하도록 수정
bhoh032019 Dec 10, 2024
0a17f9a
Feat: 로그인 상태에 따른 navbar의 변화를 context를 이용해 관리
bhoh032019 Dec 10, 2024
39091e4
refactor: '/items'페이지를 위한 기존 코드 리팩토링
bhoh032019 Jan 15, 2025
f32dfe2
feat: 중고마켓 페이지 경로 수정 및 css 파일 이름 변경
bhoh032019 Jan 17, 2025
51b3d97
feat: react-query설치
bhoh032019 Jan 18, 2025
a926039
fix: 중복 코드 제거
bhoh032019 Jan 18, 2025
4a6004f
feat: react-query를 활용한 infinite scroll 구현
bhoh032019 Jan 18, 2025
9f5995f
feat: react-query를 이용한 bestArticle을 구현
bhoh032019 Jan 18, 2025
ea0e050
fix: 불필요한 props제거
bhoh032019 Jan 18, 2025
4f11ed4
feat: useMutation을 이용한 댓글 등록 기능 추가
bhoh032019 Jan 18, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file modified .next/cache/webpack/client-development-fallback/0.pack.gz
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file modified .next/cache/webpack/client-development/0.pack.gz
Binary file not shown.
Binary file modified .next/cache/webpack/client-development/1.pack.gz
Binary file not shown.
Binary file modified .next/cache/webpack/client-development/10.pack.gz
Binary file not shown.
Binary file modified .next/cache/webpack/client-development/11.pack.gz
Binary file not shown.
Binary file modified .next/cache/webpack/client-development/12.pack.gz
Binary file not shown.
Binary file modified .next/cache/webpack/client-development/13.pack.gz
Binary file not shown.
Binary file modified .next/cache/webpack/client-development/14.pack.gz
Binary file not shown.
Binary file modified .next/cache/webpack/client-development/2.pack.gz
Binary file not shown.
Binary file modified .next/cache/webpack/client-development/3.pack.gz
Binary file not shown.
Binary file modified .next/cache/webpack/client-development/4.pack.gz
Binary file not shown.
Binary file modified .next/cache/webpack/client-development/5.pack.gz
Binary file not shown.
Binary file modified .next/cache/webpack/client-development/6.pack.gz
Binary file not shown.
Binary file modified .next/cache/webpack/client-development/7.pack.gz
Binary file not shown.
Binary file modified .next/cache/webpack/client-development/8.pack.gz
Binary file not shown.
Binary file modified .next/cache/webpack/client-development/9.pack.gz
Binary file not shown.
Binary file modified .next/cache/webpack/client-development/index.pack.gz
Binary file not shown.
Binary file modified .next/cache/webpack/client-development/index.pack.gz.old
Binary file not shown.
Binary file modified .next/cache/webpack/server-development/0.pack.gz
Binary file not shown.
Binary file modified .next/cache/webpack/server-development/1.pack.gz
Binary file not shown.
Binary file modified .next/cache/webpack/server-development/10.pack.gz
Binary file not shown.
Binary file modified .next/cache/webpack/server-development/11.pack.gz
Binary file not shown.
Binary file modified .next/cache/webpack/server-development/2.pack.gz
Binary file not shown.
Binary file modified .next/cache/webpack/server-development/3.pack.gz
Binary file not shown.
Binary file modified .next/cache/webpack/server-development/4.pack.gz
Binary file not shown.
Binary file modified .next/cache/webpack/server-development/5.pack.gz
Binary file not shown.
Binary file modified .next/cache/webpack/server-development/6.pack.gz
Binary file not shown.
Binary file modified .next/cache/webpack/server-development/7.pack.gz
Binary file not shown.
Binary file modified .next/cache/webpack/server-development/8.pack.gz
Binary file not shown.
Binary file modified .next/cache/webpack/server-development/9.pack.gz
Binary file not shown.
Binary file modified .next/cache/webpack/server-development/index.pack.gz
Binary file not shown.
Binary file modified .next/cache/webpack/server-development/index.pack.gz.old
Binary file not shown.
62 changes: 62 additions & 0 deletions components/CommentList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React from 'react';
import Profile from '@public/svgs/ic_profile.svg';
import SortIcon from '@public/svgs/ic_kebab.svg';
import styles from '@styles/BoardDetailPage.module.css';

interface CommentListProps {
comment: {
id: number;
content: string;
createdAt: string;
updatedAt: string;
writer: {
id: number;
nickname: string;
image?: string;
};
};
index: number;
}

export default function CommentList({ comment, index }: CommentListProps) {
const formatDate = (isoDate: string) => {
const date = new Date(isoDate);
return date
.toLocaleDateString('ko-KR', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
})
.replace(/\. /g, '. ')
.slice(0, -1); // 공백 제거
};

return (
<div className={styles['comment-container']} key={index}>
<div className={styles['comment-header']}>
<div className={styles['comment-content']}>{comment.content}</div>
<SortIcon />
</div>
<div className={styles['comment-info']}>
<div className={styles['comment-info-content']}>
{comment.writer.image ? (
<img
className={styles['profile-icon']}
src={comment.writer.image}
/>
) : (
<Profile className={styles['profile-icon']} />
)}
<div className={styles['comment-user-info']}>
<div className={styles['comment-nickname']}>
{comment.writer.nickname}
</div>
<div className={styles['comment-updatedAt']}>
{formatDate(comment.updatedAt)}
</div>
</div>
</div>
</div>
</div>
);
}
29 changes: 0 additions & 29 deletions components/ItemCard.tsx

This file was deleted.

44 changes: 40 additions & 4 deletions components/Layout/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,22 @@ import Link from 'next/link';
import { useRouter } from 'next/router';
import styles from '@styles/Header.module.css';
import Image from 'next/image';
import { useState } from 'react';
import { useAuth } from '@/contexts/AuthProvider';

const Header = () => {
const { pathname } = useRouter();
const { isAuthenticated, logout } = useAuth();
const [isDropdownVisible, setIsDropdownVisible] = useState(false);

const toggleDropdown = () => {
setIsDropdownVisible(!isDropdownVisible);
};

const handleLogout = () => {
logout();
setIsDropdownVisible(false);
};

return (
<>
Expand All @@ -26,17 +39,40 @@ const Header = () => {
href="/items"
className={
pathname === '/items' || pathname === '/additem'
? styles['active-link nav-list']
? `${styles['active-link']} ${styles['nav-list']}`
: styles['nav-list']
}
>
중고마켓
</Link>
</div>
</div>
<Link className={styles['login']} href="/login">
로그인
</Link>
{isAuthenticated ? (
<div className={styles['sortButtonWrapper']}>
<button
className={styles['sortDropdownTriggerButton']}
onClick={toggleDropdown}
>
<Image
src="/svgs/ic_profile.svg"
width={40}
height={40}
alt="프로필 이미지"
/>
</button>
{isDropdownVisible && (
<div className={styles['dropdownMenu']}>
<div className={styles['dropdownItem']} onClick={handleLogout}>
로그아웃
</div>
</div>
)}
</div>
) : (
<Link className={styles['login']} href="/login">
로그인
</Link>
)}
</nav>
</>
);
Expand Down
118 changes: 35 additions & 83 deletions components/boards/AllArticlesSection.tsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,37 @@
import { useEffect, useRef, useState } from 'react';
import { useInfiniteQuery } from 'react-query';
import styles from '@styles/AllArticlesSection.module.css';
import { getAllArticles } from '@pages/api/boardApi';
import Link from 'next/link';
import Image from 'next/image';
import Heart from '@public/svgs/ic_heart.svg';
import SearchBar from '@components/SearchBar';
import router, { useRouter } from 'next/router';
import { useRouter } from 'next/router';
import DropdownMenu from '@components/DropdownMenu';
import { ArticleList, ArticleOrderBy } from '@components/types/articleTypes';
import React from 'react';
import React, { useRef, useEffect } from 'react';

interface AllArticlesSectionProps {
initialArticles: ArticleList[];
}

export default function AllArticlesSection({
initialArticles,
}: AllArticlesSectionProps) {
const [articles, setArticles] = useState<ArticleList[]>(initialArticles);
const [orderBy, setOrderBy] = useState<ArticleOrderBy>('recent');
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(initialArticles.length > 0);
const [loading, setLoading] = useState(false);
export default function AllArticlesSection() {
const router = useRouter();
const loadMoreRef = useRef<HTMLDivElement | null>(null);

const keyword = (router.query.q as string) || '';
const loadMoreRef = useRef(null);
const [orderBy, setOrderBy] = React.useState<ArticleOrderBy>('recent');

const formatDate = (isoDate: string) => {
const date = new Date(isoDate);
return date
.toLocaleDateString('ko-KR', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
})
.replace(/\. /g, '. ')
.slice(0, -1); // 공백 제거
};
const { data, fetchNextPage, hasNextPage, isFetching, isLoading, refetch } =
useInfiniteQuery(
['allArticles', { orderBy, keyword }],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이런 key 값들은 보통 queries.ts 파일 하나 생성 수 그 곳에 객체로 관리합니다.

const DEFAULT_KEY = ['']
const QUERY_KEYS = {
  ALL_ARTICLES = [...DEFAULT_KEY, 'allArticles'] as const
  ...
}

({ pageParam = 1 }) => getAllArticles(pageParam, orderBy, keyword),
{
getNextPageParam: (lastPage, allPages) => {
return lastPage.list.length > 0 ? allPages.length + 1 : undefined;
},
staleTime: 1000 * 60 * 5, // Cache for 5 minutes
},
);

const handleSortSelection = (sortOption: ArticleOrderBy) => {
setOrderBy(sortOption);
resetData();
refetch();
};

const handleSearch = (searchKeyword: string) => {
Expand All @@ -56,42 +47,6 @@ export default function AllArticlesSection({
});
};

const resetData = () => {
setArticles([]);
setPage(1);
setHasMore(true);
};

const fetchAllArticles = async (currentPage: number) => {
setLoading(true);
try {
const data = await getAllArticles(currentPage, orderBy, keyword);
if (data.list.length > 0) {
setArticles((prevArticles) => [...prevArticles, ...data.list]);
} else {
setHasMore(false);
}
} catch (error) {
console.error('데이터를 불러오는데 실패 했습니다.', error);
} finally {
setLoading(false);
}
};

useEffect(() => {
if (page === 1 && articles === initialArticles) return; // 중복 호출 방지
if (page > 1) {
fetchAllArticles(page);
}
}, [page]);

useEffect(() => {
if (articles !== initialArticles) {
resetData();
fetchAllArticles(1);
}
}, [orderBy, keyword]);

useEffect(() => {
const observerOptions = {
root: null,
Expand All @@ -101,28 +56,26 @@ export default function AllArticlesSection({

const observerCallback = (entries: IntersectionObserverEntry[]) => {
entries.forEach((entry) => {
if (entry.isIntersecting && hasMore && !loading) {
setPage((prevPage) => prevPage + 1);
if (entry.isIntersecting && hasNextPage && !isFetching) {
fetchNextPage();
}
Comment on lines +59 to 61
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

사용방법에 맞춰서 잘 하셨습니다. 👍

});
};

const observerInstance = new IntersectionObserver(
const observer = new IntersectionObserver(
observerCallback,
observerOptions,
);
const currentRef = loadMoreRef.current;

if (currentRef) {
observerInstance.observe(currentRef);
}
if (currentRef) observer.observe(currentRef);

return () => {
if (currentRef) {
observerInstance.unobserve(currentRef);
}
if (currentRef) observer.unobserve(currentRef);
};
}, [hasMore, loading]);
}, [hasNextPage, isFetching, fetchNextPage]);

if (isLoading) return <div>Loading...</div>;

return (
<div>
Expand All @@ -142,12 +95,12 @@ export default function AllArticlesSection({
]}
/>
</div>
{!loading && articles.length === 0 && (
{!isFetching && data?.pages[0]?.list.length === 0 && (
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이렇게 data?.pages[0].list 로 접근하기 보다는 변수로 만들어서 접근하면 가독성이 더 좋을 것 같아요~

const queryData = ata?.pages[0]?.list;

<div>데이터가 존재하지 않습니다.</div>
)}
{articles.length > 0 ? (
<div className={styles['allarticle-list']}>
{articles.map((article) => (
{data?.pages.map((page, pageIndex) => (
<div key={pageIndex} className={styles['allarticle-list']}>
{page.list.map((article: ArticleList) => (
<Link
href={`/board/${article.id}`}
key={article.id}
Expand All @@ -169,7 +122,7 @@ export default function AllArticlesSection({
{article.writer.nickname}
</span>
<span className={styles['allarticle-updatedAt']}>
{formatDate(article.createdAt)}
{new Date(article.createdAt).toLocaleDateString()}
</span>
</div>
<span className={styles['allarticle-likes']}>
Expand All @@ -180,10 +133,9 @@ export default function AllArticlesSection({
</Link>
))}
</div>
) : (
loading && <div>데이터를 불러오는 중...</div>
)}
{hasMore && !loading && <div ref={loadMoreRef} className="h-10" />}
))}
{hasNextPage && !isFetching && <div ref={loadMoreRef} className="h-10" />}
{isFetching && <div>Loading more...</div>}
</div>
);
}
Loading
Loading