|
1 | 1 | "use client"; |
2 | 2 |
|
| 3 | +import React, { useEffect } from "react"; |
| 4 | +import { useInView } from "react-intersection-observer"; |
| 5 | +import { usePosts } from "@/hooks/queries/post/usePosts"; |
| 6 | +import { usePathname, useSearchParams } from "next/navigation"; |
| 7 | +import SortSection from "@/app/components/layout/posts/SortSection"; |
| 8 | +import SearchSection from "@/app/components/layout/posts/SearchSection"; |
| 9 | +import { useUser } from "@/hooks/queries/user/me/useUser"; |
| 10 | +import Link from "next/link"; |
| 11 | +import { RiEdit2Fill } from "react-icons/ri"; |
| 12 | +import FloatingBtn from "@/app/components/button/default/FloatingBtn"; |
| 13 | +import CardBoard from "@/app/components/card/board/CardBoard"; |
| 14 | + |
| 15 | +const POSTS_PER_PAGE = 10; |
| 16 | + |
3 | 17 | export default function AlbaTalk() { |
4 | | - return <div>AlbaTalk</div>; |
| 18 | + const pathname = usePathname(); |
| 19 | + const searchParams = useSearchParams(); |
| 20 | + const { user } = useUser(); |
| 21 | + |
| 22 | + // URL ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ์์ ํค์๋์ ์ ๋ ฌ ๊ธฐ์ค ๊ฐ์ ธ์ค๊ธฐ |
| 23 | + const keyword = searchParams.get("keyword"); |
| 24 | + const orderBy = searchParams.get("orderBy"); |
| 25 | + |
| 26 | + // ๋ฌดํ ์คํฌ๋กค์ ์ํ Intersection Observer ์ค์ |
| 27 | + const { ref, inView } = useInView({ |
| 28 | + threshold: 0.1, |
| 29 | + triggerOnce: false, |
| 30 | + rootMargin: "100px", |
| 31 | + }); |
| 32 | + |
| 33 | + // ๊ฒ์๊ธ ๋ชฉ๋ก ์กฐํ |
| 34 | + const { data, isLoading, error, hasNextPage, fetchNextPage, isFetchingNextPage } = usePosts({ |
| 35 | + limit: POSTS_PER_PAGE, |
| 36 | + keyword: keyword || undefined, |
| 37 | + orderBy: orderBy || undefined, |
| 38 | + }); |
| 39 | + |
| 40 | + // ์คํฌ๋กค์ด ํ๋จ์ ๋๋ฌํ๋ฉด ๋ค์ ํ์ด์ง ๋ก๋ |
| 41 | + useEffect(() => { |
| 42 | + if (inView && hasNextPage && !isFetchingNextPage) { |
| 43 | + fetchNextPage(); |
| 44 | + } |
| 45 | + }, [inView, hasNextPage, fetchNextPage, isFetchingNextPage]); |
| 46 | + |
| 47 | + // ์๋ฌ ์ํ ์ฒ๋ฆฌ |
| 48 | + if (error) { |
| 49 | + return ( |
| 50 | + <div className="flex h-[calc(100vh-200px)] items-center justify-center"> |
| 51 | + <p className="text-red-500">๊ฒ์๊ธ ๋ชฉ๋ก์ ๋ถ๋ฌ์ค๋๋ฐ ์คํจํ์ต๋๋ค.</p> |
| 52 | + </div> |
| 53 | + ); |
| 54 | + } |
| 55 | + |
| 56 | + // ๋ก๋ฉ ์ํ ์ฒ๋ฆฌ |
| 57 | + if (isLoading) { |
| 58 | + return ( |
| 59 | + <div className="flex h-[calc(100vh-200px)] items-center justify-center"> |
| 60 | + <div>๋ก๋ฉ ์ค...</div> |
| 61 | + </div> |
| 62 | + ); |
| 63 | + } |
| 64 | + |
| 65 | + return ( |
| 66 | + <div className="flex min-h-screen flex-col items-center"> |
| 67 | + {/* ๊ฒ์ ์น์
๊ณผ ์ ๋ ฌ ์ต์
์ ๊ณ ์ ์์น๋ก ์ค์ */} |
| 68 | + <div className="fixed left-0 right-0 top-16 z-40 bg-white shadow-sm"> |
| 69 | + {/* ๊ฒ์ ์น์
*/} |
| 70 | + <div className="w-full border-b border-grayscale-100"> |
| 71 | + <div className="mx-auto flex max-w-screen-2xl flex-col gap-4 px-4 py-4 md:px-6 lg:px-8"> |
| 72 | + <div className="flex items-center justify-between"> |
| 73 | + <SearchSection /> |
| 74 | + </div> |
| 75 | + </div> |
| 76 | + </div> |
| 77 | + |
| 78 | + {/* ์ ๋ ฌ ์ต์
์น์
*/} |
| 79 | + <div className="w-full border-b border-grayscale-100"> |
| 80 | + <div className="mx-auto flex max-w-screen-2xl items-center justify-end gap-2 px-4 py-4 md:px-6 lg:px-8"> |
| 81 | + <div className="flex items-center gap-4"> |
| 82 | + <SortSection pathname={pathname} searchParams={searchParams} /> |
| 83 | + </div> |
| 84 | + </div> |
| 85 | + </div> |
| 86 | + </div> |
| 87 | + |
| 88 | + {/* ๋ฉ์ธ ์ฝํ
์ธ ์์ญ */} |
| 89 | + <div className="w-full pt-[132px]"> |
| 90 | + {/* ๊ธ์ฐ๊ธฐ ๋ฒํผ - ๊ณ ์ ์์น */} |
| 91 | + {user && ( |
| 92 | + <Link href="/albatalk/addtalk" className="fixed bottom-[50%] right-4 z-[9999] translate-y-1/2"> |
| 93 | + <FloatingBtn icon={<RiEdit2Fill className="size-6" />} variant="orange" /> |
| 94 | + </Link> |
| 95 | + )} |
| 96 | + |
| 97 | + {!data?.pages?.[0]?.data?.length ? ( |
| 98 | + <div className="flex h-[calc(100vh-200px)] flex-col items-center justify-center"> |
| 99 | + <p className="text-grayscale-500">๋ฑ๋ก๋ ๊ฒ์๊ธ์ด ์์ต๋๋ค.</p> |
| 100 | + </div> |
| 101 | + ) : ( |
| 102 | + <div className="mx-auto mt-4 w-full max-w-screen-xl px-3"> |
| 103 | + <div className="flex flex-col gap-4"> |
| 104 | + {data?.pages.map((page) => ( |
| 105 | + <React.Fragment key={page.nextCursor}> |
| 106 | + {page.data.map((post) => ( |
| 107 | + <div key={post.id} className="rounded-lg border border-grayscale-100 p-4 hover:bg-grayscale-50"> |
| 108 | + <Link href={`/albatalk/${post.id}`}> |
| 109 | + <CardBoard |
| 110 | + title={post.title} |
| 111 | + content={post.content} |
| 112 | + nickname={post.writer.nickname} |
| 113 | + updatedAt={post.updatedAt} |
| 114 | + commentCount={post.commentCount} |
| 115 | + likeCount={post.likeCount} |
| 116 | + /> |
| 117 | + </Link> |
| 118 | + </div> |
| 119 | + ))} |
| 120 | + </React.Fragment> |
| 121 | + ))} |
| 122 | + </div> |
| 123 | + |
| 124 | + {/* ๋ฌดํ ์คํฌ๋กค ํธ๋ฆฌ๊ฑฐ ์์ญ */} |
| 125 | + <div ref={ref} className="h-4 w-full"> |
| 126 | + {isFetchingNextPage && ( |
| 127 | + <div className="flex justify-center py-4"> |
| 128 | + <div className="h-6 w-6 animate-spin rounded-full border-2 border-primary-orange-300 border-t-transparent" /> |
| 129 | + </div> |
| 130 | + )} |
| 131 | + </div> |
| 132 | + </div> |
| 133 | + )} |
| 134 | + </div> |
| 135 | + </div> |
| 136 | + ); |
5 | 137 | } |
0 commit comments