Skip to content

Commit

Permalink
Feat/#55: 상대, 내 프로필 조건부 렌더링, 게시글 삭제 기능 추가 (#61)
Browse files Browse the repository at this point in the history
* refactor: 유저 아이디 미리 받아서 export 하던 `memberId` 변수를 유저 아이디 받아오는 "함수" 유틸로 변경

* fix: 내 프로필 - 아이디어, 북마크 글이 많을때 마지막 글이 가려지는 마크업 수정

* refactor: 프로필, 프로필 수정 라우팅 주소 변경

* refactor: 프로필 수정 후 navigate 주소 userId param 적용해서 알맞게 수정

* refactor: 유저 정보 받아오는 api: 외부에서 userId 받아오는 것으로 수정

* fix: 수정된 타입에 맞게 Props type 수정

* refactor: Navbar 조건부 렌더링 주소 설정: 프로필 수정 라우팅 주소 변경에 대응

* refactor: Navbar 내 프로필 버튼 클릭 시 navigate 주소 알맞게 수정

* refactor: 라우팅 훅 useNavigatePage 추가

* fix: getUserId 잘못쓰이던 부분 수정

* refactor: 프로필 페이지에서 쓰이는 `/ideas:id` GET api가 `userId` 파라미터 받도록 수정

* refactor: 프로필 북마크 게시글 응답 타입 수정

* feat: `NewIdeaCard.Profile` 프로필 클릭 콜백 prop 추가

* feat: 피드 게시글 프로필 클릭 시 상대 프로필 페이지로 이동

* !refactor: `Profile` 프로필 페이지를 내 프로필, 상대 프로필에 따라 조건부 렌더링

`ProfileInfoSection`은 조건부 렌더링이 "프로필 수정"버튼만 있어서 일단 공통으로 뺐습니다.

* feat: 상대 프로필 헤더 추가

* style: 상대 프로필 헤더 배경을 흰색 & 위치 맨 위 고정 & 회색 Divider 추가

* style: 프로필에서 뒤로가기 `Back` 컴포넌트가 흰색이 아니도록 수정

* fix: useMemberInfoQuery parameter 주입

* feat: 게시글 삭제 기능 추가
  • Loading branch information
yogjin authored Mar 10, 2024
1 parent 1a24a5f commit cffb5ec
Show file tree
Hide file tree
Showing 30 changed files with 459 additions and 115 deletions.
2 changes: 1 addition & 1 deletion src/layouts/Back.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const Back = () => {
const { hasMatched } = useRouteMatched();
const navigate = useNavigate();

const isMatchedWhiteStyle = hasMatched('/profile/:id', '/feed/:id');
const isMatchedWhiteStyle = hasMatched('/feed/:id');

return (
<Wrapper onClick={() => navigate(-1)} isWhiteStyle={isMatchedWhiteStyle}>
Expand Down
5 changes: 3 additions & 2 deletions src/layouts/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import {
import { useNavigate } from 'react-router-dom';

import useRouteMatched from '../hooks/useRouteMatch';
import { getUserId } from '../pages/Profile/utils/getUserId';

const Navbar = () => {
const { hasMatched } = useRouteMatched();
const navigate = useNavigate();
const isMatchedNavigation = hasMatched(
'/profile/:id',
'/profile-edit/:id',
'/login',
'/write',
'/agreement',
Expand All @@ -35,7 +36,7 @@ const Navbar = () => {
<Navigation.Item position="center" onClick={() => navigate('/write')}>
<SVGNavWrite24 />
</Navigation.Item>
<Navigation.Item onClick={() => navigate('/profile')}>
<Navigation.Item onClick={() => navigate(`/profile/${getUserId()}`)}>
{location.pathname.startsWith('/profile') ? <SVGNavActiveProfile /> : <SVGNavProfile />}
</Navigation.Item>
</Navigation>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import { Spacer, Text } from 'concept-be-design-system';
import { Fragment, Suspense, useRef } from 'react';

import NewIdeaCardListSkeleton from './NewIdeaCardListSkeleton';
import { useDeleteIdea } from '../../../components/NewIdeaCard/hooks/mutations/useDeleteIdea';
import NewIdeaCard from '../../../components/NewIdeaCard/NewIdeaCard';
import useNavigatePage from '../../../hooks/useNavigatePage';
import { getUserId } from '../../../Profile/utils/getUserId';
import { useFilterParams } from '../../context/filterContext';
import { useIdeasQuery } from '../../hooks/queries/useIdeasQuery';
import { useFeedInfiniteFetch } from '../../hooks/useFeedInfiniteFetch';
Expand All @@ -14,11 +17,18 @@ const nickname = getUserNickname();
const CardList = () => {
const { filterParams, updateFilterParams } = useFilterParams();
const { ideas, fetchNextPage } = useIdeasQuery(filterParams);
const { goProfilePage } = useNavigatePage();
const { deleteIdea } = useDeleteIdea();

const intersectionRef = useRef(null);

useFeedInfiniteFetch(intersectionRef, fetchNextPage);

const handleDeleteIdea = (ideaId: number) => {
//TODO: #54 머지 후 Confirm 컴포넌트로 대체
if (confirm('게시글을 삭제하시겠습니까?')) deleteIdea(ideaId);
};

return (
<>
{ideas.map((idea, idx) => {
Expand Down Expand Up @@ -49,12 +59,12 @@ const CardList = () => {
<Fragment key={idx}>
{isMine ? (
<NewIdeaCard id={idea.id} content={content} footer={footer}>
<NewIdeaCard.Content />
<NewIdeaCard.Content onClickDelete={() => handleDeleteIdea(idea.id)} />
<NewIdeaCard.Footer />
</NewIdeaCard>
) : (
<NewIdeaCard id={idea.id} profile={profile} content={content} footer={footer}>
<NewIdeaCard.Profile />
<NewIdeaCard.Profile onClickProfile={() => goProfilePage(idea.memberResponse.id)} />
<NewIdeaCard.Content />
<NewIdeaCard.Footer />
</NewIdeaCard>
Expand Down
4 changes: 2 additions & 2 deletions src/pages/Feed/hooks/mutations/useDeleteBookmarkIdea.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';

import { http } from '../../../../api/http';
import { memberId } from '../../../Profile/utils/memberId';
import { getUserId } from '../../../Profile/utils/getUserId';

const _deleteBookmarkIdea = (ideaId: number) => {
return http.delete(`/bookmark/${ideaId}`);
Expand All @@ -13,7 +13,7 @@ export const useDeleteBookmarkIdea = () => {
mutationFn: _deleteBookmarkIdea,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['ideas'] });
queryClient.invalidateQueries({ queryKey: ['members', 'detail', memberId, 'bookmarkedIdeas'] });
queryClient.invalidateQueries({ queryKey: ['members', 'detail', getUserId(), 'bookmarkedIdeas'] });
},
});

Expand Down
1 change: 1 addition & 0 deletions src/pages/Feed/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export type BestIdea = {

// IdeaCard 글 글쓴이 정보
type MemberResponse = {
id: number;
profileImageUrl: string;
nickname: string;
skills: string[];
Expand Down
3 changes: 2 additions & 1 deletion src/pages/FeedDetail/components/Comment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import EditComment from './EditComment';
import ModifyDropdown from './ModifyDropdown';
import Recomment from './Recomment';
import WriteRecomment from './WriteRecomment';
import { MemberSkills } from '../../Profile/types';
import { get999PlusCount } from '../../utils';
import useDeleteCommentMutation from '../hooks/mutations/useDeleteComment';
import useFocusEditComment from '../hooks/useFocusEditComment';
Expand All @@ -17,7 +18,7 @@ interface Props {
feedId: string;
myImageUrl: string;
myNickname: string;
mySkillList: string[];
mySkillList: MemberSkills[];
comment: CommentParentResponse;
}

Expand Down
3 changes: 2 additions & 1 deletion src/pages/FeedDetail/components/Comments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Fragment, useRef } from 'react';
import Comment from './Comment';
import WriteComment from './WriteComment';
import { useMemberInfoQuery } from '../../Profile/hooks/queries/useMemberInfoQuery';
import { getUserId } from '../../Profile/utils/getUserId';
import useCommentsQuery from '../hooks/queries/useCommentsQuery';
import useCommentInfiniteFetch from '../hooks/useCommentInfiniteFetch';

Expand All @@ -13,7 +14,7 @@ interface Props {

const Comments = ({ feedId }: Props) => {
const { comments, fetchNextPage } = useCommentsQuery(feedId);
const { profileImageUrl: myImageUrl, nickname: myNickname, skills: mySkillList } = useMemberInfoQuery();
const { profileImageUrl: myImageUrl, nickname: myNickname, skills: mySkillList } = useMemberInfoQuery(getUserId());

const intersectionRef = useRef(null);
useCommentInfiniteFetch(intersectionRef, fetchNextPage);
Expand Down
71 changes: 13 additions & 58 deletions src/pages/Profile/Profile.page.tsx
Original file line number Diff line number Diff line change
@@ -1,67 +1,22 @@
import styled from '@emotion/styled';
import { Header, TabLayout, theme, SVGHeaderSetting } from 'concept-be-design-system';
import { Suspense } from 'react';
import { useNavigate } from 'react-router-dom';
import { useParams } from 'react-router-dom';

import BookmarkSection from './components/BookmarkSection';
import IdeaSection from './components/IdeaSection';
import ProfileInfoSection from './components/ProfileInfoSection';
import Spinner from '../../components/Spinner/Spinner';
import Logo from '../../layouts/Logo';
import MyProfile from './components/MyProfile/MyProfile.page';
import OtherProfile from './components/OtherProfile/OtherProfile.page';
import { useMemberInfoQuery } from './hooks/queries/useMemberInfoQuery';
import { getUserId } from './utils/getUserId';

const Profile = () => {
const navigate = useNavigate();
const { id: userIdFromParams } = useParams();

return (
<ProfileContainer>
<Header main>
<Header.Item>
<Logo />
</Header.Item>
<Header.Item>
<SVGHeaderSetting onClick={() => navigate(`/profile/1/more`)} cursor="pointer" />
</Header.Item>
</Header>
const userId = userIdFromParams ?? getUserId();

<Suspense fallback={<Spinner />}>
<ProfileWrapper>
<ProfileInfoSection />
<TabLayout>
<TabLayout.Tab label="아이디어">
<TabPanelBox>
<Suspense fallback={<></>}>
<IdeaSection />
</Suspense>
</TabPanelBox>
</TabLayout.Tab>
<TabLayout.Tab label="북마크">
<TabPanelBox>
<Suspense fallback={<></>}>
<BookmarkSection />
</Suspense>
</TabPanelBox>
</TabLayout.Tab>
</TabLayout>
</ProfileWrapper>
</Suspense>
</ProfileContainer>
const memberInfo = useMemberInfoQuery(userId);

return memberInfo.isMyProfile === true ? (
<MyProfile userId={userId} memberInfo={memberInfo} />
) : (
<OtherProfile userId={userId} memberInfo={memberInfo} />
);
};

export default Profile;

const ProfileContainer = styled.div`
padding-bottom: 60px;
`;

const ProfileWrapper = styled.div`
position: relative;
`;

const TabPanelBox = styled.div`
padding: 30px 20px 60px 20px;
background-color: ${theme.color.bg1};
height: 100%;
display: flex;
flex-direction: column;
`;
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { Spacer } from 'concept-be-design-system';
import { Fragment, useRef } from 'react';

import EmptyTabContentSection from './EmptyTabContentSection';
import NewIdeaCard from '../../components/NewIdeaCard/NewIdeaCard';
import { useFeedInfiniteFetch } from '../../Feed/hooks/useFeedInfiniteFetch';
import { SVGBookOpen } from '../asset';
import { useBookmarkedIdeasQuery } from '../hooks/queries/useBookmarkedIdeasQuery';
import NewIdeaCard from '../../../components/NewIdeaCard/NewIdeaCard';
import { useFeedInfiniteFetch } from '../../../Feed/hooks/useFeedInfiniteFetch';
import useNavigatePage from '../../../hooks/useNavigatePage';
import { SVGBookOpen } from '../../asset';
import { useBookmarkedIdeasQuery } from '../../hooks/queries/useBookmarkedIdeasQuery';
import EmptyTabContentSection from '../EmptyTabContentSection';

const BookmarkSection = () => {
const { bookmarkedIdeas, fetchNextPage } = useBookmarkedIdeasQuery();
const { goProfilePage } = useNavigatePage();

const intersectionRef = useRef(null);

Expand Down Expand Up @@ -53,7 +55,7 @@ const BookmarkSection = () => {
return (
<Fragment key={idx}>
<NewIdeaCard id={idea.id} profile={profile} content={content} footer={footer}>
<NewIdeaCard.Profile />
<NewIdeaCard.Profile onClickProfile={() => goProfilePage(idea.memberResponse.id)} />
<NewIdeaCard.Content />
<NewIdeaCard.Footer />
</NewIdeaCard>
Expand Down
71 changes: 71 additions & 0 deletions src/pages/Profile/components/MyProfile/IdeaSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { Spacer } from 'concept-be-design-system';
import { Fragment, useRef } from 'react';

import { useDeleteIdea } from '../../../components/NewIdeaCard/hooks/mutations/useDeleteIdea';
import NewIdeaCard from '../../../components/NewIdeaCard/NewIdeaCard';
import { useFeedInfiniteFetch } from '../../../Feed/hooks/useFeedInfiniteFetch';
import { SVGMessageDotsCircle } from '../../asset';
import { useIdeasQuery } from '../../hooks/queries/useIdeasQuery';
import EmptyTabContentSection from '../EmptyTabContentSection';

type Props = {
userId: number;
};

const IdeaSection = ({ userId }: Props) => {
const { ideas, fetchNextPage } = useIdeasQuery(userId);

const intersectionRef = useRef(null);

useFeedInfiniteFetch(intersectionRef, fetchNextPage);

const { deleteIdea } = useDeleteIdea();
const handleDeleteIdea = (ideaId: number) => {
//TODO: #54 머지 후 Confirm 컴포넌트로 대체
if (confirm('게시글을 삭제하시겠습니까?')) deleteIdea(ideaId);
};

if (ideas.length === 0) {
return (
<EmptyTabContentSection
svg={SVGMessageDotsCircle}
textList={['작성한 글이 없어요', '재밌는 아이디어를 공유해보세요.']}
/>
);
}

return (
<>
{ideas.map((idea, idx) => {
const isMine = true;

const content = {
canEdit: isMine,
branches: idea.branches,
title: idea.title,
introduce: idea.introduce,
skillCategories: idea.skillCategories,
};
const footer = {
hitsCount: idea.hitsCount,
commentsCount: idea.commentsCount,
likesCount: idea.likesCount,
bookmarksCount: idea.bookmarksCount,
};

return (
<Fragment key={idx}>
<NewIdeaCard id={idea.id} content={content} footer={footer}>
<NewIdeaCard.Content onClickDelete={() => handleDeleteIdea(idea.id)} />
<NewIdeaCard.Footer />
</NewIdeaCard>
<Spacer size={20} />
</Fragment>
);
})}
<div ref={intersectionRef}></div>
</>
);
};

export default IdeaSection;
73 changes: 73 additions & 0 deletions src/pages/Profile/components/MyProfile/MyProfile.page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import styled from '@emotion/styled';
import { Header, TabLayout, theme, SVGHeaderSetting } from 'concept-be-design-system';
import { Suspense } from 'react';
import { useNavigate } from 'react-router-dom';

import BookmarkSection from './BookmarkSection';
import IdeaSection from './IdeaSection';
import Spinner from '../../../../components/Spinner/Spinner';
import Logo from '../../../../layouts/Logo';
import { Member } from '../../types';
import ProfileInfoSection from '../ProfileInfoSection';

type Props = {
userId: number;
memberInfo: Member;
};

const MyProfile = ({ userId, memberInfo }: Props) => {
const navigate = useNavigate();

return (
<ProfileContainer>
<Header main>
<Header.Item>
<Logo />
</Header.Item>
<Header.Item>
<SVGHeaderSetting onClick={() => navigate(`/profile/${userId}/more`)} cursor="pointer" />
</Header.Item>
</Header>

<Suspense fallback={<Spinner />}>
<ProfileWrapper>
<ProfileInfoSection memberInfo={memberInfo} />
<TabLayout height="100%">
<TabLayout.Tab label="아이디어">
<TabPanelBox>
<Suspense fallback={<></>}>
<IdeaSection userId={userId} />
</Suspense>
</TabPanelBox>
</TabLayout.Tab>

<TabLayout.Tab label="북마크">
<TabPanelBox>
<Suspense fallback={<></>}>
<BookmarkSection />
</Suspense>
</TabPanelBox>
</TabLayout.Tab>
</TabLayout>
</ProfileWrapper>
</Suspense>
</ProfileContainer>
);
};

export default MyProfile;

const ProfileContainer = styled.div`
padding-bottom: 60px;
`;

const ProfileWrapper = styled.div`
position: relative;
`;

const TabPanelBox = styled.div`
padding: 30px 20px 60px 20px;
background-color: ${theme.color.bg1};
display: flex;
flex-direction: column;
`;
Loading

0 comments on commit cffb5ec

Please sign in to comment.