- {channel.channelName}
+ {channel?.channelName}
{category}
- {tags.map((tag, index) => (
+ {tags?.map((tag, index) => (
))}
diff --git a/frontend/src/components/replay/ReplayView.tsx b/frontend/src/components/replay/ReplayView.tsx
index 10eaaa45..729d6c72 100644
--- a/frontend/src/components/replay/ReplayView.tsx
+++ b/frontend/src/components/replay/ReplayView.tsx
@@ -6,21 +6,20 @@ import PlayerInfo from './PlayerInfo';
import Footer from '@common/Footer';
import Header from '@common/Header';
import { useClientReplay } from '@queries/replay/useFetchReplay';
+import { getReplayURL } from '@utils/getVideoURL';
const ReplayView = () => {
const { id: videoId } = useParams();
const { data: clientReplayData } = useClientReplay({ videoId: videoId as string });
- if (!clientReplayData) {
- return 로딩 중...
;
- }
+ const { info } = clientReplayData;
return (
다시보기 페이지
-
-
+
+
);
diff --git a/frontend/src/hocs/withLiveExistCheck.tsx b/frontend/src/hocs/withLiveExistCheck.tsx
new file mode 100644
index 00000000..49c88c77
--- /dev/null
+++ b/frontend/src/hocs/withLiveExistCheck.tsx
@@ -0,0 +1,22 @@
+import { ComponentType, useEffect } from 'react';
+import { useParams, useNavigate } from 'react-router-dom';
+
+import { useCheckLiveExist } from '@apis/queries/client/useCheckLiveExist';
+
+export default function withLiveExistCheck(WrappedComponent: ComponentType
) {
+ return function WithLiveExistCheckComponent(props: P) {
+ const { id: liveId } = useParams();
+ const navigate = useNavigate();
+
+ const { data: isLiveExistData } = useCheckLiveExist({ liveId: liveId as string });
+ const isLiveExist = isLiveExistData?.existed;
+
+ useEffect(() => {
+ if (!isLiveExist) {
+ navigate('/error');
+ }
+ }, [isLiveExistData]);
+
+ return ;
+ };
+}
diff --git a/frontend/src/hocs/withReplayExistCheck.tsx b/frontend/src/hocs/withReplayExistCheck.tsx
new file mode 100644
index 00000000..fc1b0b59
--- /dev/null
+++ b/frontend/src/hocs/withReplayExistCheck.tsx
@@ -0,0 +1,22 @@
+import { ComponentType, useEffect } from 'react';
+import { useParams, useNavigate } from 'react-router-dom';
+
+import { useCheckReplayExist } from '@apis/queries/replay/useCheckReplayExist';
+
+export default function withReplayExistCheck
(WrappedComponent: ComponentType
) {
+ return function WithReplayExistCheckComponent(props: P) {
+ const { id: videoId } = useParams();
+ const navigate = useNavigate();
+
+ const { data: isReplayExistData } = useCheckReplayExist({ videoId: videoId as string });
+ const isReplayExist = isReplayExistData?.existed;
+
+ useEffect(() => {
+ if (!isReplayExist) {
+ navigate('/error');
+ }
+ }, [isReplayExistData]);
+
+ return ;
+ };
+}
diff --git a/frontend/src/pages/ClientPage.tsx b/frontend/src/pages/ClientPage.tsx
index dbf987cc..70556bc8 100644
--- a/frontend/src/pages/ClientPage.tsx
+++ b/frontend/src/pages/ClientPage.tsx
@@ -1,19 +1,29 @@
import styled from 'styled-components';
-import { ClientView, Header } from '@components/client';
+
import { ClientChatRoom } from '@components/chat';
+import { ClientView, Header } from '@components/client';
+import { AsyncBoundary } from '@components/common/AsyncBoundary';
+import { PlayerStreamError } from '@components/error';
+import withLiveExistCheck from '@hocs/withLiveExistCheck';
-export default function ClientPage() {
+function ClientPageComponent() {
return (
<>
-
-
+ >} rejectedFallback={() => }>
+
+
+
>
);
}
+const ClientPage = withLiveExistCheck(ClientPageComponent);
+
+export default ClientPage;
+
const ClientContainer = styled.div`
box-sizing: border-box;
padding-top: 70px;
diff --git a/frontend/src/pages/ErrorPage.tsx b/frontend/src/pages/ErrorPage.tsx
new file mode 100644
index 00000000..837e5908
--- /dev/null
+++ b/frontend/src/pages/ErrorPage.tsx
@@ -0,0 +1,61 @@
+import styled from 'styled-components';
+import { useNavigate } from 'react-router-dom';
+
+import WarningIcon from '@assets/icons/warning_icon.svg';
+
+const ErrorPage = () => {
+ const navigate = useNavigate();
+
+ return (
+
+
+ 존재하지 않는 방송입니다.
+ 지금 입력하신 주소의 페이지는 사라졌거나 다른 페이지로 변경되었습니다.
+ 주소를 다시 확인해주세요.
+ navigate('/')}>다른 방송 보러가기
+
+ );
+};
+
+export default ErrorPage;
+
+const WarningIconStyled = styled(WarningIcon)`
+ color: ${({ theme }) => theme.tokenColors['text-weak']};
+`;
+
+const ErrorContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ height: 100vh;
+ gap: 10px;
+`;
+
+const ErrorMainText = styled.h1`
+ color: ${({ theme }) => theme.tokenColors['text-strong']};
+ ${({ theme }) => theme.tokenTypographys['display-bold24']}
+ margin-bottom: 10px;
+`;
+
+const ErrorSubText = styled.p`
+ color: ${({ theme }) => theme.tokenColors['text-weak']};
+ ${({ theme }) => theme.tokenTypographys['body-medium16']}
+`;
+
+const HomeBox = styled.div`
+ display: flex;
+ align-items: center;
+ margin-top: 20px;
+ padding: 10px 20px;
+ justify-content: center;
+ ${({ theme }) => theme.tokenTypographys['display-bold16']};
+ background-color: ${({ theme }) => theme.colorMap.gray[900]};
+ color: ${({ theme }) => theme.tokenColors['color-white']};
+ border-radius: 20px;
+ cursor: pointer;
+
+ &:hover {
+ background-color: ${({ theme }) => theme.colorMap.gray[700]};
+ }
+`;
diff --git a/frontend/src/pages/ReplayPage.tsx b/frontend/src/pages/ReplayPage.tsx
index 4127ef9b..351a6085 100644
--- a/frontend/src/pages/ReplayPage.tsx
+++ b/frontend/src/pages/ReplayPage.tsx
@@ -1,18 +1,27 @@
import styled from 'styled-components';
+import { AsyncBoundary } from '@components/common/AsyncBoundary';
+import { PlayerStreamError } from '@components/error';
import { ReplayView, Header } from '@components/replay';
+import withReplayExistCheck from '@hocs/withReplayExistCheck';
-export default function ReplayPage() {
+function ReplayPageComponent() {
return (
<>
-
+ >} rejectedFallback={() => }>
+
+
>
);
}
+const ReplayPage = withReplayExistCheck(ReplayPageComponent);
+
+export default ReplayPage;
+
const ReplayContainer = styled.div`
box-sizing: border-box;
padding: 60px 10px 0 10px;
diff --git a/frontend/src/pages/index.ts b/frontend/src/pages/index.ts
index ce4b1c3e..bf521815 100644
--- a/frontend/src/pages/index.ts
+++ b/frontend/src/pages/index.ts
@@ -1,3 +1,5 @@
export { default as MainPage } from './MainPage';
export { default as ClientPage } from './ClientPage';
export { default as HostPage } from './HostPage';
+export { default as ReplayPage } from './ReplayPage';
+export { default as ErrorPage } from './ErrorPage';
\ No newline at end of file
diff --git a/frontend/src/type/live.ts b/frontend/src/type/live.ts
index 98117ea4..0595551d 100644
--- a/frontend/src/type/live.ts
+++ b/frontend/src/type/live.ts
@@ -27,3 +27,11 @@ export type RecentLiveResponse = {
info: RecentLive[];
appendInfo: RecentLive[];
};
+
+export type ClientLiveResponse = {
+ info: ClientLive;
+};
+
+export type LiveExistenceResponse = {
+ existed: boolean;
+};
diff --git a/frontend/src/type/replay.ts b/frontend/src/type/replay.ts
index 71320ae4..cd21c52c 100644
--- a/frontend/src/type/replay.ts
+++ b/frontend/src/type/replay.ts
@@ -21,3 +21,11 @@ export type RecentReplayResponse = {
info: ReplayStream[];
appendInfo: ReplayStream[];
};
+
+export type ClientReplayResponse = {
+ info: ReplayStream;
+};
+
+export type ReplayExistenceResponse = {
+ existed: boolean;
+};