Skip to content

Commit

Permalink
Merge pull request #79 from SWM-METEOR/FIN-429
Browse files Browse the repository at this point in the history
마이페이지 API 연동 및 좋아요 한 글 모아보기 구현
  • Loading branch information
YuriKwon authored Nov 18, 2023
2 parents 9c4943b + ab26823 commit 6d534c7
Show file tree
Hide file tree
Showing 11 changed files with 245 additions and 42 deletions.
3 changes: 2 additions & 1 deletion app/trends/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import TrendsIcon from '@/components/Icons/TrendsIcon';
import FeedIcon from '@/components/Icons/FeedIcon';
import QnAIcon from '@/components/Icons/QnAIcon';
import TrendsArticleListContainer from '@/components/articles/TrendsArticleList/TrendsArticleListContainer';
import FeedListContainer from '@/components/feed/FeedListContainer';
import QnAListContainer from '@/components/qna/QnAListContainer';

export default function TrendsPage() {
Expand Down Expand Up @@ -71,7 +72,7 @@ export default function TrendsPage() {
</div>
<div className="mb-[126px] w-full h-full">
{selectedMode === 'trends' ? <TrendsArticleListContainer /> : <></>}
{selectedMode === 'feed' ? <></> : <></>}
{selectedMode === 'feed' ? <FeedListContainer></FeedListContainer> : <></>}
{selectedMode === 'qna' ? <QnAListContainer /> : <></>}
</div>
</div>
Expand Down
40 changes: 35 additions & 5 deletions app/user/[nickname]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,31 @@
'use client';
import { useState } from 'react';
import Image from 'next/image';
import { useQuery } from '@tanstack/react-query';
import { getCookie } from 'cookies-next';

import axiosInstance from '@/utils/axios';
import TechMapContainer from '@/components/user/TechMap/TechMapContainer';
import ProfileBoxContainer from '@/components/user/ProfileBox/ProfileBoxContainer';
import MyArticleListContainer from '@/components/articles/MyArticleList/MyArticleListContainer';
import LikeArticleListContainer from '@/components/articles/LikeArticleList/LikeArticleListContainer';

export default function UserHomePage({ params }: { params: { nickname: string } }) {
// console.log(params.nickname);
const [isClickedLike, setIsClickedLike] = useState(false); // 좋아요한 글 클릭 여부
const nickname = decodeURIComponent(params.nickname);
const accessToken = getCookie('accessToken');

const likeArticleCountQuery = useQuery(
['likeArticleCount'],
async () => {
const res = await axiosInstance.get('/users/articles/like/count');
return res.data.data.likeCount;
},
{
staleTime: Infinity,
enabled: !!accessToken,
}
);

return (
<div className="w-[1280px] mx-auto">
Expand All @@ -16,22 +36,32 @@ export default function UserHomePage({ params }: { params: { nickname: string }
<div className="flex">
<div className="flex flex-col w-[200px] mr-[81px] flex-shrink-0">
{/* 좋아요한 글 필터링 버튼 */}
<button className="flex-shrink-0 flex gap-[8px] items-center justify-center h-[62px] bg-white rounded-[8px] shadow-[0_0_10px_0_rgba(0,0,0,0.05)] mb-[40px] text-[16px] font-bold">
<button
className="flex-shrink-0 flex gap-[8px] items-center justify-center h-[62px] bg-white rounded-[8px] shadow-[0_0_10px_0_rgba(0,0,0,0.05)] mb-[40px] text-[16px] font-bold"
onClick={() => setIsClickedLike(true)}
>
<Image className="" src="/filled-heart.svg" alt="heart" width="16" height="14" />
<span>좋아요한 글</span>
<span className="text-[#00A1FF]">6</span>
<span className="text-[#00A1FF]">{likeArticleCountQuery.data || 0}</span>
</button>
{/* 카테고리 목록 */}
<div>
<span className="text-[#666666] text-[14px] mb-[5px]">카테고리</span>
<div className="flex flex-col items-start">
<button className="h-[40px] my-[10px] text-[#333333] text-[16px] font-bold">
<button
className="h-[40px] my-[10px] text-[#333333] text-[16px] font-bold"
onClick={() => setIsClickedLike(false)}
>
전체(26)
</button>
</div>
</div>
</div>
<MyArticleListContainer nickname={nickname} />
{isClickedLike ? (
<LikeArticleListContainer />
) : (
<MyArticleListContainer nickname={nickname} />
)}
</div>
</div>
);
Expand Down
22 changes: 3 additions & 19 deletions components/articles/Article/ArticleView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import Image from 'next/image';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';

import GreaterThanIcon from '@/components/Icons/GreaterThanIcon';
import { SIDEPANEL_TAB_NAME } from '@/constants/sidePanel';
import useSmartDrag from '@/hooks/useSmartDrag';
import { isTooltipMode } from '@/types/smartDrag';
Expand Down Expand Up @@ -129,16 +128,13 @@ export default function ArticleView({

return (
<div className="flex flex-col gap-4 w-full max-w-full">
<p className="flex text-[16px] gap-1 items-center">
<span>프론트엔드</span>
<GreaterThanIcon />
<span className="text-[#00A1FF] font-bold">자바스크립트</span>
<div className="flex text-[16px] gap-1 items-center">
<h1 className="text-[32px] font-bold">{title}</h1>
<span className="ml-auto mr-[20px] flex gap-[16px]">
<EditButtonContainer articleId={id} authorNickname={authorNickname} />
<DeleteButtonContainer articleId={id} authorNickname={authorNickname} />
</span>
</p>
<h1 className="text-[32px] font-bold">{title}</h1>
</div>
<div className="flex items-center gap-[8px]">
<div className="w-[30px] h-[30px] rounded-[10px] overflow-hidden flex-shrink-0">
<Image src={profileImageUrl} alt={authorNickname} width="30" height="30" />
Expand All @@ -148,18 +144,6 @@ export default function ArticleView({
{/* 팔로우 */}
<FollowButtonContainer followTargetNickname={authorNickname} size={'small'} />
</div>
{/* 카테고리 */}
<div className="flex items-center gap-[5px]">
<button className="flex items-center min-w-[50px] h-[30px] py-[8px] px-[10px] rounded-[8px] border border-[#DDDDDD] text-[12px] text-[#666666]">
<span># OAuth</span>
</button>
<button className="flex items-center min-w-[50px] h-[30px] py-[8px] px-[10px] rounded-[8px] border border-[#DDDDDD] text-[12px] text-[#666666]">
<span># Spring</span>
</button>
<button className="flex items-center min-w-[50px] h-[30px] py-[8px] px-[10px] rounded-[8px] border border-[#DDDDDD] text-[12px] text-[#666666]">
<span># JAVA</span>
</button>
</div>
<div className="w-full max-w-4xl self-center py-10 text-lg">
<div className="relative">
{/* 툴팁 */}
Expand Down
7 changes: 1 addition & 6 deletions components/articles/ArticlePreview/MyArticleBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import Link from 'next/link';
import Image from 'next/image';
import GreaterThanIcon from '@/components/Icons/GreaterThanIcon';

import HeartIcon from '@/components/Icons/HeartIcon';
import CommentIcon from '@/components/Icons/CommentIcon';
import { ArticlePreviewType } from '@/types/Article';
Expand All @@ -24,11 +24,6 @@ export default function MyArticleBox({
<Image fill className="object-cover" src={thumbnail} alt="logo" sizes="100%" />
</div>
<div className="flex flex-col gap-[10px]">
<p className="flex text-[13px] gap-1 items-center">
<span>CS</span>
<GreaterThanIcon />
<span className="text-[#666666] font-bold">네트워크</span>
</p>
<p className="text-[15px] font-bold line-clamp-1">{title}</p>
<div className="text-[#666666] text-[12px] line-clamp-2">{body}</div>
<p className="flex items-center mt-[8px] text-[13px]">
Expand Down
61 changes: 61 additions & 0 deletions components/articles/LikeArticleList/LikeArticleListContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
'use client';

import { useEffect, useState } from 'react';

import LikeArticleListView from '@/components/articles/LikeArticleList/LikeArticleListView';
import axiosInstance from '@/utils/axios';
import { ArticlePreviewType } from '@/types/Article';

interface ArticleDataType {
page: number;
size: number;
articleList: ArticlePreviewType[];
}

export default function LikeArticleListContainer() {
const [articleData, setArticleData] = useState<ArticleDataType | null>(null);
const [page, setPage] = useState(1);
const [hasMoreItems, setHasMoreItems] = useState(true);
const maxReqSize = 10;

async function loadMoreUsersArticles() {
if (!hasMoreItems) return;

try {
const res = await axiosInstance.get(`/users/articles/like?page=${page}&size=${maxReqSize}`);
const articleRes = res.data.data;

if (articleRes.articleList.length === 0) {
setHasMoreItems(false);
return;
}

if (articleData) {
setArticleData((prevData) => ({
...articleRes,
articleList: [...(prevData?.articleList || []), ...articleRes.articleList],
}));
} else {
setArticleData(articleRes);
}
} catch (error) {
throw new Error('Failed to fetch article data');
}
}

// initial fetch
useEffect(() => {
loadMoreUsersArticles();
}, []);

if (!articleData) return null;

return (
<LikeArticleListView
articleList={articleData.articleList}
loadMoreItems={() => loadMoreUsersArticles()}
page={page}
setPage={setPage}
/>
);
}
67 changes: 67 additions & 0 deletions components/articles/LikeArticleList/LikeArticleListView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React, { useState, useEffect, useRef } from 'react';

import MyArticleBox from '@/components/articles/ArticlePreview/MyArticleBox';
import { ArticlePreviewType } from '@/types/Article';

interface PropsType {
articleList: ArticlePreviewType[];
loadMoreItems: () => Promise<void>;
page: number;
setPage: React.Dispatch<React.SetStateAction<number>>;
}

export default function LikeArticleListView({
articleList,
loadMoreItems,
page,
setPage,
}: PropsType) {
const [loading, setLoading] = useState(false);
const observerRef = useRef(null);

useEffect(() => {
if (page === 1) return;

const loadItems = async () => {
setLoading(true);
await loadMoreItems();
setLoading(false);
};

loadItems();
}, [page]);

useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting) {
setPage((prevPage) => prevPage + 1);
}
},
{ threshold: 0.1 }
);

if (observerRef.current) {
observer.observe(observerRef.current);
}

return () => {
if (observerRef.current) {
observer.unobserve(observerRef.current);
}
};
}, []);

return (
<div className="w-[1000px] mt-[40px]">
<p className="text-[20px] font-bold">좋아요한 글 보기</p>
<hr className="mt-[15px] w-full text-black mb-[30px]" />
<div className="grid grid-cols-2 gap-[20px]">
{articleList.map((article) => (
<MyArticleBox key={article.id} {...article} nickname={article.authorNickname} />
))}
</div>
<div ref={observerRef}></div>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ interface PropsType {

async function getRelatedArticles(nickname: string, articleTitle: string) {
try {
nickname = decodeURIComponent(nickname);
articleTitle = decodeURIComponent(articleTitle);
const res = await axiosInstance.get(`/articles/related/${nickname}/${articleTitle}`);
return res.data;
} catch (error) {
throw new Error('Failed to fetch article data');
throw new Error('Failed to fetch related article data');
}
}

Expand Down
6 changes: 6 additions & 0 deletions components/feed/FeedListContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
'use client';

import FeedListView from '@/components/feed/FeedListView';
export default function FeedListContainer() {
return <FeedListView />;
}
17 changes: 17 additions & 0 deletions components/feed/FeedListView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export default function FeedListView() {
return (
<div className="w-full h-full mt-[40px] bg-main">
<div className="w-full h-[280px] bg-white rounded-[20px] shadow-[0_0_10px_0_rgba(0,0,0,0.05)]">
<div className="flex">
<span>이미지</span>
<span>유리</span>
<span>날짜</span>
</div>
<h1>글 제목</h1>
<article>글 내용</article>
<hr className="w-full text-[#EEEEEE] mt-[12.5px]" />
<div>XX님이 댓글을 남김</div>
</div>
</div>
);
}
41 changes: 40 additions & 1 deletion components/user/ProfileBox/ProfileBoxContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,46 @@
'use client';

import { useQueries } from '@tanstack/react-query';

import axiosInstance from '@/utils/axios';
import ProfileBoxView from '@/components/user/ProfileBox/ProfileBoxView';

export default function ProfileBoxContainer() {
// 데이터 패칭
const fetchNickname = async () => {
const res = await axiosInstance.get('/users/nickname');
return res.data.data.nickname;
};

const fetchFollowerCount = async () => {
const res = await axiosInstance.get('/followers/count');
console.log(res);
return res.data.data.count;
};

const fetchProfileImageUrl = async () => {
const res = await axiosInstance.get('/users/profile-image-url');
console.log(res);
return res.data.data.profileImageUrl;
};

const results = useQueries({
queries: [
{ queryKey: ['nickname'], queryFn: fetchNickname },
{ queryKey: ['followerCount'], queryFn: fetchFollowerCount },
{ queryKey: ['profileImage'], queryFn: fetchProfileImageUrl },
],
});

const nickname = results[0].data;
const followerCount = results[1].data;
const profileImageUrl = results[2].data;

return <ProfileBoxView />;
return (
<ProfileBoxView
nickname={nickname}
followerCount={followerCount}
profileImageUrl={profileImageUrl}
/>
);
}
Loading

0 comments on commit 6d534c7

Please sign in to comment.