From 6a33fd02833714d848c20707428aaca4ee9ab1ed Mon Sep 17 00:00:00 2001 From: whistleJs Date: Tue, 20 Feb 2024 00:08:31 +0900 Subject: [PATCH] =?UTF-8?q?feature-074:=20=EB=AA=A8=EB=8D=B8=EB=A7=81=20?= =?UTF-8?q?=EC=97=B0=EB=8F=99=20=EC=A7=84=ED=96=89=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/videos.ts | 11 ++- src/components/Home/SearchYoutube.tsx | 1 + .../SummaryDetailBox/SummaryDetailBox.tsx | 12 ++- .../ModelController/ModelController.tsx | 44 ++++++----- src/components/modals/RecommendationModal.tsx | 17 ++++- src/models/modeling.ts | 33 ++++++++- src/pages/SummaryPage.tsx | 73 +++++++++++++++++-- src/stores/model-controller.ts | 5 +- src/utils/date.ts | 2 +- 9 files changed, 158 insertions(+), 40 deletions(-) diff --git a/src/apis/videos.ts b/src/apis/videos.ts index 4131996..c2e5092 100644 --- a/src/apis/videos.ts +++ b/src/apis/videos.ts @@ -1,4 +1,5 @@ import { APIBaseResponse, APIResponse } from '@/models/config/axios'; +import { ModelingFinalData } from '@/models/modeling'; import { IVideo, UpdateVideoCategoryRequest, @@ -13,7 +14,7 @@ import { IVideoProps } from 'types/videos'; const PREFIX = '/videos'; -export const createVideoAPI = (data: IVideo) => { +export const createVideoAPI = (data: ModelingFinalData) => { return axios.post>(PREFIX + `/new-video`, data); }; @@ -24,6 +25,10 @@ export const getVideoAPI = ( return axios.get>(PREFIX + `/${videoId}/${versionId}`); }; +export const getDummyVideoAPI = (videoId: string | number) => { + return axios.get>(PREFIX + `/dummyVideos/${videoId}/get`); +}; + export const deleteVideos = async (videos: number[] | undefined) => { const response = await axiosInstance.delete('/videos/selectDelete', { data: { videos }, @@ -76,6 +81,10 @@ export const getUnReadDummyVideosAPI = () => { return axios.get>('/videos/dummyVideos/unRead'); }; +export const getAllDummyVideosAPI = () => { + return axios.get>('/videos/dummyVideos'); +}; + export const getUnReadDummyVideos = async (): Promise< APIResponse> > => { diff --git a/src/components/Home/SearchYoutube.tsx b/src/components/Home/SearchYoutube.tsx index 779cb9b..df48f7f 100644 --- a/src/components/Home/SearchYoutube.tsx +++ b/src/components/Home/SearchYoutube.tsx @@ -96,6 +96,7 @@ const SearchYoutube = ({ searchRef }: Props) => { setVideoLink(null); setStatus('NONE'); setProgress(0); + setModelingData(null); }; const handleClickCreateVideoButton = async () => { diff --git a/src/components/SummaryPage/SummaryDetailBox/SummaryDetailBox.tsx b/src/components/SummaryPage/SummaryDetailBox/SummaryDetailBox.tsx index 8358d15..9feb236 100644 --- a/src/components/SummaryPage/SummaryDetailBox/SummaryDetailBox.tsx +++ b/src/components/SummaryPage/SummaryDetailBox/SummaryDetailBox.tsx @@ -93,6 +93,14 @@ const SummaryDetailBox = ({ onRefresh }: Props) => { } }; + useEffect(() => { + return () => { + setSummaryVideoTime(0); + setPlaySubHeadingId(-1); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + useEffect(() => { if (player.current) return; @@ -102,10 +110,6 @@ const SummaryDetailBox = ({ onRefresh }: Props) => { window.onmessage = handleMessage; - return () => { - setSummaryVideoTime(0); - setPlaySubHeadingId(-1); - }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [summaryVideo]); diff --git a/src/components/common/ModelController/ModelController.tsx b/src/components/common/ModelController/ModelController.tsx index 90de4ea..f20dc83 100644 --- a/src/components/common/ModelController/ModelController.tsx +++ b/src/components/common/ModelController/ModelController.tsx @@ -1,11 +1,5 @@ import { useEffect, useRef } from 'react'; -import { useRecoilState, useSetRecoilState } from 'recoil'; - -import { - modelingProcess1, - modelingProcess2, - modelingProcess3, -} from '@/apis/video'; +import { useRecoilState, useSetRecoilState, useRecoilValue } from 'recoil'; import { modelingDataState, @@ -13,10 +7,18 @@ import { modelingStatusState, videoLinkState, } from '@/stores/model-controller'; +import { + modelingProcess1, + modelingProcess2, + modelingProcess3, +} from '@/apis/video'; +import { userTokenState } from '@/stores/user'; + import { createVideoAlarmAPI } from '@/apis/user'; const ModelController = () => { const interval = useRef(); + const userToken = useRecoilValue(userTokenState); const [videoLink, setVideoLink] = useRecoilState(videoLinkState); const setModelingStatus = useSetRecoilState(modelingStatusState); const setModelingProgress = useSetRecoilState(modelingProgressState); @@ -32,11 +34,13 @@ const ModelController = () => { const handleError = async () => { try { - await createVideoAlarmAPI(0, 'fail', { - title: '앗, 영상 변환 중 오류가 생겼어요', - content: '어떤 문제인지 확인해보세요!', - is_confirm: false, - }); + if (userToken) { + await createVideoAlarmAPI(0, 'fail', { + title: '앗, 영상 변환 중 오류가 생겼어요', + content: '어떤 문제인지 확인해보세요!', + is_confirm: false, + }); + } } catch (e) { console.error(e); } @@ -45,6 +49,8 @@ const ModelController = () => { clearInterval(interval.current); } + clearInterval(interval.current); + setModelingStatus('ERROR'); setVideoLink(null); }; @@ -53,6 +59,8 @@ const ModelController = () => { if (!videoLink) return; const callProcess1API = async () => { + setModelingProgress(Math.ceil(Math.random() * 5)); + try { const { videoId } = (await modelingProcess1(videoLink)).data.result; @@ -83,11 +91,14 @@ const ModelController = () => { try { const { finalData } = (await modelingProcess3({ videoId })).data.result; - if (interval.current) { - clearInterval(interval.current); - } + clearInterval(interval.current); - setModelingData(finalData); + setModelingData({ + ...finalData, + youtube_id: videoId, + created_at: new Date().toString(), + updated_at: new Date().toString(), + }); setModelingProgress(100); setModelingStatus('COMPLETE'); } catch (e) { @@ -98,7 +109,6 @@ const ModelController = () => { }; setModelingStatus('CONTINUE'); - setModelingProgress(Math.ceil(Math.random() * 5)); callProcess1API(); startInterval(); // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/src/components/modals/RecommendationModal.tsx b/src/components/modals/RecommendationModal.tsx index 209253a..21c32d5 100644 --- a/src/components/modals/RecommendationModal.tsx +++ b/src/components/modals/RecommendationModal.tsx @@ -1,8 +1,8 @@ import { useEffect, useState } from 'react'; import { Link } from 'react-router-dom'; -import { useSetRecoilState } from 'recoil'; +import { useSetRecoilState, useRecoilValue } from 'recoil'; -import { getUnReadDummyVideosAPI } from '@/apis/videos'; +import { getAllDummyVideosAPI, getUnReadDummyVideosAPI } from '@/apis/videos'; import CloseIcon from '@/assets/icons/close.svg?react'; import TransformationIcon from '@/assets/icons/transformation.svg?react'; @@ -12,10 +12,12 @@ import useOutsideClick from '@/hooks/useOutsideClick'; import { IVideo } from '@/models/video'; import { recommendationModalState } from '@/stores/modal'; +import { userTokenState } from '@/stores/user'; import { RecommendationModalContainer } from '@/styles/modals/RecommendationModal.style'; const RecommendationModal = () => { + const userToken = useRecoilValue(userTokenState); const setIsOpenModal = useSetRecoilState(recommendationModalState); const [dummyVideo, setDummyVideo] = useState(); @@ -25,10 +27,17 @@ const RecommendationModal = () => { useEffect(() => { const callAPI = async () => { + let videos: IVideo[] = []; + try { - const { videos } = (await getUnReadDummyVideosAPI()).data.result; const random = Math.round(Math.random() * (videos.length - 1)); + if (userToken) { + videos = (await getUnReadDummyVideosAPI()).data.result.videos; + } else { + videos = (await getAllDummyVideosAPI()).data.result.videos; + } + setDummyVideo(videos[random]); } catch (e) { console.error(e); @@ -36,7 +45,7 @@ const RecommendationModal = () => { }; callAPI(); - }, [setDummyVideo]); + }, [userToken, setDummyVideo]); return ( diff --git a/src/models/modeling.ts b/src/models/modeling.ts index 8c281b4..c8b16e3 100644 --- a/src/models/modeling.ts +++ b/src/models/modeling.ts @@ -1,5 +1,3 @@ -import { IVideo } from './video'; - export type ModelingStatus = | 'NONE' | 'CONTINUE' @@ -17,7 +15,36 @@ export interface ModelingProcessRequest { videoId: string; } +export interface ModelingSubHeading { + content: string; + end_time: number; + name: string; + start_time: number; +} + +export interface ModelingSummary { + content: string; +} + +export interface ModelingTag { + name: string; +} + +export interface ModelingFinalData { + description: string; + link: string; + subheading: ModelingSubHeading[]; + summary: ModelingSummary[]; + tag: ModelingTag[]; + title: string; + youtube_created_at: string; + youtube_id: string; + + created_at: string; + updated_at: string; +} + export interface ModelingResponse { - finalData: IVideo; + finalData: ModelingFinalData; message: string; } diff --git a/src/pages/SummaryPage.tsx b/src/pages/SummaryPage.tsx index d59d6b8..4abc89c 100644 --- a/src/pages/SummaryPage.tsx +++ b/src/pages/SummaryPage.tsx @@ -1,29 +1,54 @@ import { useCallback, useEffect } from 'react'; -import { useNavigate, useParams } from 'react-router-dom'; -import { useRecoilState } from 'recoil'; +import { useNavigate, useParams, useLocation } from 'react-router-dom'; +import { useRecoilState, useRecoilValue } from 'recoil'; -import { getVideoAPI } from '@/apis/videos'; +import { getDummyVideoAPI, getVideoAPI } from '@/apis/videos'; import { SummaryDetailBox } from '@/components/SummaryPage'; import { SummaryScriptBox } from '@/components/SummaryPage'; +import { IVideo } from '@/models/video'; + +import { modelingDataState } from '@/stores/model-controller'; import { summaryVideoState } from '@/stores/summary'; +import { userTokenState } from '@/stores/user'; import { Container } from '@/styles/SummaryPage'; const SummaryPage = () => { const navigate = useNavigate(); const { videoId } = useParams(); + const { search } = useLocation(); + + const userToken = useRecoilValue(userTokenState); + const [modelingData, setModelingData] = useRecoilState(modelingDataState); const [summaryVideo, setSummaryVideo] = useRecoilState(summaryVideoState); const callAPI = useCallback(async () => { if (!videoId) return; + const searchParam = new URLSearchParams(search); + const isInsight = searchParam.get('insight') === 'true'; + + let isSuccess = false; + let result: IVideo | null = null; + try { - const { isSuccess, result } = (await getVideoAPI(videoId)).data; + if (isInsight) { + const { data } = await getDummyVideoAPI(videoId); + + isSuccess = data.isSuccess; + result = data.result; + } else { + const { data } = await getVideoAPI(videoId); + + isSuccess = data.isSuccess; + result = data.result; + } if (!isSuccess) { navigate('/'); + return; } setSummaryVideo(result); @@ -31,15 +56,49 @@ const SummaryPage = () => { console.error(e); navigate('/'); } - }, [videoId, navigate, setSummaryVideo]); + }, [search, videoId, navigate, setSummaryVideo]); + + const setGuestSummaryVideo = () => { + if (!modelingData) { + navigate('/'); + return; + } + + const { subheading, tag, summary, ...others } = modelingData; + + setSummaryVideo({ + subHeading: subheading.map((item, id) => { + return { id, ...item }; + }), + tag: tag.map((item, id) => { + return { id, ...item }; + }), + summary: summary.map((item, id) => { + return { id, ...item }; + }), + video_id: 0, + image: '', + ...others, + }); + }; useEffect(() => { - callAPI(); + if (userToken) { + callAPI(); + } else { + if (videoId === 'guest') { + setGuestSummaryVideo(); + } else { + callAPI(); + } + } return () => { setSummaryVideo(null); + // setModelingData(null); }; - }, [callAPI, setSummaryVideo]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [userToken]); return ( diff --git a/src/stores/model-controller.ts b/src/stores/model-controller.ts index 4022a82..1989b8a 100644 --- a/src/stores/model-controller.ts +++ b/src/stores/model-controller.ts @@ -1,7 +1,6 @@ import { atom } from 'recoil'; -import { IVideo } from '@/models/video'; -import { ModelingStatus } from '@/models/modeling'; +import { ModelingFinalData, ModelingStatus } from '@/models/modeling'; import localStorageEffect from './effects/localStorageEffect'; @@ -20,7 +19,7 @@ export const modelingStatusState = atom({ default: 'NONE', }); -export const modelingDataState = atom({ +export const modelingDataState = atom({ key: 'modeling-data', default: null, effects_UNSTABLE: [localStorageEffect], diff --git a/src/utils/date.ts b/src/utils/date.ts index d7d55fb..828e2d6 100644 --- a/src/utils/date.ts +++ b/src/utils/date.ts @@ -43,7 +43,7 @@ export const getDate = (dateString?: string) => { export const formatTime = (time: number) => { const hour = Math.floor(time / 60 / 60); - const minute = Math.floor(time / 60); + const minute = Math.floor(time / 60) - hour * 60; const second = Math.floor(time % 60); if (hour > 0) {