Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/features/book/components/BookInfo.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Division } from '@/shared/components/Division'
import { Spinner } from '@/shared/ui'
import { Switch } from '@/shared/ui/Switch'

import { useBookDetail } from '../hooks'
Expand All @@ -14,7 +15,12 @@ const BookInfo = ({ bookId, isRecording, onToggleRecording }: BookInfoProps) =>
const { data, isLoading, isError } = useBookDetail(bookId)

// if (isLoading) return <BookInfoSkeleton />
if (isLoading) return <div>로딩중...</div>
if (isLoading)
return (
<div className="h-[486px] flex items-center justify-center">
<Spinner />
</div>
)
if (isError || !data) return <div>책 정보를 불러올 수 없습니다.</div>

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { ChevronLeft, ChevronRight } from 'lucide-react'
import { useRef } from 'react'

import { Spinner } from '@/shared/ui'

import { useGatheringBooks } from '../hooks/useGatheringBooks'
import EmptyState from './EmptyState'
import GatheringBookCard from './GatheringBookCard'
Expand Down Expand Up @@ -44,7 +46,9 @@ export default function GatheringBookshelfSection({ gatheringId }: GatheringBook
return (
<section className="flex flex-col gap-medium">
<h2 className="typo-heading3 text-black">모임 책장</h2>
<div className="py-8 text-center text-grey-600 typo-body3">로딩 중...</div>
<div className="flex items-center justify-center">
<Spinner />
</div>
</section>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useNavigate } from 'react-router-dom'

import { useUserProfile } from '@/features/user'
import { PAGE_SIZES, ROUTES } from '@/shared/constants'
import { Button, Pagination, Tabs, TabsList, TabsTrigger } from '@/shared/ui'
import { Button, Pagination, Spinner, Tabs, TabsList, TabsTrigger } from '@/shared/ui'

import type { GatheringUserRole, MeetingFilter } from '../gatherings.types'
import { useGatheringMeetings, useMeetingTabCounts } from '../hooks'
Expand Down Expand Up @@ -133,7 +133,9 @@ export default function GatheringMeetingSection({

{/* 약속 목록 */}
{isLoading ? (
<div className="py-xlarge text-center text-grey-600 typo-body3">로딩 중...</div>
<div className="flex items-center justify-center">
<Spinner />
</div>
) : totalCount === 0 ? (
<EmptyState type="meetings" />
) : (
Expand Down
4 changes: 1 addition & 3 deletions src/features/topics/components/ProposedTopicList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ type ProposedTopicListProps = {
hasNextPage: boolean
isFetchingNextPage: boolean
onLoadMore: () => void
pageSize?: number
gatheringId: number
meetingId: number
}
Expand All @@ -20,7 +19,6 @@ export default function ProposedTopicList({
hasNextPage,
isFetchingNextPage,
onLoadMore,
pageSize = 5,
gatheringId,
meetingId,
}: ProposedTopicListProps) {
Expand Down Expand Up @@ -58,7 +56,7 @@ export default function ProposedTopicList({
)}

{/* 무한 스크롤 로딩 상태 */}
{isFetchingNextPage && <TopicListSkeleton count={pageSize} />}
{isFetchingNextPage && <TopicListSkeleton />}

{/* 무한 스크롤 트리거 */}
{hasNextPage && !isFetchingNextPage && <div ref={observerRef} className="h-4" />}
Expand Down
13 changes: 12 additions & 1 deletion src/features/topics/components/TopicHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { format } from 'date-fns'
import { Check } from 'lucide-react'
import { useNavigate } from 'react-router-dom'

import { ROUTES } from '@/shared/constants'
import { Button } from '@/shared/ui'

type ProposedHeaderProps = {
Expand All @@ -10,6 +12,8 @@ type ProposedHeaderProps = {
confirmedTopicDate: string | null
proposedTopicsCount: number
onOpenChange: (open: boolean) => void
gatheringId: number
meetingId: number
}

type ConfirmedHeaderProps = {
Expand All @@ -22,6 +26,7 @@ type ConfirmedHeaderProps = {
type TopicHeaderProps = ProposedHeaderProps | ConfirmedHeaderProps

export default function TopicHeader(props: TopicHeaderProps) {
const navigate = useNavigate()
return (
<>
{/* 제안탭 */}
Expand Down Expand Up @@ -58,7 +63,13 @@ export default function TopicHeader(props: TopicHeaderProps) {
</Button>
)}

{props.actions.canSuggest && <Button>제안하기</Button>}
{props.actions.canSuggest && (
<Button
onClick={() => navigate(ROUTES.TOPICS_CREATE(props.gatheringId, props.meetingId))}
>
제안하기
</Button>
)}
</div>
</div>
)}
Expand Down
13 changes: 13 additions & 0 deletions src/features/topics/components/TopicSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import TopicListSkeleton from '@/features/topics/components/TopicListSkeleton'

export default function TopicSkeleton() {
return (
<div className="flex flex-col gap-base">
<div className="flex flex-col gap-tiny">
<p className="w-3/5 h-5.5 bg-grey-200 rounded animate-pulse"></p>
<p className="w-2/5 h-5 bg-grey-200 rounded animate-pulse"></p>
</div>
<TopicListSkeleton />
</div>
)
}
1 change: 1 addition & 0 deletions src/features/topics/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export { default as ProposedTopicList } from './ProposedTopicList'
export { default as TopicCard } from './TopicCard'
export { default as TopicHeader } from './TopicHeader'
export { default as TopicListSkeleton } from './TopicListSkeleton'
export { default as TopicSkeleton } from './TopicSkeleton'
7 changes: 2 additions & 5 deletions src/pages/Gatherings/GatheringDetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from '@/features/gatherings'
import { ROUTES } from '@/shared/constants'
import { useScrollCollapse } from '@/shared/hooks'
import { Spinner } from '@/shared/ui'
import { useGlobalModalStore } from '@/store/globalModalStore'

export default function GatheringDetailPage() {
Expand Down Expand Up @@ -89,11 +90,7 @@ export default function GatheringDetailPage() {

// 로딩 상태
if (isLoading || !gathering) {
return (
<div className="flex h-screen items-center justify-center">
<p className="text-grey-600 typo-subtitle2">로딩 중...</p>
</div>
)
return <Spinner height="full" />
}

return (
Expand Down
8 changes: 4 additions & 4 deletions src/pages/Gatherings/GatheringListPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
} from '@/features/gatherings'
import { ROUTES } from '@/shared/constants'
import { useInfiniteScroll } from '@/shared/hooks'
import { Button, Tabs, TabsList, TabsTrigger } from '@/shared/ui'
import { Button, Spinner, Tabs, TabsList, TabsTrigger } from '@/shared/ui'
import { useGlobalModalStore } from '@/store'

type TabValue = 'all' | 'favorites'
Expand Down Expand Up @@ -106,7 +106,7 @@ export default function GatheringListPage() {
<>
{isLoading ? (
<div className="flex h-35 items-center justify-center">
<p className="text-grey-600 typo-subtitle2">로딩 중...</p>
<Spinner />
</div>
) : gatherings.length === 0 ? (
<EmptyState type="all" />
Expand All @@ -128,7 +128,7 @@ export default function GatheringListPage() {
)}
{isFetchingNextPage && (
<div className="flex justify-center py-4">
<p className="text-grey-600 typo-body3">로딩 중...</p>
<Spinner />
</div>
)}
</>
Expand All @@ -138,7 +138,7 @@ export default function GatheringListPage() {
<>
{isFavoritesLoading ? (
<div className="flex h-35 items-center justify-center">
<p className="text-grey-600 typo-subtitle2">로딩 중...</p>
<Spinner />
</div>
) : favorites.length === 0 ? (
<EmptyState type="favorites" />
Expand Down
7 changes: 2 additions & 5 deletions src/pages/Gatherings/GatheringSettingPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
Button,
Container,
Input,
Spinner,
Tabs,
TabsContent,
TabsList,
Expand Down Expand Up @@ -179,11 +180,7 @@ export default function GatheringSettingPage() {
}

if (isLoading) {
return (
<div className="flex h-60 items-center justify-center">
<p className="typo-body1 text-grey-600">로딩 중...</p>
</div>
)
return <Spinner height="full" />
}

if (!gathering || gathering.currentUserRole !== 'LEADER') return null
Expand Down
51 changes: 24 additions & 27 deletions src/pages/Meetings/MeetingDetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,23 @@ import {
ConfirmTopicModal,
ProposedTopicList,
TopicHeader,
TopicSkeleton,
useConfirmedTopics,
useProposedTopics,
} from '@/features/topics'
import { Tabs, TabsContent, TabsList, TabsTrigger, TextButton } from '@/shared/ui'
import { Spinner, Tabs, TabsContent, TabsList, TabsTrigger, TextButton } from '@/shared/ui'

export default function MeetingDetailPage() {
const { gatheringId, meetingId } = useParams<{ gatheringId: string; meetingId: string }>()

const [activeTab, setActiveTab] = useState<TopicStatus>('PROPOSED')
const [isConfirmTopicOpen, setIsConfirmTopicOpen] = useState(false)

const { data: meeting, isLoading, error } = useMeetingDetail(Number(meetingId))
const {
data: meeting,
isLoading: meetingLoading,
error: meetingError,
} = useMeetingDetail(Number(meetingId))

// 제안된 주제 조회 (무한 스크롤)
const {
Expand Down Expand Up @@ -65,15 +70,11 @@ export default function MeetingDetailPage() {
if (confirmedError) {
alert(`확정된 주제 조회 실패: ${confirmedError.userMessage}`)
}
}, [proposedError, confirmedError])

if (error) {
return (
<div className="flex items-center justify-center h-screen">
<p className="text-error typo-body2">{error.userMessage}</p>
</div>
)
}
if (meetingError) {
alert(`약속 조회 실패: ${meetingError.userMessage}`)
}
// navigate(ROUTES.GATHERING_DETAIL(gatheringId), { replace: true })
}, [proposedError, confirmedError, meetingError])

return (
<>
Expand All @@ -88,9 +89,9 @@ export default function MeetingDetailPage() {
<div className="flex justify-between gap-[36px]">
{/* 약속 로딩 적용 */}
<div className="w-[300px] flex-none flex flex-col gap-base">
{isLoading ? (
{meetingLoading ? (
<div className="flex items-center justify-center h-[400px]">
<p className="text-grey-500 typo-body2">로딩 중...</p>
<Spinner />
</div>
) : meeting ? (
<>
Expand Down Expand Up @@ -138,11 +139,9 @@ export default function MeetingDetailPage() {
</TabsTrigger>
</TabsList>
<TabsContent value="PROPOSED">
{isProposedLoading ? (
<div className="flex items-center justify-center h-[200px]">
<p className="text-grey-500 typo-body2">로딩 중...</p>
</div>
) : proposedTopicsInfiniteData ? (
{isProposedLoading || !proposedTopicsInfiniteData ? (
<TopicSkeleton />
) : (
<div className="flex flex-col gap-base">
<TopicHeader
activeTab="PROPOSED"
Expand All @@ -151,6 +150,8 @@ export default function MeetingDetailPage() {
confirmedTopicDate={meeting?.confirmedTopicDate ?? null}
proposedTopicsCount={proposedTopicsInfiniteData.pages[0].totalCount ?? 0}
onOpenChange={setIsConfirmTopicOpen}
gatheringId={Number(gatheringId)}
meetingId={Number(meetingId)}
/>
<ProposedTopicList
topics={proposedTopicsInfiniteData.pages.flatMap(
Expand All @@ -159,20 +160,17 @@ export default function MeetingDetailPage() {
hasNextPage={hasNextProposedPage}
isFetchingNextPage={isFetchingNextProposedPage}
onLoadMore={fetchNextProposedPage}
pageSize={5}
gatheringId={Number(gatheringId)}
meetingId={Number(meetingId)}
/>
</div>
) : null}
)}
</TabsContent>

<TabsContent value="CONFIRMED">
{isConfirmedLoading ? (
<div className="flex items-center justify-center h-[200px]">
<p className="text-grey-500 typo-body2">로딩 중...</p>
</div>
) : confirmedTopicsInfiniteData ? (
{isConfirmedLoading || !confirmedTopicsInfiniteData ? (
<TopicSkeleton />
) : (
<div className="flex flex-col gap-base">
<TopicHeader
activeTab="CONFIRMED"
Expand All @@ -187,10 +185,9 @@ export default function MeetingDetailPage() {
hasNextPage={hasNextConfirmedPage}
isFetchingNextPage={isFetchingNextConfirmedPage}
onLoadMore={fetchNextConfirmedPage}
pageSize={5}
/>
</div>
) : null}
)}
</TabsContent>
</Tabs>
</div>
Expand Down
3 changes: 2 additions & 1 deletion src/pages/PreOpinions/PreOpinionListPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
usePreOpinionAnswers,
} from '@/features/preOpinions'
import SubPageHeader from '@/shared/components/SubPageHeader'
import { Spinner } from '@/shared/ui'
import { useGlobalModalStore } from '@/store'

export default function PreOpinionListPage() {
Expand Down Expand Up @@ -52,7 +53,7 @@ export default function PreOpinionListPage() {

const selectedMember = data?.members.find((m) => m.memberInfo.userId === activeMemberId)

if (isLoading) return <div>로딩중...</div>
if (isLoading) return <Spinner height="full" />

return (
<>
Expand Down
4 changes: 2 additions & 2 deletions src/routes/PrivateRoute.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Navigate, Outlet, useLocation } from 'react-router-dom'
import { ApiError } from '@/api'
import { useAuth } from '@/features/auth'
import { ROUTES } from '@/shared/constants'
import { ErrorFallback } from '@/shared/ui'
import { ErrorFallback, Spinner } from '@/shared/ui'

export function PrivateRoute() {
const { data: user, isLoading, isError, error, refetch } = useAuth()
Expand All @@ -14,7 +14,7 @@ export function PrivateRoute() {
if (isLoading) {
return (
<div className="flex min-h-screen items-center justify-center">
<p>Loading...</p>
<Spinner />
</div>
)
}
Expand Down
Loading
Loading