Skip to content

Commit

Permalink
대기화면 구현 (#167)
Browse files Browse the repository at this point in the history
* feat: react-scan 설정

* feat: React-error-boundary 설정

* feat: react-query 설정

* feat: 존재하지 않는 방 에러 처리

* feat: 사이드바 앨범 리스트 query로 변경

* feat: 스트리밍 페이지 에러 핸들링

* fix: 마지막 노래 특정 부등호 수정

* style: 주석제거
  • Loading branch information
chaeryeon823 authored Nov 28, 2024
1 parent 2b00fdb commit 15d0841
Show file tree
Hide file tree
Showing 16 changed files with 300 additions and 136 deletions.
3 changes: 2 additions & 1 deletion client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
<head>
<meta charset="utf-8" />
<title>inear</title>
<!-- <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script> -->
<script src="https://unpkg.com/react-scan/dist/auto.global.js"></script>
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<!-- <link rel="icon" href="./public/favicon.ico" /> -->
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
Expand Down
3 changes: 3 additions & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
"@storybook/react": "^8.4.2",
"@storybook/react-vite": "^8.4.2",
"@storybook/test": "^8.4.2",
"@tanstack/eslint-plugin-query": "^5.61.4",
"@tanstack/react-query": "^5.61.5",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"@vitejs/plugin-react": "^4.3.3",
Expand All @@ -32,6 +34,7 @@
"lottie-react": "^2.4.0",
"msw": "^2.6.1",
"postcss": "^8.4.47",
"react-error-boundary": "^4.1.2",
"react-router-dom": "^6.28.0",
"storybook": "^8.4.2",
"swiper": "^11.1.15",
Expand Down
43 changes: 43 additions & 0 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Routes, Route } from 'react-router-dom';
import { MainPage } from '@/pages/MainPage';
import { StreamingPage } from '@/pages/StreamingPage';
import { AdminPage } from '@/pages/AdminPage';
import { AlbumPage } from '@/pages/AlbumPage';
import { AdminLoginPage } from '@/pages/AdminLoginPage';
import { ProtectedRoute } from '@/components/ProtectedRoute';
import { Outlet } from 'react-router-dom';
import { Sidebar } from './widgets/sidebar/ui/Sidebar';
import { GlobalBoundary } from './GlobalBoundary';

const MainLayout = () => (
<div className="flex flex-row">
<Sidebar />
<div className="flex-1">
<Outlet />
</div>
</div>
);

export function App() {
return (
<GlobalBoundary>
<Routes>
<Route element={<MainLayout />}>
<Route index element={<MainPage />} />
<Route path="/streaming/:roomId" element={<StreamingPage />} />
<Route path="/album/:albumId" element={<AlbumPage />} />
</Route>

<Route path="/admin/login" element={<AdminLoginPage />} />
<Route
path="/admin"
element={
<ProtectedRoute>
<AdminPage />
</ProtectedRoute>
}
/>
</Routes>
</GlobalBoundary>
);
}
30 changes: 30 additions & 0 deletions client/src/GlobalBoundary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { ErrorBoundary, FallbackProps } from 'react-error-boundary';
import { Suspense } from 'react';
import { useNavigate } from 'react-router-dom';

export const GlobalErrorFallback = ({
error,
resetErrorBoundary,
}: FallbackProps) => {
const navigate = useNavigate();
const navigateToMain = () => {
navigate('/');
resetErrorBoundary();
};
console.log('글로벌');
return (
<div>
<h1>에러가 발생했습니다.</h1>
<pre>{error.message}</pre>
<button onClick={navigateToMain}>메인으로 이동</button>
</div>
);
};

export const GlobalBoundary = ({ children }: { children: React.ReactNode }) => {
return (
<ErrorBoundary FallbackComponent={GlobalErrorFallback}>
<Suspense fallback={<div>로딩중...</div>}>{children}</Suspense>
</ErrorBoundary>
);
};
49 changes: 49 additions & 0 deletions client/src/NetworkBoundary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { ErrorBoundary, FallbackProps } from 'react-error-boundary';
import { Suspense } from 'react';
import { QueryErrorResetBoundary } from '@tanstack/react-query';
import { Button } from './shared/ui';

export const NetworkFallback = ({
error,
resetErrorBoundary,
}: FallbackProps) => {
console.log('네트워크');
const handleClickReset = () => {
resetErrorBoundary();
};
return (
<div className="flex flex-col h-screen justify-center items-center w-full text-grayscale-100 gap-4">
<h1>이 에러엔 슬픈 전설이 있어</h1>
<pre>{error.message}</pre>
{/* <Button message={'메인으로 이동'} onClick={handleClickReset} /> */}
</div>
);
};

export const NetworkBoundary = ({
children,
}: {
children: React.ReactNode;
}) => {
return (
<QueryErrorResetBoundary>
{({ reset }) => (
<ErrorBoundary
onReset={reset}
FallbackComponent={NetworkFallback}
onError={(error) => {
if (error?.name === 'QueryError') {
console.error('Query Error:', error);
}
}}
>
<Suspense
fallback={<div className="text-grayscale-100">로딩중...</div>}
>
{children}
</Suspense>
</ErrorBoundary>
)}
</QueryErrorResetBoundary>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export const useStreamingPlayer = (

const handleEnded = () => {
setIsLoaded(false);
if (totalSongs <= songIndex + 1) {
if (totalSongs < songIndex + 1) {
setSongIndex(0);
navigate(`/`);
return;
Expand Down
24 changes: 22 additions & 2 deletions client/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,29 @@
import { createRoot } from 'react-dom/client';
import './index.css';
import { Router } from '@/app/router';
import { App } from '@/App';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { BrowserRouter } from 'react-router-dom';

const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: 1,
refetchOnWindowFocus: false,
},
},
});

createRoot(document.getElementById('root')!).render(
<>
<Router />
<QueryClientProvider client={queryClient}>
<BrowserRouter
future={{
v7_relativeSplatPath: true,
v7_startTransition: true,
}}
>
<App />
</BrowserRouter>
</QueryClientProvider>
</>,
);
19 changes: 15 additions & 4 deletions client/src/pages/StreamingErrorPage/ui/StreamingErrorPage.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
export function StreamingErrorPage() {
import { useRouteError } from 'react-router-dom';
class APIError extends Error {
statueCode: number;
originalError: Error;

constructor(statueCode: number, message: string, originalError?: Error) {
super(message);
this.name = 'APIError';
this.statueCode = statueCode;
this.originalError = originalError || new Error(message);
}
}

export function StreamingErrorPage({ message }: { message?: string }) {
return (
<div className="flex flex-row h-screen text-grayscale-100 justify-center items-center">
방을 찾을 수 없을지도,,,
</div>
<div className="flex flex-row h-screen text-grayscale-100 justify-center items-center w-full"></div>
);
}
74 changes: 47 additions & 27 deletions client/src/pages/StreamingPage/ui/StreamingPage.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,65 @@
import { ChattingContainer } from '@/widgets/chatting';
import { Vote } from '@/widgets/vote';
import { Streaming } from '@/widgets/streaming';
import { StreamingErrorPage } from '@/pages/StreamingErrorPage';
import { useSocketStore } from '@/shared/store/useSocketStore';
import { useChatMessageStore } from '@/shared/store/useChatMessageStore';
import { useParams } from 'react-router-dom';
import { useEffect, useState } from 'react';
import { RoomResponse } from '@/entities/album/types';
import { publicAPI } from '@/shared/api/publicAPI';
import { Person } from '@/shared/icon/Person';
import { NetworkBoundary } from '@/NetworkBoundary';
import { useQuery } from '@tanstack/react-query';

function StreamingContainer() {
const { roomId } = useParams<{ roomId: string }>();
const [songIndex, setSongIndex] = useState<number>(0);

const {
data: roomInfo,
error,
isError,
} = useQuery({
queryKey: ['room', roomId],
queryFn: () => publicAPI.getRoomInfo(roomId!),
});

useEffect(() => {
if (roomInfo) {
setSongIndex(Number(roomInfo.trackOrder));
}
}, [roomInfo]);

if (isError) {
throw error;
}

if (!roomInfo) {
return null;
}

return (
<div className="w-full">
<Streaming
roomInfo={roomInfo}
songIndex={songIndex}
setSongIndex={setSongIndex}
/>
{roomInfo && <Vote songs={roomInfo.songResponseList} />}
</div>
);
}

export function StreamingPage() {
const { isConnected, connect, reset, userCount } = useSocketStore();
const { clearMessages } = useChatMessageStore();
const { roomId } = useParams<{ roomId: string }>();
const [roomInfo, setRoomInfo] = useState<RoomResponse | null>(null);
const [songIndex, setSongIndex] = useState<number>(0);

const getRoomInfo = async (roomId: string) => {
const res: RoomResponse = await publicAPI
.getRoomInfo(roomId)
.then((res) => res)
.catch((err) => console.log(err));
setRoomInfo(res);
setSongIndex(Number(res.trackOrder));
};

useEffect(() => {
// 페이지 진입 시 소켓 초기화
reset();
clearMessages();

if (roomId) {
getRoomInfo(roomId);
connect(roomId);
}

Expand All @@ -42,28 +69,21 @@ export function StreamingPage() {
};
}, [roomId]);

if (!isConnected) {
return <StreamingErrorPage />;
}

return (
<div className="flex flex-row h-screen">
{roomInfo && songIndex && (
<Streaming
roomInfo={roomInfo}
songIndex={songIndex}
setSongIndex={setSongIndex}
/>
)}
<div className="bg-grayscale-900 flex-shrink-0 w-[340px] text-grayscale-100 px-8 pt-10 pb-8 flex flex-col relative">
<div className="flex-grow min-w-[calc(100%-340px)]">
<NetworkBoundary key={roomId}>
<StreamingContainer />
</NetworkBoundary>
</div>
<div className="h-screen bg-grayscale-900 flex-shrink-0 w-[340px] text-grayscale-100 px-8 pt-10 pb-8 flex flex-col">
<div className="flex justify-between items-center mb-4">
<div className="text-2xl font-bold">채팅</div>
<div className="flex items-center gap-2">
<Person />
<span className="text-lg">{userCount}</span>
</div>
</div>
{roomInfo && <Vote songs={roomInfo.songResponseList} />}
<ChattingContainer />
</div>
</div>
Expand Down
Loading

0 comments on commit 15d0841

Please sign in to comment.