;
+
+export const Default: Story = {
+ args: {
+ date: '2025-02-25',
+ items: sampleItems,
+ },
+};
+
+export const All: Story = {
+ render: () => (
+
+ console.log('콘텐츠 1 클릭'),
+ },
+ {
+ id: '2',
+ title: '콘텐츠 2',
+ mainTag: 'Tag2',
+ subTag: 'Sub2',
+ images: [MobileCardSampleFirst, MobileCardSampleSecond],
+ onClick: () => console.log('콘텐츠 2 클릭'),
+ },
+ ]}
+ />
+ console.log('콘텐츠 3 클릭'),
+ },
+ {
+ id: '4',
+ title: '콘텐츠 4',
+ mainTag: 'Tag4',
+ subTag: 'Sub4',
+ images: [MobileCardSampleFirst, MobileCardSampleSecond],
+ onClick: () => console.log('콘텐츠 4 클릭'),
+ },
+ ]}
+ />
+
+ ),
+};
diff --git a/src/components/mobile/organisms/DateGroupedCard/DateGroupedCard.style.ts b/src/components/mobile/organisms/DateGroupedCard/DateGroupedCard.style.ts
new file mode 100644
index 00000000..bef39de6
--- /dev/null
+++ b/src/components/mobile/organisms/DateGroupedCard/DateGroupedCard.style.ts
@@ -0,0 +1,27 @@
+import { css } from '@emotion/react';
+import color from '@/styles/color';
+import typo from '@/styles/typo';
+
+export const containerStyle = css({
+ display: 'flex',
+ flexDirection: 'column',
+ width: '333px',
+ border: 'none',
+ backgroundColor: color.WT,
+});
+
+export const dateStyle = css(typo.Mobile.Main.Regular_12, {
+ color: color.GY[1],
+ marginBottom: '6px',
+});
+
+export const listStyle = css({
+ listStyle: 'none',
+ margin: 0,
+ padding: 0,
+ display: 'grid',
+ gridTemplateColumns: 'repeat(2, 1fr)',
+ gap: '8px',
+});
+
+export const listItemStyle = css({});
diff --git a/src/components/mobile/organisms/DateGroupedCard/DateGroupedCard.tsx b/src/components/mobile/organisms/DateGroupedCard/DateGroupedCard.tsx
new file mode 100644
index 00000000..a5860907
--- /dev/null
+++ b/src/components/mobile/organisms/DateGroupedCard/DateGroupedCard.tsx
@@ -0,0 +1,25 @@
+import React from 'react';
+import ContentsButton, {
+ ContentsButtonProps,
+} from '@/components/molecules/ContentsButton/ContentsButton';
+import * as S from './DateGroupedCard.style';
+
+export interface DateGroupedCardProps {
+ date: string;
+ items: ContentsButtonProps[];
+}
+
+const DateGroupedCard = ({ date, items }: DateGroupedCardProps) => (
+
+
{date}
+
+ {items.map(({ id, ...buttonProps }) => (
+ -
+
+
+ ))}
+
+
+);
+
+export default DateGroupedCard;
diff --git a/src/components/mobile/organisms/DateGroupedList/DateGroupedList.tsx b/src/components/mobile/organisms/DateGroupedList/DateGroupedList.tsx
index 618a5c1b..b6541144 100644
--- a/src/components/mobile/organisms/DateGroupedList/DateGroupedList.tsx
+++ b/src/components/mobile/organisms/DateGroupedList/DateGroupedList.tsx
@@ -5,7 +5,7 @@ import * as S from './DateGroupedList.style';
export interface DateGroupedListItem {
id: number;
title: string;
- imgUrl: string;
+ imgUrl?: string;
}
export interface DateGroupedListProps {
@@ -19,7 +19,11 @@ const DateGroupedList = ({ date, items }: DateGroupedListProps) => (
{items.map(({ id, title, imgUrl }) => (
-
-
+
))}
diff --git a/src/components/mobile/organisms/IconButtonArea/IconButtonArea.tsx b/src/components/mobile/organisms/IconButtonArea/IconButtonArea.tsx
index 6c32d3fd..2072aa9a 100644
--- a/src/components/mobile/organisms/IconButtonArea/IconButtonArea.tsx
+++ b/src/components/mobile/organisms/IconButtonArea/IconButtonArea.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { ReactElement } from 'react';
import IconButton from '@/components/mobile/molecules/IconButton/IconButton';
import {
IcMobileBookmark,
@@ -10,66 +10,78 @@ import {
IcMobileWritten,
IcMobileWrittenDF,
} from '@/assets';
+import { ButtonType, TabType } from '@/types/mypages';
import * as S from './IconButtonArea.style';
+export interface IconButtonConfig {
+ id: ButtonType;
+ label: Record;
+ activeIcon: ReactElement;
+ inactiveIcon: ReactElement;
+ showIn: TabType[];
+}
+
export interface IconButtonAreaProps {
- activeTab: 'talkPick' | 'balanceGame';
- onButtonClick: (id: string) => void;
- activeButton: string | null;
+ activeTab: TabType;
+ activeButton: ButtonType | null;
+ onButtonClick: (id: ButtonType) => void;
}
+const allButtons: IconButtonConfig[] = [
+ {
+ id: 'saved',
+ label: { talkPick: '내가 저장한', balanceGame: '내가 저장한' },
+ activeIcon: ,
+ inactiveIcon: ,
+ showIn: ['talkPick', 'balanceGame'],
+ },
+ {
+ id: 'voted',
+ label: { talkPick: '내가 투표한', balanceGame: '내가 투표한' },
+ activeIcon: ,
+ inactiveIcon: ,
+ showIn: ['talkPick', 'balanceGame'],
+ },
+ {
+ id: 'commented',
+ label: { talkPick: '내가 댓글 단', balanceGame: '' },
+ activeIcon: ,
+ inactiveIcon: ,
+ showIn: ['talkPick'],
+ },
+ {
+ id: 'created',
+ label: { talkPick: '내가 작성한', balanceGame: '내가 만든' },
+ activeIcon: ,
+ inactiveIcon: ,
+ showIn: ['talkPick', 'balanceGame'],
+ },
+];
+
const IconButtonArea = ({
activeTab,
onButtonClick,
activeButton,
}: IconButtonAreaProps) => {
- const allButtons = [
- {
- id: 'saved',
- label: { talkPick: '내가 저장한', balanceGame: '내가 저장한' },
- activeIcon: ,
- inactiveIcon: ,
- showIn: ['talkPick', 'balanceGame'],
- },
- {
- id: 'voted',
- label: { talkPick: '내가 투표한', balanceGame: '내가 투표한' },
- activeIcon: ,
- inactiveIcon: ,
- showIn: ['talkPick', 'balanceGame'],
- },
- {
- id: 'commented',
- label: { talkPick: '내가 댓글 단', balanceGame: '' },
- activeIcon: ,
- inactiveIcon: ,
- showIn: ['talkPick'],
- },
- {
- id: 'created',
- label: { talkPick: '내가 작성한', balanceGame: '내가 만든' },
- activeIcon: ,
- inactiveIcon: ,
- showIn: ['talkPick', 'balanceGame'],
- },
- ];
-
const filteredButtons = allButtons.filter(({ showIn }) =>
showIn.includes(activeTab),
);
return (
- {filteredButtons.map(({ id, label, activeIcon, inactiveIcon }) => (
- onButtonClick(id)}
- />
- ))}
+ {filteredButtons.map(({ id, label, activeIcon, inactiveIcon }) => {
+ const isActive = activeButton === id;
+ return (
+ onButtonClick(id)}
+ />
+ );
+ })}
);
};
diff --git a/src/components/mobile/organisms/ProfileInfoCard/ProfileInfoCard.stories.tsx b/src/components/mobile/organisms/ProfileInfoCard/ProfileInfoCard.stories.tsx
index 5019dde7..74b57691 100644
--- a/src/components/mobile/organisms/ProfileInfoCard/ProfileInfoCard.stories.tsx
+++ b/src/components/mobile/organisms/ProfileInfoCard/ProfileInfoCard.stories.tsx
@@ -43,6 +43,7 @@ export const Default: Story = {
bookmarkCount: 34,
menuData: [
{
+ id: 0,
label: '회원정보 수정',
onClick: () => console.log('회원정보 수정 클릭됨'),
},
@@ -62,6 +63,7 @@ export const All: Story = {
bookmarkCount={34}
menuData={[
{
+ id: 0,
label: '회원정보 수정',
onClick: () => console.log('Aycho 회원정보 수정 클릭됨'),
},
@@ -77,6 +79,7 @@ export const All: Story = {
bookmarkCount={22}
menuData={[
{
+ id: 1,
label: '회원정보 수정',
onClick: () => console.log('김안녕 회원정보 수정 클릭됨'),
},
diff --git a/src/components/mobile/organisms/ProfileInfoCard/ProfileInfoCard.tsx b/src/components/mobile/organisms/ProfileInfoCard/ProfileInfoCard.tsx
index 5de858eb..93f84f69 100644
--- a/src/components/mobile/organisms/ProfileInfoCard/ProfileInfoCard.tsx
+++ b/src/components/mobile/organisms/ProfileInfoCard/ProfileInfoCard.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { ReactNode } from 'react';
import MobileProfileImage from '@/components/mobile/atoms/MobileProfileImage/MobileProfileImage';
import LabelCountBox from '@/components/mobile/molecules/LabelCountBox/LabelCountBox';
import MenuTap from '@/components/atoms/MenuTap/MenuTap';
@@ -10,7 +10,7 @@ export interface ProfileInfoCardProps {
username: string;
postCount: number;
bookmarkCount: number;
- menuData: { label: string; onClick: () => void }[];
+ menuData: { id: number; label: ReactNode; onClick: () => void }[];
}
const ProfileInfoCard = ({
diff --git a/src/components/mobile/organisms/TalkPickSection/TalkPickSection.tsx b/src/components/mobile/organisms/TalkPickSection/TalkPickSection.tsx
index fcf97700..6dba412a 100644
--- a/src/components/mobile/organisms/TalkPickSection/TalkPickSection.tsx
+++ b/src/components/mobile/organisms/TalkPickSection/TalkPickSection.tsx
@@ -1,238 +1,241 @@
-import React, { useState } from 'react';
-import {
- AngleSmallUp,
- AngleSmallDown,
- MobileBookmarkDF,
- MobileBookmarkPR,
- MobileShare,
- PickIcon,
-} from '@/assets';
-import { useNavigate } from 'react-router-dom';
-import { TalkPickDetail } from '@/types/talk-pick';
-import { PATH } from '@/constants/path';
-import { ERROR, PROMPT } from '@/constants/message';
-import { formatDate, formatNumber } from '@/utils/formatData';
-import Button from '@/components/atoms/Button/Button';
-import IconButton from '@/components/mobile/atoms/IconButton/IconButton';
-import SummaryBox from '@/components/mobile/molecules/SummaryBox/SummaryBox';
-import ProfileIcon from '@/components/atoms/ProfileIcon/ProfileIcon';
-import ToastModal from '@/components/atoms/ToastModal/ToastModal';
-import VoteToggle from '@/components/mobile/molecules/VoteToggle/VoteToggle';
-import MenuTap, { MenuItem } from '@/components/atoms/MenuTap/MenuTap';
-import TextModal from '@/components/mobile/molecules/TextModal/TextModal';
-import ShareModal from '@/components/mobile/molecules/ShareModal/ShareModal';
-import ReportModal from '@/components/mobile/molecules/ReportModal/ReportModal';
-import { useCreateTalkPickBookmarkMutation } from '@/hooks/api/bookmark/useCreateTalkPickBookmarkMutation';
-import { useDeleteTalkPickBookmarkMutation } from '@/hooks/api/bookmark/useDeleteTalkPickBookmarkMutation';
-import { useDeleteTalkPickMutation } from '@/hooks/api/talk-pick/useDeleteTalkPickMutation';
-import useToastModal from '@/hooks/modal/useToastModal';
-import * as S from './TalkPickSection.style';
-
-export interface TalkPickProps {
- talkPick: TalkPickDetail;
- myTalkPick: boolean;
- isTodayTalkPick: boolean;
-}
-
-const TalkPickSection = ({
- talkPick,
- myTalkPick,
- isTodayTalkPick,
-}: TalkPickProps) => {
- const navigate = useNavigate();
-
- const [isExpanded, setIsExpanded] = useState(false);
- const { isVisible, modalText, showToastModal } = useToastModal();
-
- const [activeModal, setActiveModal] = useState<
- 'reportTalkPick' | 'reportText' | 'deleteText' | 'share' | 'none'
- >('none');
-
- const onCloseModal = () => {
- setActiveModal('none');
- };
-
- const { mutate: createBookmark } = useCreateTalkPickBookmarkMutation(
- talkPick?.id ?? 0,
- );
-
- const { mutate: deleteBookmark } = useDeleteTalkPickBookmarkMutation(
- talkPick?.id ?? 0,
- );
-
- const handleBookmarkClick = () => {
- if (!talkPick) return;
-
- if (myTalkPick) {
- showToastModal(ERROR.BOOKMARK.MY_TALKPICK);
- return;
- }
-
- if (talkPick.myBookmark) {
- deleteBookmark();
- } else {
- createBookmark();
- }
- };
-
- const handleContentToggle = () => {
- setIsExpanded((prev) => !prev);
- };
-
- const myTalkPickItem: MenuItem[] = [
- {
- label: '수정',
- onClick: () => {
- navigate(`/${PATH.CREATE.TALK_PICK}`, { state: { talkPick } });
- },
- },
- {
- label: '삭제',
- onClick: () => {
- setActiveModal('deleteText');
- },
- },
- ];
-
- const otherTalkPickItem: MenuItem[] = [
- {
- label: '신고',
- onClick: () => {
- setActiveModal('reportText');
- },
- },
- ];
-
- const { mutate: deleteTalkPick } = useDeleteTalkPickMutation(
- talkPick?.id ?? 0,
- );
-
- const handleDeleteButton = () => {
- deleteTalkPick();
- onCloseModal();
- };
-
- return (
-
- {isVisible && (
-
- {modalText}
-
- )}
-
- {}}
- onClose={onCloseModal}
- />
-
- {
- setActiveModal('reportTalkPick');
- }}
- onClose={onCloseModal}
- />
- {}}
- onClose={onCloseModal}
- />
-
-
-
- {isTodayTalkPick ? '오늘의 톡픽' : '톡픽'}
-
-
-
- }
- onClick={() => setActiveModal('share')}
- />
- :
- }
- onClick={handleBookmarkClick}
- />
-
-
-
-
-
-
{talkPick?.baseFields.title}
-
-
-
-
-
-
-
{talkPick?.writer}
-
•
-
- {formatDate(talkPick?.createdAt ?? '')}
-
-
-
-
- 조회 {formatNumber(talkPick?.views ?? '')}
-
-
-
-
-
- {isExpanded && (
-
-
- {talkPick?.baseFields.content}
-
- {talkPick?.imgUrls.length !== 0 && (
-
- {talkPick?.imgUrls.map((url) => (
-

- ))}
-
- )}
-
- )}
-
:
}
- css={S.contentBtnStyling}
- onClick={handleContentToggle}
- >
- {isExpanded ? '요약하기' : '전체 글 보기'}
-
-
-
-
-
-
-
- );
-};
-
-export default TalkPickSection;
+import React, { useState } from 'react';
+import {
+ AngleSmallUp,
+ AngleSmallDown,
+ MobileBookmarkDF,
+ MobileBookmarkPR,
+ MobileShare,
+ PickIcon,
+} from '@/assets';
+import { useNavigate } from 'react-router-dom';
+import { TalkPickDetail } from '@/types/talk-pick';
+import { PATH } from '@/constants/path';
+import { ERROR, PROMPT } from '@/constants/message';
+import { formatDate, formatNumber } from '@/utils/formatData';
+import Button from '@/components/atoms/Button/Button';
+import IconButton from '@/components/mobile/atoms/IconButton/IconButton';
+import SummaryBox from '@/components/mobile/molecules/SummaryBox/SummaryBox';
+import ProfileIcon from '@/components/atoms/ProfileIcon/ProfileIcon';
+import ToastModal from '@/components/atoms/ToastModal/ToastModal';
+import VoteToggle from '@/components/mobile/molecules/VoteToggle/VoteToggle';
+import MenuTap, { MenuItem } from '@/components/atoms/MenuTap/MenuTap';
+import TextModal from '@/components/mobile/molecules/TextModal/TextModal';
+import ShareModal from '@/components/mobile/molecules/ShareModal/ShareModal';
+import ReportModal from '@/components/mobile/molecules/ReportModal/ReportModal';
+import { useCreateTalkPickBookmarkMutation } from '@/hooks/api/bookmark/useCreateTalkPickBookmarkMutation';
+import { useDeleteTalkPickBookmarkMutation } from '@/hooks/api/bookmark/useDeleteTalkPickBookmarkMutation';
+import { useDeleteTalkPickMutation } from '@/hooks/api/talk-pick/useDeleteTalkPickMutation';
+import useToastModal from '@/hooks/modal/useToastModal';
+import * as S from './TalkPickSection.style';
+
+export interface TalkPickProps {
+ talkPick: TalkPickDetail;
+ myTalkPick: boolean;
+ isTodayTalkPick: boolean;
+}
+
+const TalkPickSection = ({
+ talkPick,
+ myTalkPick,
+ isTodayTalkPick,
+}: TalkPickProps) => {
+ const navigate = useNavigate();
+
+ const [isExpanded, setIsExpanded] = useState(false);
+ const { isVisible, modalText, showToastModal } = useToastModal();
+
+ const [activeModal, setActiveModal] = useState<
+ 'reportTalkPick' | 'reportText' | 'deleteText' | 'share' | 'none'
+ >('none');
+
+ const onCloseModal = () => {
+ setActiveModal('none');
+ };
+
+ const { mutate: createBookmark } = useCreateTalkPickBookmarkMutation(
+ talkPick?.id ?? 0,
+ );
+
+ const { mutate: deleteBookmark } = useDeleteTalkPickBookmarkMutation(
+ talkPick?.id ?? 0,
+ );
+
+ const handleBookmarkClick = () => {
+ if (!talkPick) return;
+
+ if (myTalkPick) {
+ showToastModal(ERROR.BOOKMARK.MY_TALKPICK);
+ return;
+ }
+
+ if (talkPick.myBookmark) {
+ deleteBookmark();
+ } else {
+ createBookmark();
+ }
+ };
+
+ const handleContentToggle = () => {
+ setIsExpanded((prev) => !prev);
+ };
+
+ const myTalkPickItem: MenuItem[] = [
+ {
+ id: 0,
+ label: '수정',
+ onClick: () => {
+ navigate(`/${PATH.CREATE.TALK_PICK}`, { state: { talkPick } });
+ },
+ },
+ {
+ id: 1,
+ label: '삭제',
+ onClick: () => {
+ setActiveModal('deleteText');
+ },
+ },
+ ];
+
+ const otherTalkPickItem: MenuItem[] = [
+ {
+ id: 0,
+ label: '신고',
+ onClick: () => {
+ setActiveModal('reportText');
+ },
+ },
+ ];
+
+ const { mutate: deleteTalkPick } = useDeleteTalkPickMutation(
+ talkPick?.id ?? 0,
+ );
+
+ const handleDeleteButton = () => {
+ deleteTalkPick();
+ onCloseModal();
+ };
+
+ return (
+
+ {isVisible && (
+
+ {modalText}
+
+ )}
+
+ {}}
+ onClose={onCloseModal}
+ />
+
+ {
+ setActiveModal('reportTalkPick');
+ }}
+ onClose={onCloseModal}
+ />
+ {}}
+ onClose={onCloseModal}
+ />
+
+
+
+ {isTodayTalkPick ? '오늘의 톡픽' : '톡픽'}
+
+
+
+ }
+ onClick={() => setActiveModal('share')}
+ />
+ :
+ }
+ onClick={handleBookmarkClick}
+ />
+
+
+
+
+
+
{talkPick?.baseFields.title}
+
+
+
+
+
+
+
{talkPick?.writer}
+
•
+
+ {formatDate(talkPick?.createdAt ?? '')}
+
+
+
+
+ 조회 {formatNumber(talkPick?.views ?? '')}
+
+
+
+
+
+ {isExpanded && (
+
+
+ {talkPick?.baseFields.content}
+
+ {talkPick?.imgUrls.length !== 0 && (
+
+ {talkPick?.imgUrls.map((url) => (
+

+ ))}
+
+ )}
+
+ )}
+
:
}
+ css={S.contentBtnStyling}
+ onClick={handleContentToggle}
+ >
+ {isExpanded ? '요약하기' : '전체 글 보기'}
+
+
+
+
+
+
+
+ );
+};
+
+export default TalkPickSection;
diff --git a/src/components/molecules/CommentItem/CommentItem.tsx b/src/components/molecules/CommentItem/CommentItem.tsx
index 5fe8d4b3..199a54d2 100644
--- a/src/components/molecules/CommentItem/CommentItem.tsx
+++ b/src/components/molecules/CommentItem/CommentItem.tsx
@@ -103,12 +103,14 @@ const CommentItem = ({
const myComment: MenuItem[] = [
{
+ id: 0,
label: '수정',
onClick: () => {
setEditCommentClicked(true);
},
},
{
+ id: 1,
label: '삭제',
onClick: () => {
setActiveModal('deleteText');
@@ -118,6 +120,7 @@ const CommentItem = ({
const reportComment: MenuItem[] = [
{
+ id: 0,
label: '신고',
onClick: () => {
setActiveModal('reportText');
diff --git a/src/components/molecules/ContentsButton/ContentsButton.tsx b/src/components/molecules/ContentsButton/ContentsButton.tsx
index 53104e03..79a8bad4 100644
--- a/src/components/molecules/ContentsButton/ContentsButton.tsx
+++ b/src/components/molecules/ContentsButton/ContentsButton.tsx
@@ -17,7 +17,7 @@ export interface ContentsButtonProps extends ComponentPropsWithRef<'button'> {
title: string;
mainTag: string;
subTag?: string;
- images: string[];
+ images?: string[];
bookmarked?: BookmarkProps['bookmarked'];
showBookmark?: boolean;
size?: 'large' | 'medium' | 'small' | 'extraSmall';
diff --git a/src/components/molecules/ReplyItem/ReplyItem.tsx b/src/components/molecules/ReplyItem/ReplyItem.tsx
index a256160f..ad012333 100644
--- a/src/components/molecules/ReplyItem/ReplyItem.tsx
+++ b/src/components/molecules/ReplyItem/ReplyItem.tsx
@@ -1,180 +1,183 @@
-import React, { useEffect, useMemo, useRef, useState } from 'react';
-import { Comment } from '@/types/comment';
-import { useMemberQuery } from '@/hooks/api/member/useMemberQuery';
-import { formatDateFromISOWithTime } from '@/utils/formatData';
-import { useCommentActions } from '@/hooks/comment/useCommentActions';
-import MenuTap, { MenuItem } from '@/components/atoms/MenuTap/MenuTap';
-import ToastModal from '@/components/atoms/ToastModal/ToastModal';
-import LikeButton from '@/components/atoms/LikeButton/LikeButton';
-import CategoryBarChip from '@/components/atoms/CategoryBarChip/CategoryBarChip';
-import TextArea from '@/components/molecules/TextArea/TextArea';
-import TextModal from '@/components/molecules/TextModal/TextModal';
-import ReportModal from '@/components/molecules/ReportModal/ReportModal';
-import useToastModal from '@/hooks/modal/useToastModal';
-import useOutsideClick from '@/hooks/common/useOutsideClick';
-import * as S from './ReplyItem.style';
-
-export interface ReplyItemProps {
- reply: Comment;
- selectedPage: number;
- talkPickWriter: string;
- parentId: number;
-}
-
-const ReplyItem = ({
- reply,
- selectedPage,
- talkPickWriter,
- parentId,
-}: ReplyItemProps) => {
- const { member } = useMemberQuery();
-
- const isMyReply = useMemo(() => {
- return reply?.nickname === member?.nickname;
- }, [reply?.nickname, member?.nickname]);
-
- const isTalkPickWriter = useMemo(() => {
- return reply?.nickname === talkPickWriter;
- }, [reply?.nickname, talkPickWriter]);
-
- const replyRef = useRef(null);
- const { isVisible, modalText, showToastModal } = useToastModal();
-
- const [editReplyClicked, setEditReplyClicked] = useState(false);
- const [editReplyText, setEditReplyText] = useState(reply.content);
-
- const [activeModal, setActiveModal] = useState<
- 'reportReply' | 'reportText' | 'deleteText' | 'none'
- >('none');
-
- const onCloseModal = () => {
- setActiveModal('none');
- };
-
- const { handleEditSubmit, handleDelete, handleLikeToggle, handleReport } =
- useCommentActions(
- reply,
- editReplyText,
- selectedPage,
- setEditReplyClicked,
- showToastModal,
- parentId,
- );
-
- useEffect(() => {
- setEditReplyText(reply.content);
- }, [reply.content]);
-
- useOutsideClick(replyRef, () => setEditReplyClicked(false));
-
- const handleDeleteReplyButton = () => {
- onCloseModal();
- handleDelete();
- };
-
- const myReply: MenuItem[] = [
- {
- label: '수정',
- onClick: () => {
- setEditReplyClicked(true);
- },
- },
- {
- label: '삭제',
- onClick: () => {
- setActiveModal('deleteText');
- },
- },
- ];
-
- const reportReply: MenuItem[] = [
- {
- label: '신고',
- onClick: () => {
- setActiveModal('reportText');
- },
- },
- ];
-
- const handleReportReplyButton = (reason: string) => {
- handleReport(reason);
- onCloseModal();
- };
-
- return (
-
- {isVisible && (
-
- {modalText}
-
- )}
-
- {
- handleDeleteReplyButton();
- }}
- onClose={onCloseModal}
- />
- {
- setActiveModal('reportReply');
- }}
- onClose={onCloseModal}
- />
- handleReportReplyButton(reason)}
- onClose={onCloseModal}
- />
-
-
-
-
-
- {isTalkPickWriter && (
- 작성자
- )}
- {reply?.nickname}
-
- {formatDateFromISOWithTime(reply?.createdAt ?? '')}
-
- {reply.edited && 수정됨}
-
- {!editReplyClicked && (
-
- )}
-
- {editReplyClicked ? (
-
-
-
- );
-};
-
-export default ReplyItem;
+import React, { useEffect, useMemo, useRef, useState } from 'react';
+import { Comment } from '@/types/comment';
+import { useMemberQuery } from '@/hooks/api/member/useMemberQuery';
+import { formatDateFromISOWithTime } from '@/utils/formatData';
+import { useCommentActions } from '@/hooks/comment/useCommentActions';
+import MenuTap, { MenuItem } from '@/components/atoms/MenuTap/MenuTap';
+import ToastModal from '@/components/atoms/ToastModal/ToastModal';
+import LikeButton from '@/components/atoms/LikeButton/LikeButton';
+import CategoryBarChip from '@/components/atoms/CategoryBarChip/CategoryBarChip';
+import TextArea from '@/components/molecules/TextArea/TextArea';
+import TextModal from '@/components/molecules/TextModal/TextModal';
+import ReportModal from '@/components/molecules/ReportModal/ReportModal';
+import useToastModal from '@/hooks/modal/useToastModal';
+import useOutsideClick from '@/hooks/common/useOutsideClick';
+import * as S from './ReplyItem.style';
+
+export interface ReplyItemProps {
+ reply: Comment;
+ selectedPage: number;
+ talkPickWriter: string;
+ parentId: number;
+}
+
+const ReplyItem = ({
+ reply,
+ selectedPage,
+ talkPickWriter,
+ parentId,
+}: ReplyItemProps) => {
+ const { member } = useMemberQuery();
+
+ const isMyReply = useMemo(() => {
+ return reply?.nickname === member?.nickname;
+ }, [reply?.nickname, member?.nickname]);
+
+ const isTalkPickWriter = useMemo(() => {
+ return reply?.nickname === talkPickWriter;
+ }, [reply?.nickname, talkPickWriter]);
+
+ const replyRef = useRef(null);
+ const { isVisible, modalText, showToastModal } = useToastModal();
+
+ const [editReplyClicked, setEditReplyClicked] = useState(false);
+ const [editReplyText, setEditReplyText] = useState(reply.content);
+
+ const [activeModal, setActiveModal] = useState<
+ 'reportReply' | 'reportText' | 'deleteText' | 'none'
+ >('none');
+
+ const onCloseModal = () => {
+ setActiveModal('none');
+ };
+
+ const { handleEditSubmit, handleDelete, handleLikeToggle, handleReport } =
+ useCommentActions(
+ reply,
+ editReplyText,
+ selectedPage,
+ setEditReplyClicked,
+ showToastModal,
+ parentId,
+ );
+
+ useEffect(() => {
+ setEditReplyText(reply.content);
+ }, [reply.content]);
+
+ useOutsideClick(replyRef, () => setEditReplyClicked(false));
+
+ const handleDeleteReplyButton = () => {
+ onCloseModal();
+ handleDelete();
+ };
+
+ const myReply: MenuItem[] = [
+ {
+ id: 0,
+ label: '수정',
+ onClick: () => {
+ setEditReplyClicked(true);
+ },
+ },
+ {
+ id: 1,
+ label: '삭제',
+ onClick: () => {
+ setActiveModal('deleteText');
+ },
+ },
+ ];
+
+ const reportReply: MenuItem[] = [
+ {
+ id: 0,
+ label: '신고',
+ onClick: () => {
+ setActiveModal('reportText');
+ },
+ },
+ ];
+
+ const handleReportReplyButton = (reason: string) => {
+ handleReport(reason);
+ onCloseModal();
+ };
+
+ return (
+
+ {isVisible && (
+
+ {modalText}
+
+ )}
+
+ {
+ handleDeleteReplyButton();
+ }}
+ onClose={onCloseModal}
+ />
+ {
+ setActiveModal('reportReply');
+ }}
+ onClose={onCloseModal}
+ />
+ handleReportReplyButton(reason)}
+ onClose={onCloseModal}
+ />
+
+
+
+
+
+ {isTalkPickWriter && (
+ 작성자
+ )}
+ {reply?.nickname}
+
+ {formatDateFromISOWithTime(reply?.createdAt ?? '')}
+
+ {reply.edited && 수정됨}
+
+ {!editReplyClicked && (
+
+ )}
+
+ {editReplyClicked ? (
+
+
+
+ );
+};
+
+export default ReplyItem;
diff --git a/src/components/organisms/BalanceGameSection/BalanceGameSection.tsx b/src/components/organisms/BalanceGameSection/BalanceGameSection.tsx
index e6675637..dfafb931 100644
--- a/src/components/organisms/BalanceGameSection/BalanceGameSection.tsx
+++ b/src/components/organisms/BalanceGameSection/BalanceGameSection.tsx
@@ -132,10 +132,12 @@ const BalanceGameSection = ({
);
const myGameItem: MenuItem[] = [
- { label: '수정', onClick: onEdit },
- { label: '삭제', onClick: onDelete },
+ { id: 0, label: '수정', onClick: onEdit },
+ { id: 1, label: '삭제', onClick: onDelete },
+ ];
+ const otherGameItem: MenuItem[] = [
+ { id: 0, label: '신고', onClick: onReport },
];
- const otherGameItem: MenuItem[] = [{ label: '신고', onClick: onReport }];
return (
diff --git a/src/components/organisms/TalkPickSection/TalkPickSection.tsx b/src/components/organisms/TalkPickSection/TalkPickSection.tsx
index 2e7cf729..d72ca7c1 100644
--- a/src/components/organisms/TalkPickSection/TalkPickSection.tsx
+++ b/src/components/organisms/TalkPickSection/TalkPickSection.tsx
@@ -1,256 +1,259 @@
-/* eslint-disable no-console */
-import React, { useState } from 'react';
-import {
- AngleSmallUp,
- AngleSmallDown,
- BookmarkRR,
- BookmarkSR,
- Share,
-} from '@/assets';
-import { useNavigate } from 'react-router-dom';
-import { TalkPickDetail } from '@/types/talk-pick';
-import { PATH } from '@/constants/path';
-import { ERROR, PROMPT, SUCCESS } from '@/constants/message';
-import { formatDate, formatNumber } from '@/utils/formatData';
-import Button from '@/components/atoms/Button/Button';
-import SummaryBox from '@/components/molecules/SummaryBox/SummaryBox';
-import ToastModal from '@/components/atoms/ToastModal/ToastModal';
-import VotePrototype from '@/components/molecules/VotePrototype/VotePrototype';
-import MenuTap, { MenuItem } from '@/components/atoms/MenuTap/MenuTap';
-import TextModal from '@/components/molecules/TextModal/TextModal';
-import ShareModal from '@/components/molecules/ShareModal/ShareModal';
-import ReportModal from '@/components/molecules/ReportModal/ReportModal';
-import { useCreateTalkPickBookmarkMutation } from '@/hooks/api/bookmark/useCreateTalkPickBookmarkMutation';
-import { useDeleteTalkPickBookmarkMutation } from '@/hooks/api/bookmark/useDeleteTalkPickBookmarkMutation';
-import { useDeleteTalkPickMutation } from '@/hooks/api/talk-pick/useDeleteTalkPickMutation';
-import useToastModal from '@/hooks/modal/useToastModal';
-import * as S from './TalkPickSection.style';
-
-export interface TalkPickProps {
- talkPick: TalkPickDetail;
- myTalkPick: boolean;
- isTodayTalkPick: boolean;
-}
-
-const TalkPickSection = ({
- talkPick,
- myTalkPick,
- isTodayTalkPick,
-}: TalkPickProps) => {
- const currentURL: string = window.location.href;
- const navigate = useNavigate();
-
- const [isExpanded, setIsExpanded] = useState
(false);
- const { isVisible, modalText, showToastModal } = useToastModal();
-
- const [activeModal, setActiveModal] = useState<
- 'reportTalkPick' | 'reportText' | 'deleteText' | 'share' | 'none'
- >('none');
-
- const onCloseModal = () => {
- setActiveModal('none');
- };
-
- const copyTalkPickLink = (link: string) => {
- navigator.clipboard
- .writeText(link)
- .then(() => {
- console.log('톡픽 링크 복사 완료!');
- })
- .catch((err) => {
- console.log(err);
- });
- };
-
- const { mutate: createBookmark } = useCreateTalkPickBookmarkMutation(
- talkPick?.id ?? 0,
- );
-
- const { mutate: deleteBookmark } = useDeleteTalkPickBookmarkMutation(
- talkPick?.id ?? 0,
- );
-
- const handleBookmarkClick = () => {
- if (!talkPick) return;
-
- if (myTalkPick) {
- showToastModal(ERROR.BOOKMARK.MY_TALKPICK);
- return;
- }
-
- if (talkPick.myBookmark) {
- deleteBookmark();
- } else {
- createBookmark();
- }
- };
-
- const handleContentToggle = () => {
- setIsExpanded((prev) => !prev);
- };
-
- const myTalkPickItem: MenuItem[] = [
- {
- label: '수정',
- onClick: () => {
- navigate(`/${PATH.CREATE.TALK_PICK}`, { state: { talkPick } });
- },
- },
- {
- label: '삭제',
- onClick: () => {
- setActiveModal('deleteText');
- },
- },
- ];
- const otherTalkPickItem: MenuItem[] = [
- {
- label: '신고',
- onClick: () => {
- setActiveModal('reportText');
- },
- },
- ];
-
- const { mutate: deleteTalkPick } = useDeleteTalkPickMutation(
- talkPick?.id ?? 0,
- );
-
- const handleDeleteButton = () => {
- deleteTalkPick();
- onCloseModal();
- };
-
- const handleCopyButton = (link: string) => {
- copyTalkPickLink(link);
- onCloseModal();
- showToastModal(SUCCESS.COPY.LINK);
- };
-
- return (
-
- {isVisible && (
-
- {modalText}
-
- )}
-
- handleCopyButton(currentURL)}
- onClose={onCloseModal}
- />
-
- {
- setActiveModal('reportTalkPick');
- }}
- onClose={onCloseModal}
- />
- {}}
- onClose={onCloseModal}
- />
-
-
{isTodayTalkPick && '오늘의 톡픽'}
-
-
-
-
{talkPick?.baseFields.title}
-
-
-
-
- {talkPick?.writer}
-
- {formatDate(talkPick?.createdAt ?? '')}
-
- {talkPick?.isEdited && (
- (수정됨)
- )}
-
-
- 조회
-
- {formatNumber(talkPick?.views ?? 0)}
-
-
-
-
-
-
- {isExpanded && (
-
-
- {talkPick?.baseFields.content}
-
- {talkPick?.imgUrls.length !== 0 && (
-
- {talkPick?.imgUrls.map((url) => (
-

- ))}
-
- )}
-
- )}
-
:
}
- css={S.contentBtnStyling}
- onClick={handleContentToggle}
- >
- {isExpanded ? '요약하기' : '전체 글 보기'}
-
-
-
-
-
-
-
- : }
- onClick={handleBookmarkClick}
- >
- {talkPick?.bookmarks}
-
- }
- css={S.shareBtnStyling}
- onClick={() => {
- setActiveModal('share');
- }}
- >
- 공유하기
-
-
-
- );
-};
-
-export default TalkPickSection;
+/* eslint-disable no-console */
+import React, { useState } from 'react';
+import {
+ AngleSmallUp,
+ AngleSmallDown,
+ BookmarkRR,
+ BookmarkSR,
+ Share,
+} from '@/assets';
+import { useNavigate } from 'react-router-dom';
+import { TalkPickDetail } from '@/types/talk-pick';
+import { PATH } from '@/constants/path';
+import { ERROR, PROMPT, SUCCESS } from '@/constants/message';
+import { formatDate, formatNumber } from '@/utils/formatData';
+import Button from '@/components/atoms/Button/Button';
+import SummaryBox from '@/components/molecules/SummaryBox/SummaryBox';
+import ToastModal from '@/components/atoms/ToastModal/ToastModal';
+import VotePrototype from '@/components/molecules/VotePrototype/VotePrototype';
+import MenuTap, { MenuItem } from '@/components/atoms/MenuTap/MenuTap';
+import TextModal from '@/components/molecules/TextModal/TextModal';
+import ShareModal from '@/components/molecules/ShareModal/ShareModal';
+import ReportModal from '@/components/molecules/ReportModal/ReportModal';
+import { useCreateTalkPickBookmarkMutation } from '@/hooks/api/bookmark/useCreateTalkPickBookmarkMutation';
+import { useDeleteTalkPickBookmarkMutation } from '@/hooks/api/bookmark/useDeleteTalkPickBookmarkMutation';
+import { useDeleteTalkPickMutation } from '@/hooks/api/talk-pick/useDeleteTalkPickMutation';
+import useToastModal from '@/hooks/modal/useToastModal';
+import * as S from './TalkPickSection.style';
+
+export interface TalkPickProps {
+ talkPick: TalkPickDetail;
+ myTalkPick: boolean;
+ isTodayTalkPick: boolean;
+}
+
+const TalkPickSection = ({
+ talkPick,
+ myTalkPick,
+ isTodayTalkPick,
+}: TalkPickProps) => {
+ const currentURL: string = window.location.href;
+ const navigate = useNavigate();
+
+ const [isExpanded, setIsExpanded] = useState(false);
+ const { isVisible, modalText, showToastModal } = useToastModal();
+
+ const [activeModal, setActiveModal] = useState<
+ 'reportTalkPick' | 'reportText' | 'deleteText' | 'share' | 'none'
+ >('none');
+
+ const onCloseModal = () => {
+ setActiveModal('none');
+ };
+
+ const copyTalkPickLink = (link: string) => {
+ navigator.clipboard
+ .writeText(link)
+ .then(() => {
+ console.log('톡픽 링크 복사 완료!');
+ })
+ .catch((err) => {
+ console.log(err);
+ });
+ };
+
+ const { mutate: createBookmark } = useCreateTalkPickBookmarkMutation(
+ talkPick?.id ?? 0,
+ );
+
+ const { mutate: deleteBookmark } = useDeleteTalkPickBookmarkMutation(
+ talkPick?.id ?? 0,
+ );
+
+ const handleBookmarkClick = () => {
+ if (!talkPick) return;
+
+ if (myTalkPick) {
+ showToastModal(ERROR.BOOKMARK.MY_TALKPICK);
+ return;
+ }
+
+ if (talkPick.myBookmark) {
+ deleteBookmark();
+ } else {
+ createBookmark();
+ }
+ };
+
+ const handleContentToggle = () => {
+ setIsExpanded((prev) => !prev);
+ };
+
+ const myTalkPickItem: MenuItem[] = [
+ {
+ id: 0,
+ label: '수정',
+ onClick: () => {
+ navigate(`/${PATH.CREATE.TALK_PICK}`, { state: { talkPick } });
+ },
+ },
+ {
+ id: 1,
+ label: '삭제',
+ onClick: () => {
+ setActiveModal('deleteText');
+ },
+ },
+ ];
+ const otherTalkPickItem: MenuItem[] = [
+ {
+ id: 0,
+ label: '신고',
+ onClick: () => {
+ setActiveModal('reportText');
+ },
+ },
+ ];
+
+ const { mutate: deleteTalkPick } = useDeleteTalkPickMutation(
+ talkPick?.id ?? 0,
+ );
+
+ const handleDeleteButton = () => {
+ deleteTalkPick();
+ onCloseModal();
+ };
+
+ const handleCopyButton = (link: string) => {
+ copyTalkPickLink(link);
+ onCloseModal();
+ showToastModal(SUCCESS.COPY.LINK);
+ };
+
+ return (
+
+ {isVisible && (
+
+ {modalText}
+
+ )}
+
+ handleCopyButton(currentURL)}
+ onClose={onCloseModal}
+ />
+
+ {
+ setActiveModal('reportTalkPick');
+ }}
+ onClose={onCloseModal}
+ />
+ {}}
+ onClose={onCloseModal}
+ />
+
+
{isTodayTalkPick && '오늘의 톡픽'}
+
+
+
+
{talkPick?.baseFields.title}
+
+
+
+
+ {talkPick?.writer}
+
+ {formatDate(talkPick?.createdAt ?? '')}
+
+ {talkPick?.isEdited && (
+ (수정됨)
+ )}
+
+
+ 조회
+
+ {formatNumber(talkPick?.views ?? 0)}
+
+
+
+
+
+
+ {isExpanded && (
+
+
+ {talkPick?.baseFields.content}
+
+ {talkPick?.imgUrls.length !== 0 && (
+
+ {talkPick?.imgUrls.map((url) => (
+

+ ))}
+
+ )}
+
+ )}
+
:
}
+ css={S.contentBtnStyling}
+ onClick={handleContentToggle}
+ >
+ {isExpanded ? '요약하기' : '전체 글 보기'}
+
+
+
+
+
+
+
+ : }
+ onClick={handleBookmarkClick}
+ >
+ {talkPick?.bookmarks}
+
+ }
+ css={S.shareBtnStyling}
+ onClick={() => {
+ setActiveModal('share');
+ }}
+ >
+ 공유하기
+
+
+
+ );
+};
+
+export default TalkPickSection;
diff --git a/src/hooks/api/mypages/useInfiniteScroll.ts b/src/hooks/api/mypages/useInfiniteScroll.ts
index d337c04a..1d55cab0 100644
--- a/src/hooks/api/mypages/useInfiniteScroll.ts
+++ b/src/hooks/api/mypages/useInfiniteScroll.ts
@@ -1,4 +1,9 @@
-import { useInfiniteQuery, InfiniteData } from '@tanstack/react-query';
+import {
+ useInfiniteQuery,
+ InfiniteData,
+ UseInfiniteQueryOptions,
+ UseInfiniteQueryResult,
+} from '@tanstack/react-query';
/**
* 2개의 제네릭:
@@ -6,32 +11,42 @@ import { useInfiniteQuery, InfiniteData } from '@tanstack/react-query';
* - TData: 최종 변환(Select) 이후에 사용할 UI 모델 타입
*/
+/**
+ * 기본 React Query 무한 쿼리 옵션에서
+ * 내부에서 이미 설정하는 필드( queryKey, initialPageParam, getNextPageParam )를 제거한 타입.
+ */
+type MyInfiniteUserOptions = Omit<
+ UseInfiniteQueryOptions,
+ 'queryKey' | 'initialPageParam' | 'getNextPageParam'
+>;
+
+/**
+ * @param queryKey - readonly string[] (예: ['gameBookmark'] as const)
+ * @param queryFn - 페이지 번호(pageParam)를 받아 API 호출 후 TQueryFnData를 반환하는 함수
+ * @param selectFn - (선택) InfiniteData를 TData로 변환하는 함수
+ * @param options - 사용자가 넘길 수 있는 옵션 (enabled, staleTime 등), 내부 필드들은 Omit됨
+ * @returns UseInfiniteQueryResult
+ */
export const useInfiniteScroll = <
TQueryFnData extends { last: boolean; number: number },
TData = TQueryFnData,
>(
- queryKey: string[],
- queryFn: ({ pageParam }: { pageParam?: number }) => Promise,
+ queryKey: readonly string[],
+ queryFn: (ctx: { pageParam?: number }) => Promise,
selectFn?: (data: InfiniteData) => TData,
-) => {
- const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } =
- useInfiniteQuery({
- queryKey,
- queryFn: async ({ pageParam = 0 }) => {
- return queryFn({ pageParam });
- },
- getNextPageParam: (lastPage) => {
- return lastPage.last ? undefined : lastPage.number + 1;
- },
- initialPageParam: 0,
- select: selectFn,
- });
-
- return {
- data,
- fetchNextPage,
- hasNextPage,
- isFetchingNextPage,
- isLoading,
- };
+ options?: MyInfiniteUserOptions,
+): UseInfiniteQueryResult => {
+ return useInfiniteQuery({
+ queryKey,
+ queryFn: async (context) => {
+ const actualPage =
+ typeof context.pageParam === 'number' ? context.pageParam : 0;
+ return queryFn({ pageParam: actualPage });
+ },
+ getNextPageParam: (lastPage) =>
+ lastPage.last ? undefined : lastPage.number + 1,
+ initialPageParam: 0,
+ select: selectFn,
+ ...options,
+ });
};
diff --git a/src/hooks/api/mypages/useMyGameBookmarksQuery.ts b/src/hooks/api/mypages/useMyGameBookmarksQuery.ts
index 2ba8e88f..ae7c49a9 100644
--- a/src/hooks/api/mypages/useMyGameBookmarksQuery.ts
+++ b/src/hooks/api/mypages/useMyGameBookmarksQuery.ts
@@ -9,31 +9,30 @@ export interface GameBookmarkTransformedPage
content: MyBalanceGameItem[];
}
-export const useMyGameBookmarksQuery = () => {
- const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } =
- useInfiniteScroll>(
- ['gameBookmark'],
- async ({ pageParam = 0 }) => {
- return getGameBookmark(pageParam, 20);
- },
- (infiniteData: InfiniteData) => {
- const newPages = infiniteData.pages.map((page) => ({
- ...page,
- content: page.content.map((item) => transformBalanceGameItem(item)),
- }));
-
- return {
- ...infiniteData,
- pages: newPages,
- };
- },
- );
-
- return {
- data,
- fetchNextPage,
- hasNextPage,
- isFetchingNextPage,
- isLoading,
- };
+export const useMyGameBookmarksQuery = (
+ options?: Parameters<
+ typeof useInfiniteScroll<
+ GameBookmark,
+ InfiniteData
+ >
+ >[3],
+) => {
+ return useInfiniteScroll<
+ GameBookmark,
+ InfiniteData
+ >(
+ ['gameBookmark'] as const,
+ async ({ pageParam = 0 }) => getGameBookmark(pageParam, 20),
+ (infiniteData: InfiniteData) => {
+ const newPages = infiniteData.pages.map((page) => ({
+ ...page,
+ content: page.content.map((item) => transformBalanceGameItem(item)),
+ }));
+ return {
+ ...infiniteData,
+ pages: newPages,
+ };
+ },
+ options,
+ );
};
diff --git a/src/hooks/api/mypages/useMyGameVotesQuery.ts b/src/hooks/api/mypages/useMyGameVotesQuery.ts
index d562ebdd..e6e2a5e2 100644
--- a/src/hooks/api/mypages/useMyGameVotesQuery.ts
+++ b/src/hooks/api/mypages/useMyGameVotesQuery.ts
@@ -8,35 +8,27 @@ export interface GameVoteTransformedPage extends Omit {
content: MyBalanceGameItem[];
}
-export const useMyGameVotesQuery = (memberId: number) => {
- const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } =
- useInfiniteScroll>(
- ['gameVote'],
- async ({ pageParam = 0 }) => {
- return getGameVote(pageParam, 20);
- },
- (infiniteData: InfiniteData) => {
- const newPages = infiniteData.pages.map((page) => ({
- ...page,
- content: page.content.map((item) =>
- transformGameVoteItem(item, memberId),
- ),
- }));
-
- const newInfiniteData: InfiniteData = {
- ...infiniteData,
- pages: newPages,
- };
-
- return newInfiniteData;
- },
- );
-
- return {
- data,
- fetchNextPage,
- hasNextPage,
- isFetchingNextPage,
- isLoading,
- };
+export const useMyGameVotesQuery = (
+ memberId: number,
+ options?: Parameters<
+ typeof useInfiniteScroll>
+ >[3],
+) => {
+ return useInfiniteScroll>(
+ ['gameVote'] as const,
+ async ({ pageParam = 0 }) => getGameVote(pageParam, 20),
+ (infiniteData: InfiniteData) => {
+ const newPages = infiniteData.pages.map((page) => ({
+ ...page,
+ content: page.content.map((item) =>
+ transformGameVoteItem(item, memberId),
+ ),
+ }));
+ return {
+ ...infiniteData,
+ pages: newPages,
+ };
+ },
+ options,
+ );
};
diff --git a/src/hooks/api/mypages/useMyGameWrittensQuery.ts b/src/hooks/api/mypages/useMyGameWrittensQuery.ts
index fdbffe1b..9bb67496 100644
--- a/src/hooks/api/mypages/useMyGameWrittensQuery.ts
+++ b/src/hooks/api/mypages/useMyGameWrittensQuery.ts
@@ -9,31 +9,30 @@ export interface GameWrittenTransformedPage
content: MyBalanceGameItem[];
}
-export const useMyGameWrittensQuery = () => {
- const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } =
- useInfiniteScroll>(
- ['gameWritten'],
- async ({ pageParam = 0 }) => {
- return getGameWritten(pageParam, 20);
- },
- (infiniteData: InfiniteData) => {
- const newPages = infiniteData.pages.map((page) => ({
- ...page,
- content: page.content.map((item) => transformGameWrittenItem(item)),
- }));
-
- return {
- ...infiniteData,
- pages: newPages,
- };
- },
- );
-
- return {
- data,
- fetchNextPage,
- hasNextPage,
- isFetchingNextPage,
- isLoading,
- };
+export const useMyGameWrittensQuery = (
+ options?: Parameters<
+ typeof useInfiniteScroll<
+ GameWritten,
+ InfiniteData
+ >
+ >[3],
+) => {
+ return useInfiniteScroll<
+ GameWritten,
+ InfiniteData
+ >(
+ ['gameWritten'] as const,
+ async ({ pageParam = 0 }) => getGameWritten(pageParam, 20),
+ (infiniteData: InfiniteData) => {
+ const newPages = infiniteData.pages.map((page) => ({
+ ...page,
+ content: page.content.map((item) => transformGameWrittenItem(item)),
+ }));
+ return {
+ ...infiniteData,
+ pages: newPages,
+ };
+ },
+ options,
+ );
};
diff --git a/src/hooks/api/mypages/useMyTalkPickBookmarksQuery.ts b/src/hooks/api/mypages/useMyTalkPickBookmarksQuery.ts
index b8413e9c..ed307918 100644
--- a/src/hooks/api/mypages/useMyTalkPickBookmarksQuery.ts
+++ b/src/hooks/api/mypages/useMyTalkPickBookmarksQuery.ts
@@ -8,41 +8,27 @@ export interface MyBookmarkTransformedPage extends Omit {
content: MyContentItem[];
}
-export const useMyTalkPickBookmarksQuery = () => {
- const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } =
- useInfiniteScroll>(
- ['myBookmarks'],
-
- async ({ pageParam = 0 }) => {
- return getMyBookmark(pageParam, 20);
- },
-
- (infiniteData) => {
- const newPages = infiniteData.pages.map((page) => {
- const transformedContent: MyContentItem[] = page.content.map(
- transformBookmarkItem,
- );
-
- return {
- ...page,
- content: transformedContent,
- };
- });
-
- const newInfiniteData: InfiniteData = {
- ...infiniteData,
- pages: newPages,
- };
-
- return newInfiniteData;
- },
- );
-
- return {
- data,
- fetchNextPage,
- hasNextPage,
- isFetchingNextPage,
- isLoading,
- };
+export const useMyTalkPickBookmarksQuery = (
+ options?: Parameters<
+ typeof useInfiniteScroll<
+ MyBookmark,
+ InfiniteData
+ >
+ >[3],
+) => {
+ return useInfiniteScroll>(
+ ['myBookmarks'] as const,
+ async ({ pageParam = 0 }) => getMyBookmark(pageParam, 20),
+ (infiniteData: InfiniteData) => {
+ const newPages = infiniteData.pages.map((page) => ({
+ ...page,
+ content: page.content.map((item) => transformBookmarkItem(item)),
+ }));
+ return {
+ ...infiniteData,
+ pages: newPages,
+ };
+ },
+ options,
+ );
};
diff --git a/src/hooks/api/mypages/useMyTalkPickCommentsQuery.ts b/src/hooks/api/mypages/useMyTalkPickCommentsQuery.ts
index 87cb1710..7092051b 100644
--- a/src/hooks/api/mypages/useMyTalkPickCommentsQuery.ts
+++ b/src/hooks/api/mypages/useMyTalkPickCommentsQuery.ts
@@ -8,33 +8,24 @@ export interface MyCommentTransformedPage extends Omit {
content: InfoItem[];
}
-export const useMyTalkPickCommentsQuery = () => {
- const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } =
- useInfiniteScroll>(
- ['myComments'],
- async ({ pageParam = 0 }) => {
- return getMyComment(pageParam, 20);
- },
- (infiniteData) => {
- const newPages = infiniteData.pages.map((page) => ({
- ...page,
- content: page.content.map((item) => transformCommentItem(item)),
- }));
-
- const newInfiniteData: InfiniteData = {
- ...infiniteData,
- pages: newPages,
- };
-
- return newInfiniteData;
- },
- );
-
- return {
- data,
- fetchNextPage,
- hasNextPage,
- isFetchingNextPage,
- isLoading,
- };
+export const useMyTalkPickCommentsQuery = (
+ options?: Parameters<
+ typeof useInfiniteScroll>
+ >[3],
+) => {
+ return useInfiniteScroll>(
+ ['myComments'] as const,
+ async ({ pageParam = 0 }) => getMyComment(pageParam, 20),
+ (infiniteData: InfiniteData) => {
+ const newPages = infiniteData.pages.map((page) => ({
+ ...page,
+ content: page.content.map((item) => transformCommentItem(item)),
+ }));
+ return {
+ ...infiniteData,
+ pages: newPages,
+ };
+ },
+ options,
+ );
};
diff --git a/src/hooks/api/mypages/useMyTalkPickVotesQuery.ts b/src/hooks/api/mypages/useMyTalkPickVotesQuery.ts
index 35722562..3e5a693f 100644
--- a/src/hooks/api/mypages/useMyTalkPickVotesQuery.ts
+++ b/src/hooks/api/mypages/useMyTalkPickVotesQuery.ts
@@ -8,33 +8,26 @@ export interface MyVoteTransformedPage extends Omit {
content: InfoItem[];
}
-export const useMyTalkPickVotesQuery = () => {
- const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } =
- useInfiniteScroll>(
- ['myVote'],
- async ({ pageParam = 0 }) => {
- return getMyVote(pageParam, 20);
- },
- (infiniteData) => {
- const newPages = infiniteData.pages.map((page) => ({
- ...page,
- content: page.content.map((item) => transformVoteItem(item)),
- }));
-
- const newInfiniteData: InfiniteData = {
- ...infiniteData,
- pages: newPages,
- };
-
- return newInfiniteData;
- },
- );
-
- return {
- data,
- fetchNextPage,
- hasNextPage,
- isFetchingNextPage,
- isLoading,
- };
+export const useMyTalkPickVotesQuery = (
+ options?: Parameters<
+ typeof useInfiniteScroll>
+ >[3],
+) => {
+ return useInfiniteScroll>(
+ ['myVote'] as const,
+ async ({ pageParam = 0 }) => {
+ return getMyVote(pageParam, 20);
+ },
+ (infiniteData: InfiniteData) => {
+ const newPages = infiniteData.pages.map((page) => ({
+ ...page,
+ content: page.content.map((item) => transformVoteItem(item)),
+ }));
+ return {
+ ...infiniteData,
+ pages: newPages,
+ };
+ },
+ options,
+ );
};
diff --git a/src/hooks/api/mypages/useMyTalkPickWrittensQuery.ts b/src/hooks/api/mypages/useMyTalkPickWrittensQuery.ts
index 30568e94..5cda1166 100644
--- a/src/hooks/api/mypages/useMyTalkPickWrittensQuery.ts
+++ b/src/hooks/api/mypages/useMyTalkPickWrittensQuery.ts
@@ -8,33 +8,26 @@ export interface MyWrittenTransformedPage extends Omit {
content: MyContentItem[];
}
-export const useMyTalkPickWrittensQuery = () => {
- const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } =
- useInfiniteScroll>(
- ['myWritten'],
- async ({ pageParam = 0 }) => {
- return getMyWritten(pageParam, 20);
- },
- (infiniteData) => {
- const newPages = infiniteData.pages.map((page) => ({
- ...page,
- content: page.content.map((item) => transformWrittenItem(item)),
- }));
-
- const newInfiniteData: InfiniteData = {
- ...infiniteData,
- pages: newPages,
- };
-
- return newInfiniteData;
- },
- );
-
- return {
- data,
- fetchNextPage,
- hasNextPage,
- isFetchingNextPage,
- isLoading,
- };
+export const useMyTalkPickWrittensQuery = (
+ options?: Parameters<
+ typeof useInfiniteScroll>
+ >[3],
+) => {
+ return useInfiniteScroll>(
+ ['myWritten'] as const,
+ async ({ pageParam = 0 }) => {
+ return getMyWritten(pageParam, 20);
+ },
+ (infiniteData: InfiniteData) => {
+ const newPages = infiniteData.pages.map((page) => ({
+ ...page,
+ content: page.content.map((item) => transformWrittenItem(item)),
+ }));
+ return {
+ ...infiniteData,
+ pages: newPages,
+ };
+ },
+ options,
+ );
};
diff --git a/src/layout/layout.tsx b/src/layout/layout.tsx
index afcbbe0d..4f1e92d2 100644
--- a/src/layout/layout.tsx
+++ b/src/layout/layout.tsx
@@ -60,10 +60,12 @@ export const LayoutNoFooter = () => {
display: 'flex',
justifyContent: 'center',
width: '100%',
+ '@media (max-width: 430px)': {
+ paddingTop: '55px',
+ },
})}
>
- {/* */}
>
diff --git a/src/pages/mobile/MyMobilePage/MyMobilePage.style.ts b/src/pages/mobile/MyMobilePage/MyMobilePage.style.ts
new file mode 100644
index 00000000..6756d7b0
--- /dev/null
+++ b/src/pages/mobile/MyMobilePage/MyMobilePage.style.ts
@@ -0,0 +1,42 @@
+import { css } from '@emotion/react';
+import color from '@/styles/color';
+import typo from '@/styles/typo';
+
+export const pageStyle = css({
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ background: color.WT,
+ padding: '18px 20px',
+ width: '100%',
+});
+
+export const selectGroupWrapper = css({
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ padding: '18px 0 16px 0',
+ width: '100%',
+});
+
+export const contentWrapper = css({
+ display: 'flex',
+ flexDirection: 'column',
+ width: '100%',
+ alignItems: 'center',
+ justifyContent: 'center',
+ padding: '18px 0',
+ gap: '10px',
+});
+
+export const menuDataBox = css(typo.Mobile.Text.Medium_12, {
+ color: color.GY[1],
+ display: 'flex',
+ alignItems: 'center',
+ gap: '12px',
+});
+
+export const observerWrapper = css({
+ display: 'flex',
+ marginTop: '2px',
+});
diff --git a/src/pages/mobile/MyMobilePage/MyMobilePage.tsx b/src/pages/mobile/MyMobilePage/MyMobilePage.tsx
new file mode 100644
index 00000000..f0820506
--- /dev/null
+++ b/src/pages/mobile/MyMobilePage/MyMobilePage.tsx
@@ -0,0 +1,271 @@
+import React, { useState, useEffect, useMemo } from 'react';
+import {
+ TabType,
+ ButtonType,
+ BookmarkInfoItemResponse,
+ CommentInfoItemResponse,
+ VoteInfoItemResponse,
+ WrittenInfoItemResponse,
+ MyBalanceGameItem,
+} from '@/types/mypages';
+import ProfileInfoCard from '@/components/mobile/organisms/ProfileInfoCard/ProfileInfoCard';
+import IconButtonArea from '@/components/mobile/organisms/IconButtonArea/IconButtonArea';
+import DateGroupedList, {
+ DateGroupedListItem,
+} from '@/components/mobile/organisms/DateGroupedList/DateGroupedList';
+import { useMyGameBookmarksQuery } from '@/hooks/api/mypages/useMyGameBookmarksQuery';
+import { useMyGameVotesQuery } from '@/hooks/api/mypages/useMyGameVotesQuery';
+import { useMyGameWrittensQuery } from '@/hooks/api/mypages/useMyGameWrittensQuery';
+import { useMyTalkPickBookmarksQuery } from '@/hooks/api/mypages/useMyTalkPickBookmarksQuery';
+import { useMyTalkPickCommentsQuery } from '@/hooks/api/mypages/useMyTalkPickCommentsQuery';
+import { useMyTalkPickVotesQuery } from '@/hooks/api/mypages/useMyTalkPickVotesQuery';
+import { useMyTalkPickWrittensQuery } from '@/hooks/api/mypages/useMyTalkPickWrittensQuery';
+import { useMemberQuery } from '@/hooks/api/member/useMemberQuery';
+import type { UseInfiniteQueryResult } from '@tanstack/react-query';
+import { useObserver } from '@/hooks/api/mypages/useObserver';
+import { ContentsButtonProps } from '@/components/molecules/ContentsButton/ContentsButton';
+import DateGroupedCard from '@/components/mobile/organisms/DateGroupedCard/DateGroupedCard';
+import SelectGroup, {
+ SelectGroupItem,
+} from '@/components/mobile/atoms/SelectGroup/SelectGroup';
+import { useNavigate } from 'react-router-dom';
+import { SmileEmoji } from '@/assets';
+import { PATH } from '@/constants/path';
+import { useBalanceGameBookmark } from '@/hooks/mypages/useBalanceGameBookmark';
+import * as S from './MyMobilePage.style';
+
+type InfiniteQueryOrNull = UseInfiniteQueryResult | null;
+
+type DateGroupedDataItem =
+ | { date: string; items: DateGroupedListItem[] }
+ | { date: string; items: ContentsButtonProps[] };
+
+const MyMobilePage = () => {
+ const navigate = useNavigate();
+
+ const [activeTab, setActiveTab] = useState('talkPick');
+ const [activeButton, setActiveButton] = useState('saved');
+ const { member, isLoading: isMemberLoading } = useMemberQuery();
+
+ const { handleBookmarkClick } = useBalanceGameBookmark();
+
+ const gameBookmarksQ = useMyGameBookmarksQuery({
+ enabled: activeTab === 'balanceGame' && activeButton === 'saved',
+ });
+ const gameVotesQ = useMyGameVotesQuery(member!.id, {
+ enabled: activeTab === 'balanceGame' && activeButton === 'voted',
+ });
+ const gameWrittensQ = useMyGameWrittensQuery({
+ enabled: activeTab === 'balanceGame' && activeButton === 'created',
+ });
+
+ const talkPickBookmarksQ = useMyTalkPickBookmarksQuery({
+ enabled: activeTab === 'talkPick' && activeButton === 'saved',
+ });
+ const talkPickCommentsQ = useMyTalkPickCommentsQuery({
+ enabled: activeTab === 'talkPick' && activeButton === 'commented',
+ });
+ const talkPickVotesQ = useMyTalkPickVotesQuery({
+ enabled: activeTab === 'talkPick' && activeButton === 'voted',
+ });
+ const talkPickWrittensQ = useMyTalkPickWrittensQuery({
+ enabled: activeTab === 'talkPick' && activeButton === 'created',
+ });
+
+ const queryMap: Record<
+ TabType,
+ Partial>>
+ > = {
+ balanceGame: {
+ saved: gameBookmarksQ,
+ voted: gameVotesQ,
+ created: gameWrittensQ,
+ },
+ talkPick: {
+ saved: talkPickBookmarksQ,
+ voted: talkPickVotesQ,
+ commented: talkPickCommentsQ,
+ created: talkPickWrittensQ,
+ },
+ };
+
+ const currentQuery =
+ activeButton !== null ? queryMap[activeTab][activeButton] : null;
+
+ const observerConfig = {
+ current: {
+ hasNextPage: currentQuery?.hasNextPage ?? false,
+ isFetchingNextPage: currentQuery?.isFetchingNextPage ?? false,
+ fetchNextPage: currentQuery?.fetchNextPage ?? (() => Promise.resolve()),
+ },
+ };
+ const { ref: infiniteRef, isFetchingAnyNextPage } =
+ useObserver(observerConfig);
+
+ const [dateGroupedData, setDateGroupedData] = useState(
+ [],
+ );
+
+ const mergedData = useMemo(() => {
+ if (!currentQuery?.data) return undefined;
+ const data = currentQuery.data as { pages: { content: unknown[] }[] };
+ return { content: data.pages.flatMap((page) => page.content) };
+ }, [currentQuery?.data]);
+
+ useEffect(() => {
+ setActiveButton('saved');
+ }, [activeTab]);
+
+ useEffect(() => {
+ if (!mergedData?.content) {
+ setDateGroupedData([]);
+ return;
+ }
+
+ if (activeTab === 'talkPick') {
+ const talkPickItems = mergedData.content as (
+ | BookmarkInfoItemResponse
+ | CommentInfoItemResponse
+ | VoteInfoItemResponse
+ | WrittenInfoItemResponse
+ )[];
+ const groups: { [date: string]: DateGroupedListItem[] } = {};
+
+ talkPickItems.forEach((item) => {
+ const dateStr = item.editedAt || '날짜없음';
+ const transformedItem: DateGroupedListItem = {
+ id: item.id,
+ title: item.title,
+ imgUrl:
+ item.imgUrls && item.imgUrls.length > 0
+ ? item.imgUrls[0]
+ : undefined,
+ };
+
+ if (groups[dateStr]) {
+ groups[dateStr].push(transformedItem);
+ } else {
+ groups[dateStr] = [transformedItem];
+ }
+ });
+
+ const groupedData = Object.keys(groups)
+ .sort((a, b) => (a < b ? 1 : -1))
+ .map((date) => ({ date, items: groups[date] }));
+
+ setDateGroupedData(groupedData);
+ } else if (activeTab === 'balanceGame') {
+ const balanceGameItems = mergedData.content as MyBalanceGameItem[];
+ const newArr: { date: string; items: ContentsButtonProps[] }[] = [];
+
+ balanceGameItems.forEach((item) => {
+ const dateStr = item.editedAt || '날짜없음';
+
+ const transformed: ContentsButtonProps = {
+ id: String(item.gameId),
+ title: item.title,
+ images:
+ item.optionAImg && item.optionBImg
+ ? [item.optionAImg, item.optionBImg]
+ : [],
+ mainTag: item.mainTagName || '',
+ subTag: item.subTag || '',
+ onClick: () => navigate(`/balancegame/${item.gameId}`),
+ onBookmarkClick: () => {
+ handleBookmarkClick(item);
+ },
+ bookmarked: item.bookmarked ?? false,
+ showBookmark: activeButton === 'saved' || activeButton === 'voted',
+ size: 'extraSmall',
+ };
+
+ const existingGroup = newArr.find((group) => group.date === dateStr);
+ if (existingGroup) {
+ existingGroup.items.push(transformed);
+ } else {
+ newArr.push({ date: dateStr, items: [transformed] });
+ }
+ });
+ setDateGroupedData(newArr);
+ }
+ }, [mergedData, activeTab, activeButton, navigate, handleBookmarkClick]);
+
+ const handleButtonClick = (buttonId: ButtonType) => {
+ setActiveButton(buttonId);
+ };
+
+ const selectGroupItems: [SelectGroupItem, SelectGroupItem] =
+ [
+ { label: '톡픽', value: 'talkPick' },
+ { label: '밸런스게임', value: 'balanceGame' },
+ ];
+
+ if (isMemberLoading) return ;
+
+ if (!currentQuery) {
+ return ;
+ }
+
+ const editMenuLabel = (
+
+ 회원정보 수정
+
+
+ );
+
+ return (
+
+
navigate(`/${PATH.CHANGE.PROFILE}`),
+ },
+ ]}
+ />
+
+
+ items={selectGroupItems}
+ selectedValue={activeTab}
+ onSelect={(value) => setActiveTab(value)}
+ />
+
+
+
+ {dateGroupedData.length > 0 ? (
+ dateGroupedData.map(({ date, items }) =>
+ activeTab === 'talkPick' ? (
+
+ ) : (
+
+ ),
+ )
+ ) : (
+
+ )}
+
+ {isFetchingAnyNextPage &&
}
+
+
+
+ );
+};
+
+export default MyMobilePage;
diff --git a/src/stories/atoms/MenuTap.stories.tsx b/src/stories/atoms/MenuTap.stories.tsx
index 80f0d4bb..78daf81a 100644
--- a/src/stories/atoms/MenuTap.stories.tsx
+++ b/src/stories/atoms/MenuTap.stories.tsx
@@ -1,97 +1,105 @@
-/* eslint-disable no-alert */
-import React from 'react';
-import MenuTap, { MenuItem } from '@/components/atoms/MenuTap/MenuTap';
-import type { Meta, StoryObj } from '@storybook/react';
-import { storyContainer, storyInnerRowContainer } from '@/stories/story.styles';
-
-const menuOne: MenuItem[] = [{ label: 'ONE' }];
-const menuOneTwo: MenuItem[] = [{ label: 'ONE' }, { label: 'TWO' }];
-
-const reportMenu: MenuItem[] = [
- {
- label: '신고',
- onClick: () => {
- alert('게시글 신고');
- },
- },
-];
-
-const myCommentMenu: MenuItem[] = [
- {
- label: '수정',
- onClick: () => {
- alert('댓글 수정');
- },
- },
- {
- label: '삭제',
- onClick: () => {
- alert('댓글 삭제');
- },
- },
-];
-
-const otherCommentMenu: MenuItem[] = [
- {
- label: '신고',
- onClick: () => {
- alert('댓글 신고');
- },
- },
- {
- label: '차단',
- onClick: () => {
- alert('댓글 차단');
- },
- },
-];
-
-const menuOptions = {
- menuOne,
- menuOneTwo,
-};
-
-const meta = {
- title: 'atoms/MenuTap',
- component: MenuTap,
- parameters: {
- layout: 'centered',
- },
- tags: ['autodocs'],
- argTypes: {
- menuData: {
- options: Object.keys(menuOptions),
- mapping: menuOptions,
- control: { type: 'radio' },
- },
- },
- args: {
- menuData: menuOptions.menuOne,
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const Default: Story = {
- args: {
- menuData: menuOne,
- },
-};
-
-export const All: Story = {
- render: () => {
- return (
-
- -
-
신고
-
- 본인 댓글
-
- 타인 댓글
-
-
-
- );
- },
-};
+/* eslint-disable no-alert */
+import React from 'react';
+import MenuTap, { MenuItem } from '@/components/atoms/MenuTap/MenuTap';
+import type { Meta, StoryObj } from '@storybook/react';
+import { storyContainer, storyInnerRowContainer } from '@/stories/story.styles';
+
+const menuOne: MenuItem[] = [{ id: 0, label: 'ONE' }];
+const menuOneTwo: MenuItem[] = [
+ { id: 0, label: 'ONE' },
+ { id: 1, label: 'TWO' },
+];
+
+const reportMenu: MenuItem[] = [
+ {
+ id: 0,
+ label: '신고',
+ onClick: () => {
+ alert('게시글 신고');
+ },
+ },
+];
+
+const myCommentMenu: MenuItem[] = [
+ {
+ id: 0,
+ label: '수정',
+ onClick: () => {
+ alert('댓글 수정');
+ },
+ },
+ {
+ id: 1,
+ label: '삭제',
+ onClick: () => {
+ alert('댓글 삭제');
+ },
+ },
+];
+
+const otherCommentMenu: MenuItem[] = [
+ {
+ id: 0,
+ label: '신고',
+ onClick: () => {
+ alert('댓글 신고');
+ },
+ },
+ {
+ id: 1,
+ label: '차단',
+ onClick: () => {
+ alert('댓글 차단');
+ },
+ },
+];
+
+const menuOptions = {
+ menuOne,
+ menuOneTwo,
+};
+
+const meta = {
+ title: 'atoms/MenuTap',
+ component: MenuTap,
+ parameters: {
+ layout: 'centered',
+ },
+ tags: ['autodocs'],
+ argTypes: {
+ menuData: {
+ options: Object.keys(menuOptions),
+ mapping: menuOptions,
+ control: { type: 'radio' },
+ },
+ },
+ args: {
+ menuData: menuOptions.menuOne,
+ },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ args: {
+ menuData: menuOne,
+ },
+};
+
+export const All: Story = {
+ render: () => {
+ return (
+
+ -
+
신고
+
+ 본인 댓글
+
+ 타인 댓글
+
+
+
+ );
+ },
+};
diff --git a/src/types/mypages.ts b/src/types/mypages.ts
index c652bedd..0d046150 100644
--- a/src/types/mypages.ts
+++ b/src/types/mypages.ts
@@ -7,6 +7,7 @@ export interface TalkPickBaseInfoItem {
commentCount: number;
editedAt: string;
bookmarked: boolean;
+ imgUrls?: string;
}
export interface MyContentItem {
@@ -17,6 +18,7 @@ export interface MyContentItem {
bookmarks: number;
showBookmark: boolean;
bookmarked: boolean;
+ imgUrls?: string;
}
export interface InfoItem {
@@ -27,6 +29,7 @@ export interface InfoItem {
content: string;
commentCount: number;
bookmarks: number;
+ imgUrls?: string;
}
export interface MyBalanceGameItem {
@@ -83,3 +86,16 @@ export interface GameVote extends PaginationType {
export interface GameBookmark extends PaginationType {
content: MyBalanceGameItem[];
}
+
+export type TabType = 'talkPick' | 'balanceGame';
+
+export type ButtonType = 'saved' | 'voted' | 'commented' | 'created';
+
+export type AllQueryData =
+ | MyWritten
+ | MyVote
+ | MyComment
+ | MyBookmark
+ | GameWritten
+ | GameVote
+ | GameBookmark;