diff --git a/index.html b/index.html index 8769ef3..5b45a52 100644 --- a/index.html +++ b/index.html @@ -17,6 +17,11 @@
- + + diff --git a/package.json b/package.json index 0a19650..70b7584 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@types/jest": "^29.5.11", "@types/node": "^20.11.17", "@types/react-modal": "^3.16.3", + "@types/youtube": "^0.0.50", "axios": "^1.6.4", "lodash": "^4.17.21", "react": "^18.2.0", diff --git a/src/apis/user.ts b/src/apis/user.ts index 3f17706..3c06989 100644 --- a/src/apis/user.ts +++ b/src/apis/user.ts @@ -13,7 +13,9 @@ import { NickNameResponse, FindEmailResponse, FindEmailRequest, - CreateVideoAlarmRequest, + FindPasswordResponse, + FindPasswordRequest, + CreateVideoAlarmRequest } from '@/models/user'; import { AlarmResponse, @@ -91,4 +93,11 @@ export const createVideoAlarmAPI = ( PREFIX + `/videoAlarm/${videoId}/${status}`, data, ); -}; +} + +export const findPasswordAPI = (data : FindPasswordRequest) => { + return axios.post( + PREFIX + '/findPassword', + data + ); +} diff --git a/src/assets/icons/pause.svg b/src/assets/icons/pause.svg new file mode 100644 index 0000000..d30f950 --- /dev/null +++ b/src/assets/icons/pause.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/success-mail.png b/src/assets/success-mail.png new file mode 100644 index 0000000..e82cc8f Binary files /dev/null and b/src/assets/success-mail.png differ diff --git a/src/components/FindPassword.tsx b/src/components/FindPassword.tsx new file mode 100644 index 0000000..be12638 --- /dev/null +++ b/src/components/FindPassword.tsx @@ -0,0 +1,32 @@ +import React from "react" +import MailPng from '@/assets/mail.png'; +import Container from "@/styles/FindEmail"; +import { useNavigate } from "react-router-dom"; + +interface FindEmailProp { + email : string; +} + +const FindPassword : React.FC = ({email}) => { + const navigate = useNavigate(); + + const handleLoginBtn = () => { + navigate('/sign-in'); + } + return( + +
+
+ +
{email}로
회원님의 임시 비밀번호를 전송하였습니다!
+ 로그인하고 나만의 영상 아카이빙을 시작해요 +
+
+ +
+
+
+ ); +} + +export default FindPassword; \ No newline at end of file diff --git a/src/components/Home/SearchYoutube.tsx b/src/components/Home/SearchYoutube.tsx index 0ef4b36..779cb9b 100644 --- a/src/components/Home/SearchYoutube.tsx +++ b/src/components/Home/SearchYoutube.tsx @@ -91,6 +91,13 @@ const SearchYoutube = ({ searchRef }: Props) => { } }; + const handleChangeInput: React.ChangeEventHandler = (e) => { + setInputLink(e.target.value); + setVideoLink(null); + setStatus('NONE'); + setProgress(0); + }; + const handleClickCreateVideoButton = async () => { if (!modelingData) return; @@ -153,7 +160,7 @@ const SearchYoutube = ({ searchRef }: Props) => { type="text" value={inputLink} disabled={status === 'CONTINUE'} - onChange={(e) => setInputLink(e.target.value)} + onChange={handleChangeInput} placeholder="https://youtube.com/..." /> diff --git a/src/components/NicknameModal.tsx b/src/components/NicknameModal.tsx index 36e8975..7a45e92 100644 --- a/src/components/NicknameModal.tsx +++ b/src/components/NicknameModal.tsx @@ -1,13 +1,27 @@ import styled from 'styled-components'; import theme from '@/styles/theme'; import React, { useState } from 'react'; -import { nickNameAPI } from '@/apis/user'; +import { getMyInfoAPI, nickNameAPI } from '@/apis/user'; import nameImg from '@/assets/name.png'; import { BlurBackground } from '@/styles/modals/common.style'; +import { userInfoState } from '@/stores/user'; +import { useSetRecoilState } from 'recoil'; const NicknameModal = () => { const [inputCount, setInputCount] = useState(0); const [name, setName] = useState(""); + + const setUserInfo = useSetRecoilState(userInfoState); + + const refreshMyInfo = async () => { + try { + const { result } = (await getMyInfoAPI()).data; + + setUserInfo(result); + } catch (e) { + console.error(e); + } + } const onChangeName = (e: React.ChangeEvent) => { const target = e.currentTarget; @@ -15,7 +29,6 @@ const NicknameModal = () => { target.value = target.value.slice(0, 7); } setName(target.value); - console.log(name); setInputCount( target.value.replace(/[\0-\x7f]|([0-\u07ff]|(.))/g, "$&$1$2").length ); @@ -36,6 +49,7 @@ const NicknameModal = () => { const response = (await nickNameAPI({ nick_name : name, })).data + refreshMyInfo(); console.log(response); } catch (err) { console.log(err); @@ -156,6 +170,7 @@ const SucButton = styled.button` color: #fff; text-align: center; ${theme.typography.Body1}; + cursor: pointer; `; const Button = styled.button` diff --git a/src/components/SearchPage/SearchComponent.tsx b/src/components/SearchPage/SearchComponent.tsx index b81d63f..7eb7b03 100644 --- a/src/components/SearchPage/SearchComponent.tsx +++ b/src/components/SearchPage/SearchComponent.tsx @@ -54,18 +54,18 @@ const SearchComponent : React.FC = ({tags, input, searchType, sel if(!searchType){ if ((event.code === 'Comma' || event.code === 'Space') && !isComposing) { - event.preventDefault(); + event.preventDefault(); if (input) { tags.length > 0 && !input.startsWith('#') ? setTags([...tags, '#' + input]) : setTags([...tags, input]) if(selectedHashtags && setSelectedHashtags && !selectedHashtags.includes(input)) - input.startsWith('#') ? setSelectedHashtags([...selectedHashtags, input.substring(1)]) : setSelectedHashtags([...selectedHashtags, input]) + input.startsWith('#') ? setSelectedHashtags([...selectedHashtags, input.substring(1).replace(/\s/g, '')]) : setSelectedHashtags([...selectedHashtags, input.replace(/\s/g, '')]) setInput(''); } } else if ((event.key === 'Backspace' || event.code === 'Backspace') && !input) { if(tags.length > 0){ const lastValue = tags[tags.length - 1] - if(selectedHashtags && setSelectedHashtags && selectedHashtags.includes(lastValue.substring(1))) - setSelectedHashtags(selectedHashtags.filter((prev : string) => prev !== lastValue.substring(1))); + if(selectedHashtags && setSelectedHashtags && selectedHashtags.includes(lastValue.substring(1).replace(/\s/g, ''))) + setSelectedHashtags(selectedHashtags.filter((prev : string) => prev !== lastValue.substring(1).replace(/\s/g, ''))); setRemovingTagIndex(tags.length - 1); setTimeout(() => { diff --git a/src/components/SummaryPage/SummaryDetailBox/SummaryDetailBox.tsx b/src/components/SummaryPage/SummaryDetailBox/SummaryDetailBox.tsx index 4714388..8358d15 100644 --- a/src/components/SummaryPage/SummaryDetailBox/SummaryDetailBox.tsx +++ b/src/components/SummaryPage/SummaryDetailBox/SummaryDetailBox.tsx @@ -1,4 +1,5 @@ -import { useRecoilState, useRecoilValue } from 'recoil'; +import { useEffect, useRef } from 'react'; +import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; import { updateVideoCategoryIdAPI } from '@/apis/videos'; @@ -6,8 +7,10 @@ import { IVideo } from '@/models/video'; import { summaryIsEditingViewState, + summaryPlaySubHeadingIdState, summaryUpdateVideoState, summaryVideoState, + summaryVideoTimeState, } from '@/stores/summary'; import { toastListState } from '@/stores/toast'; @@ -24,9 +27,15 @@ type Props = { }; const SummaryDetailBox = ({ onRefresh }: Props) => { + const player = useRef(); + const summaryVideo = useRecoilValue(summaryVideoState) as IVideo; const summaryUpdateVideo = useRecoilValue(summaryUpdateVideoState); + const setSummaryVideoTime = useSetRecoilState(summaryVideoTimeState); const isEditingView = useRecoilValue(summaryIsEditingViewState); + const [playSubHeadingId, setPlaySubHeadingId] = useRecoilState( + summaryPlaySubHeadingIdState, + ); const [toastList, setToastList] = useRecoilState(toastListState); const subHeading = isEditingView @@ -50,6 +59,75 @@ const SummaryDetailBox = ({ onRefresh }: Props) => { } }; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const handleMessage = (e: any) => { + if (e.origin === 'https://www.youtube.com') { + try { + const { info } = JSON.parse(e.data); + + if (!info) return; + + setPlaySubHeadingId((id) => { + const item = subHeading.find((s) => s.id === id); + + // END or PAUSE + if ([0, 2].includes(info.playerState)) { + return -1; + } + + if (item) { + if ( + item.start_time > info.currentTime || + info.currentTime > item.end_time + ) + return -1; + } + + return id; + }); + + setSummaryVideoTime(info.currentTime); + } catch (e) { + console.error(e); + } + } + }; + + useEffect(() => { + if (player.current) return; + + player.current = new YT.Player('player', { + videoId: summaryVideo.youtube_id, + }); + + window.onmessage = handleMessage; + + return () => { + setSummaryVideoTime(0); + setPlaySubHeadingId(-1); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [summaryVideo]); + + useEffect(() => { + if (player.current) { + if (playSubHeadingId > -1) { + const item = subHeading.find((s) => s.id === playSubHeadingId); + + if (item) { + player.current.seekTo(item.start_time, true); + player.current.playVideo(); + } + } else if (playSubHeadingId === -2) { + // 영상 멈추기 + player.current.pauseVideo(); + + setPlaySubHeadingId(-1); + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [playSubHeadingId]); + return (
{ ))}
-