diff --git a/public/none-epi.svg b/public/none-epi.svg new file mode 100644 index 00000000..4322d558 --- /dev/null +++ b/public/none-epi.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/public/spinner.svg b/public/spinner.svg new file mode 100644 index 00000000..f65e5227 --- /dev/null +++ b/public/spinner.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/apis/queries.ts b/src/apis/queries.ts index 0010c375..6ab3a217 100644 --- a/src/apis/queries.ts +++ b/src/apis/queries.ts @@ -1,8 +1,10 @@ import { createQueryKeyStore } from '@lukemorales/query-key-factory'; import { GetUserRequestType } from '@/schema/user'; import { GetMonthlyEmotionLogsRequestType } from '@/schema/emotion'; +import { GetEpigramsParamsType } from '@/schema/epigrams'; import { getMe, getUser } from './user'; import getMonthlyEmotionLogs from './emotion'; +import getEpigrams from './getEpigrams'; const quries = createQueryKeyStore({ user: { @@ -21,6 +23,12 @@ const quries = createQueryKeyStore({ queryFn: () => getMonthlyEmotionLogs(request), }), }, + epigrams: { + getEpigrams: (request: GetEpigramsParamsType) => ({ + queryKey: ['getEpigrams', request], + queryFn: () => getEpigrams(request), + }), + }, }); export default quries; diff --git a/src/hooks/useGetEpigrams.ts b/src/hooks/useGetEpigrams.ts new file mode 100644 index 00000000..4f472145 --- /dev/null +++ b/src/hooks/useGetEpigrams.ts @@ -0,0 +1,7 @@ +import quries from '@/apis/queries'; +import { GetEpigramsParamsType } from '@/schema/epigrams'; +import { useQuery } from '@tanstack/react-query'; + +const useGetEpigrams = (requset: GetEpigramsParamsType) => useQuery(quries.epigrams.getEpigrams(requset)); + +export default useGetEpigrams; diff --git a/src/pageLayout/MypageLayout/EmotionMonthlyLogs.tsx b/src/pageLayout/MypageLayout/EmotionMonthlyLogs.tsx index 88983bbd..4020e531 100644 --- a/src/pageLayout/MypageLayout/EmotionMonthlyLogs.tsx +++ b/src/pageLayout/MypageLayout/EmotionMonthlyLogs.tsx @@ -32,9 +32,9 @@ export default function EmotionMonthlyLogs({ userId }: EmotionMonthlyLogsProps) const { data: monthlyEmotionLogs = [] } = useMonthlyEmotionLogs(emotionRequest); return ( - <> +
- +
); } diff --git a/src/pageLayout/MypageLayout/MyContent.tsx b/src/pageLayout/MypageLayout/MyContent.tsx new file mode 100644 index 00000000..81c91dda --- /dev/null +++ b/src/pageLayout/MypageLayout/MyContent.tsx @@ -0,0 +1,78 @@ +import { useEffect, useState } from 'react'; +import useGetEpigrams from '@/hooks/useGetEpigrams'; +import MyEpigrams from '@/user/ui-content/MyEpigrams'; +import Image from 'next/image'; +import { useToast } from '@/components/ui/use-toast'; +import { EpigramsResponse } from '@/types/epigram.types'; +import spinner from '../../../public/spinner.svg'; + +interface MyContentProps { + userId: number; +} + +export default function MyContent({ userId }: MyContentProps) { + const limit = 3; + const [cursor, setCursor] = useState(0); + const [epigrams, setEpigrams] = useState({ totalCount: 0, nextCursor: null, list: [] }); + const [isLoadingMore, setIsLoadingMore] = useState(false); + const { toast } = useToast(); + + const epigramsRequest = { + limit, + cursor, + writerId: userId, + }; + const { data, isLoading, error } = useGetEpigrams(epigramsRequest); + + useEffect(() => { + if (data && data.list.length > 0) { + setEpigrams((prev) => ({ + totalCount: data.totalCount, + nextCursor: data.nextCursor, + list: [...prev.list, ...data.list], + })); + setIsLoadingMore(false); + } + }, [data]); + + const handleMoreEpigramLoad = () => { + if (epigrams.nextCursor !== null) { + setCursor(epigrams.nextCursor); + setIsLoadingMore(true); + } + }; + + if (isLoading && !isLoadingMore) { + return 로딩중; + } + + if (error) { + toast({ + description: error.message, + className: 'border-state-error text-state-error font-semibold', + }); + } + + return ( +
+
+ + +
+
+
+ + {isLoadingMore && ( +
+ 로딩중 +
+ )} +
+
+
+ ); +} diff --git a/src/pageLayout/MypageLayout/MyPageLayout.tsx b/src/pageLayout/MypageLayout/MyPageLayout.tsx index 7971b8d2..b011c11f 100644 --- a/src/pageLayout/MypageLayout/MyPageLayout.tsx +++ b/src/pageLayout/MypageLayout/MyPageLayout.tsx @@ -4,6 +4,7 @@ import UserInfo from '@/types/user'; import EmotionMonthlyLogs from '@/pageLayout/MypageLayout/EmotionMonthlyLogs'; import Profile from '@/user/ui-profile/Profile'; import { useRouter } from 'next/navigation'; +import MyContent from './MyContent'; export default function MyPageLayout() { const { data, isLoading, isError }: { data: UserInfo | undefined; isLoading: boolean; isError: boolean } = useMeQuery(); @@ -34,13 +35,7 @@ export default function MyPageLayout() {
-
-
-

내 에피그램(19)

-

내 댓글(110)

-
-
댓글 컴포넌트
-
+
); diff --git a/src/schema/epigrams.ts b/src/schema/epigrams.ts index 46a7cb85..2919162d 100644 --- a/src/schema/epigrams.ts +++ b/src/schema/epigrams.ts @@ -9,7 +9,7 @@ export const GetEpigramsParams = z.object({ export const GetEpigramsResponse = z.object({ totalCount: z.number(), - nextCursor: z.number(), + nextCursor: z.number().nullable(), list: z.array( z.object({ likeCount: z.number(), diff --git a/src/types/epigram.types.ts b/src/types/epigram.types.ts index 226ee9ee..0c2d0303 100644 --- a/src/types/epigram.types.ts +++ b/src/types/epigram.types.ts @@ -22,3 +22,20 @@ export interface PatchCommentRequest { isPrivate: boolean; content: string; } + +export interface Epigram { + writerId: number; + id: number; + likeCount: number; + tags: { id: number; name: string }[]; + referenceUrl: string; + referenceTitle: string; + author: string; + content: string; +} + +export interface EpigramsResponse { + totalCount: number; + nextCursor: number | null; + list: Epigram[]; +} diff --git a/src/user/ui-content/MyEpigrams.tsx b/src/user/ui-content/MyEpigrams.tsx new file mode 100644 index 00000000..f5a7d45e --- /dev/null +++ b/src/user/ui-content/MyEpigrams.tsx @@ -0,0 +1,113 @@ +import React from 'react'; +import Image from 'next/image'; +import { useRouter } from 'next/router'; +import { Button } from '@/components/ui/button'; +import { Epigram } from '@/types/epigram.types'; +import NONE_EPI from '../../../public/none-epi.svg'; + +const sizeStyles = { + xs: 'w-[286px] max-h-[132px]', + sm: 'sm:w-[312px] sm:max-h-[152px]', + md: 'md:w-[384px] md:max-h-[180px]', + lg: 'lg:w-[540px] lg:max-h-[160px]', + xl: 'xl:w-[640px] xl:max-h-[196px]', +}; + +const textSizeStyles = { + xs: 'text-xs', + sm: 'sm:text-sm', + md: 'md:text-base', + lg: 'lg:text-xl', + xl: 'xl:text-2xl', +}; + +interface MyEpigramProps { + epigrams: Epigram[]; + totalCount: number; + onMoreEpigramLoad: () => void; +} + +function MyEpigrams({ epigrams, totalCount, onMoreEpigramLoad }: MyEpigramProps) { + const router = useRouter(); + + // 에피그램 등록 페이지 이동 + const handleAddEpigram = () => { + router.push('/addEpigram'); + }; + + // 에피그램 상세 페이지 이동 + const handleEpigramClick = (epigramId: number) => { + router.push(`/epigram/${epigramId}`); + }; + + const handleKeyDown = (event: React.KeyboardEvent, epigramId: number) => { + if (event.key === 'Enter' || event.key === ' ') { + handleEpigramClick(epigramId); + } + }; + + return totalCount > 0 ? ( +
+ {epigrams.map((epigram) => ( +
handleEpigramClick(epigram.id)} + onKeyDown={(event) => handleKeyDown(event, epigram.id)} + role='button' + tabIndex={0} + className={`relative flex-col justify-start items-end gap-2 inline-flex cursor-pointer ${sizeStyles.xs} ${sizeStyles.sm} ${sizeStyles.md} ${sizeStyles.lg} ${sizeStyles.xl}`} + > +
+
+
+
+
+ {epigram.content} +
+
+ - {epigram.author} - +
+
+
+
+
+ {epigram.tags.map((tag) => ( +
+ #{tag.name} +
+ ))} +
+
+ ))} + {totalCount > epigrams.length && ( +
+ +
+ )} +
+ ) : ( +
+ 돋보기아이콘 +
+
+

아직 작성한 에피그램이 없어요!

+

에피그램을 작성하고 감정을 공유해보세요.

+
+ +
+
+ ); +} + +export default MyEpigrams;