diff --git a/src/apis/sms.ts b/src/apis/sms.ts index 8f64af4..986d7b4 100644 --- a/src/apis/sms.ts +++ b/src/apis/sms.ts @@ -10,6 +10,10 @@ export const sendSMSAPI = (data: sendSMSRequest) => { }; +export const sendSMSFindAPI = (data : sendSMSRequest) => { + return axios.post>(PREFIX + '/sendSMS-find', data); +} + export const checkSMSAPI = (data : checkSMSRequest, token : string) => { return axios.post>(PREFIX + '/checkSMS', data, { headers : { Authorization : `Bearer ${token}`} diff --git a/src/apis/videos.ts b/src/apis/videos.ts index 4131996..b3175f8 100644 --- a/src/apis/videos.ts +++ b/src/apis/videos.ts @@ -76,13 +76,6 @@ export const getUnReadDummyVideosAPI = () => { return axios.get>('/videos/dummyVideos/unRead'); }; -export const getUnReadDummyVideos = async (): Promise< - APIResponse> -> => { - const response = await axiosInstance.get('/videos/dummyVideos/unRead'); - return response.data; -}; - export const getAllDummyVideos = async (): Promise< APIResponse> > => { diff --git a/src/assets/naver-logo.png b/src/assets/naver-logo.png index 335023d..8015462 100644 Binary files a/src/assets/naver-logo.png and b/src/assets/naver-logo.png differ diff --git a/src/components/NicknameModal.tsx b/src/components/NicknameModal.tsx index 7a45e92..10d1dea 100644 --- a/src/components/NicknameModal.tsx +++ b/src/components/NicknameModal.tsx @@ -6,14 +6,18 @@ import nameImg from '@/assets/name.png'; import { BlurBackground } from '@/styles/modals/common.style'; import { userInfoState } from '@/stores/user'; import { useSetRecoilState } from 'recoil'; +import useCreateToast from '@/hooks/useCreateToast'; const NicknameModal = () => { - const [inputCount, setInputCount] = useState(0); - const [name, setName] = useState(""); - - const setUserInfo = useSetRecoilState(userInfoState); + const [inputCount, setInputCount] = useState(0); + const [name, setName] = useState(''); + const { createToast } = useCreateToast(); - const refreshMyInfo = async () => { + const setUserInfo = useSetRecoilState(userInfoState); + const NickNameRegex = /^[a-zA-Z0-9가-힣\s]*$/; + const testNickNameRegex = NickNameRegex.test(name); + + const refreshMyInfo = async () => { try { const { result } = (await getMyInfoAPI()).data; @@ -21,100 +25,96 @@ const NicknameModal = () => { } catch (e) { console.error(e); } - } + }; - const onChangeName = (e: React.ChangeEvent) => { - const target = e.currentTarget; - if (target.value.length > 7) { - target.value = target.value.slice(0, 7); - } - setName(target.value); - setInputCount( - target.value.replace(/[\0-\x7f]|([0-\u07ff]|(.))/g, "$&$1$2").length - ); - setInputCount(target.value.length); - }; - - const onApply = () => { - if (name) { - // 서버에 데이터 전송 - onRegisterNicknameInfo(); - } else { - alert('입력값을 확인해주세요.'); - } - }; - - const onRegisterNicknameInfo = async () => { - try { - const response = (await nickNameAPI({ - nick_name : name, - })).data - refreshMyInfo(); - console.log(response); - } catch (err) { - console.log(err); - } - }; - - return ( - - - -
- signup -

어떤 이름으로 불러드릴까요?

- - vino에 오신걸 환영합니다! 원하시는 이름으로 불러드릴게요 - -
- - - - - - - {inputCount} - - /7(공백포함) - - - {name ? ( - - 등록하기 - ) : ( - ) - } -
-
+ const onChangeName = (e: React.ChangeEvent) => { + const target = e.currentTarget; + if (target.value.length > 7) { + target.value = target.value.slice(0, 7); + } + setName(target.value); + setInputCount( + target.value.replace(/[\0-\x7f]|([0-\u07ff]|(.))/g, '$&$1$2').length, ); + setInputCount(target.value.length); + }; + + const onApply = () => { + if (name) { + // 서버에 데이터 전송 + onRegisterNicknameInfo(); + } else { + createToast('입력값을 확인해주세요.'); + } }; - - export default NicknameModal; - const ModalDiv = styled.div` + const onRegisterNicknameInfo = async () => { + try { + const response = ( + await nickNameAPI({ + nick_name: name, + }) + ).data; + refreshMyInfo(); + console.log(response); + } catch (err) { + console.log(err); + } + }; + + return ( + + +
+ signup +

어떤 이름으로 불러드릴까요?

+ + vino에 오신걸 환영합니다! 원하시는 이름으로 불러드릴게요 + +
+ + + + + {inputCount} + /7(공백포함) + + + {!testNickNameRegex && ( + + *아쉽지만,이모티콘은 사용할 수 없어요 + + )} + {name ? ( + + 등록하기 + + ) : ( + + )} +
+
+ ); +}; + +export default NicknameModal; + +const ModalDiv = styled.div` padding: 40px 50px; display: flex; flex-direction: column; @@ -136,52 +136,52 @@ const NicknameModal = () => { `; const InputBox = styled.input` - width: 202px; - height: 56px; - background-color: #F3F3F3; - padding: 0px 0px 0px 20px; - display: flex; - align-items: center; - justify-content: center; - gap: 20px; - flex: 1 0 0; - font-style: normal; - border: none; - border-radius: 12px; - color: var(--Main, #1E1E1E); - font-family: Pretendard; - ${theme.typography.Body1}; - &:focus { - outline: none; - } - - &::placeholder { + width: 202px; + height: 56px; + background-color: #f3f3f3; + padding: 0px 0px 0px 20px; + display: flex; + align-items: center; + justify-content: center; + gap: 20px; + flex: 1 0 0; + font-style: normal; + border: none; + border-radius: 12px; + color: var(--Main, #1e1e1e); + font-family: Pretendard; + ${theme.typography.Body1}; + &:focus { + outline: none; + } + + &::placeholder { color: #bbb; ${theme.typography.Body1}; } `; const SucButton = styled.button` - width: 100%; - height: 56px; - border: none; - border-radius: 12px; - background-color : #1E1E1E; - color: #fff; - text-align: center; - ${theme.typography.Body1}; - cursor: pointer; + width: 100%; + height: 56px; + border: none; + border-radius: 12px; + background-color: #1e1e1e; + color: #fff; + text-align: center; + ${theme.typography.Body1}; + cursor: pointer; `; const Button = styled.button` - width: 100%; - height: 56px; - border: none; - border-radius: 12px; - background-color : #F3F3F3; - color: #BBBBBB; - text-align: center; - ${theme.typography.Body1}; + width: 100%; + height: 56px; + border: none; + border-radius: 12px; + background-color: #f3f3f3; + color: #bbbbbb; + text-align: center; + ${theme.typography.Body1}; `; const InputNickNameMessage = styled.span` @@ -195,14 +195,21 @@ const InputNickNameLength = styled.span` `; const TextDiv = styled.div` - position : relative; - width : 600px; - height : 56px; - align-items: center; - justify-content: center; - background-color: #F3F3F3; - display: flex; - flex-direction: row; - border-radius: 12px; - margin-top: 48px; - ` \ No newline at end of file + position: relative; + width: 600px; + height: 56px; + align-items: center; + justify-content: center; + background-color: #f3f3f3; + display: flex; + flex-direction: row; + border-radius: 12px; + margin-top: 48px; +`; + +export const WarningMessage = styled.span` + align-self: flex-start; + margin: 0px 0px 12px 16px; + color: ${theme.color.red}; + ${theme.typography.Body3} +`; \ No newline at end of file diff --git a/src/components/PhoneCheck.tsx b/src/components/PhoneCheck.tsx index 2433afb..51edf6b 100644 --- a/src/components/PhoneCheck.tsx +++ b/src/components/PhoneCheck.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from "react"; -import { sendSMSAPI, checkSMSAPI } from '@/apis/sms'; +import { sendSMSAPI, checkSMSAPI, sendSMSFindAPI } from '@/apis/sms'; import Container from '@/styles/PhoneCheck'; @@ -7,20 +7,21 @@ interface PhoneCheckProps { setCheck : (value: boolean) => void; tel : string; setTel : (value : string) => void; + type : boolean; // true : 회원가입 false : 이메일 비밀번호 찾기 페이지 } -const PhoneCheck : React.FC = ({setCheck, tel, setTel}) => { +const PhoneCheck : React.FC = ({setCheck, tel, setTel, type}) => { const [certifyNum, setCertifyNum] = useState(''); const [token, setToken] = useState(''); const [time, setTime] = useState(5 * 60); // 초 단위 - const [isCheck, SetIsCheck] = useState(false); const [isSend, setIsSend] = useState(false); const [isCertify, setIsCertify] = useState(false); const [isTel, setIsTel] = useState(false); const [isTimer, setIsTimer] = useState(false); const [isSuccess, setIsSuccess] = useState(false); + const [phoneCertify, setPhoneCertify] = useState(false); useEffect(() => { if (isTimer) { @@ -64,33 +65,46 @@ const PhoneCheck : React.FC = ({setCheck, tel, setTel}) => { const handleCheckCertify = async () => { setIsTimer(false); SetIsCheck(true); - const response = (await checkSMSAPI({ - verification_code : Number(certifyNum), - }, token)) - if(response.data.success){ - setIsSuccess(true); - setCheck(true); - } else { - setIsSuccess(false); + try{ + const {data} = (await checkSMSAPI({ + verification_code : Number(certifyNum), + }, token)); + + if(data.success){ + setIsSuccess(true); + setCheck(true); + } else { + setIsSuccess(false); + } + } catch(e){ + setIsSuccess(false) } } const handleCertifyNum = async () => { - setIsSend(true) - setIsTimer(true); if(isSend){ SetIsCheck(false); setTime(10); } - const response = (await sendSMSAPI({ - phone_number : tel - })) - - if(response.data.success){ - setToken(response.data.result.token); + try{ + const {data} = type === true ? (await sendSMSAPI({ + phone_number : tel + })) : (await sendSMSFindAPI({ + phone_number : tel + })); + + if(data.success){ + setIsSend(true) + setIsTimer(true); + setPhoneCertify(false); + setToken(data.result.token); + } + } catch(e){ + setPhoneCertify(true); } + } - + const minutes = Math.floor(time / 60); // 분 const seconds = time % 60; @@ -110,6 +124,7 @@ const PhoneCheck : React.FC = ({setCheck, tel, setTel}) => { /> + {phoneCertify && 전화번호가 중복되었습니다.} {isSend ?
>; onSelect: (categoryId: number, categoryName?: string) => void; }; const CategorySelectBox = ({ disabled, selectedCategoryId, + startSelect, + setStartSelect, onSelect, }: Props) => { const userToken = useRecoilValue(userTokenState); @@ -55,11 +59,17 @@ const CategorySelectBox = ({ const handleSelect = (categoryId: number) => { setSelectedId(categoryId); + setStartSelect && setStartSelect(true); setIsOpen(false); }; const handleClick = () => { - if (!selectedCategory || selectedId === selectedCategoryId || disabled) + if ( + !selectedCategory || + selectedId === selectedCategoryId || + disabled || + !startSelect + ) return; onSelect(selectedCategory.categoryId, selectedCategory.name); @@ -95,9 +105,9 @@ const CategorySelectBox = ({
diff --git a/src/components/SummaryPage/SummaryDetailBox/SummaryDetailBox.tsx b/src/components/SummaryPage/SummaryDetailBox/SummaryDetailBox.tsx index 8358d15..0c9e9ed 100644 --- a/src/components/SummaryPage/SummaryDetailBox/SummaryDetailBox.tsx +++ b/src/components/SummaryPage/SummaryDetailBox/SummaryDetailBox.tsx @@ -12,7 +12,6 @@ import { summaryVideoState, summaryVideoTimeState, } from '@/stores/summary'; -import { toastListState } from '@/stores/toast'; import { DetailBox } from '@/styles/SummaryPage'; @@ -21,6 +20,7 @@ import { formatDate } from '@/utils/date'; import { CategorySelectBox } from './CategorySelectBox'; import { NoteBox } from './NoteBox'; import { DescriptionBox } from './DescriptionBox'; +import useCreateToast from '@/hooks/useCreateToast'; type Props = { onRefresh: () => void; @@ -29,6 +29,7 @@ type Props = { const SummaryDetailBox = ({ onRefresh }: Props) => { const player = useRef(); + const { createToast } = useCreateToast(); const summaryVideo = useRecoilValue(summaryVideoState) as IVideo; const summaryUpdateVideo = useRecoilValue(summaryUpdateVideoState); const setSummaryVideoTime = useSetRecoilState(summaryVideoTimeState); @@ -36,16 +37,11 @@ const SummaryDetailBox = ({ onRefresh }: Props) => { const [playSubHeadingId, setPlaySubHeadingId] = useRecoilState( summaryPlaySubHeadingIdState, ); - const [toastList, setToastList] = useRecoilState(toastListState); const subHeading = isEditingView ? summaryUpdateVideo?.subHeading || [] : summaryVideo.subHeading; - const createToast = (content: string) => { - setToastList([...toastList, { id: Date.now(), content }]); - }; - const handleSelectCategory = async (category_id: number, name?: string) => { try { await updateVideoCategoryIdAPI(category_id, { diff --git a/src/components/SummaryPage/SummaryScriptBox/ToolBox/ToolBox.tsx b/src/components/SummaryPage/SummaryScriptBox/ToolBox/ToolBox.tsx index 1967139..f219031 100644 --- a/src/components/SummaryPage/SummaryScriptBox/ToolBox/ToolBox.tsx +++ b/src/components/SummaryPage/SummaryScriptBox/ToolBox/ToolBox.tsx @@ -13,11 +13,11 @@ import { summaryUpdateVideoState, summaryVideoState, } from '@/stores/summary'; -import { toastListState } from '@/stores/toast'; import Indicator from './Indicator'; import { SearchKeyword } from './SearchKeyword'; import { ChangeKeyword } from './ChangeKeyword'; +import useCreateToast from '@/hooks/useCreateToast'; type Props = { onRefresh: () => void; @@ -33,14 +33,10 @@ const ToolBox = ({ onRefresh, onChangeKeyword }: Props) => { const [isEditingView, setIsEditingView] = useRecoilState( summaryIsEditingViewState, ); - const [toastList, setToastList] = useRecoilState(toastListState); + const { createToast } = useCreateToast(); const [originalSummary, setOriginalSummary] = useState(null); - const createToast = (content: string) => { - setToastList([...toastList, { id: Date.now(), content }]); - }; - const handleClickModifyIcon = () => { setPlaySubHeadingId(-1); setIsEditingView(true); diff --git a/src/components/category/Card.tsx b/src/components/category/Card.tsx index 3cc084f..65b26c5 100644 --- a/src/components/category/Card.tsx +++ b/src/components/category/Card.tsx @@ -33,6 +33,7 @@ const Card: React.FC = ({ const [selectedCategoryId, setSelectedCategoryId] = useState( category.length ? category[0].categoryId : -1, ); + const [startSelect, setStartSelect] = useState(false); const onFileClickWithProps = (categoryId: number, categoryName?: string) => { setSelectedCategoryId(categoryId); @@ -63,7 +64,11 @@ const Card: React.FC = ({ )} - + {video.title} {video.description} @@ -76,6 +81,8 @@ const Card: React.FC = ({ diff --git a/src/components/category/EditCategoryName.tsx b/src/components/category/EditCategoryName.tsx index c33e9e2..f434873 100644 --- a/src/components/category/EditCategoryName.tsx +++ b/src/components/category/EditCategoryName.tsx @@ -9,7 +9,9 @@ interface IEditCategoryNameProps { categoryId: number; edit: string; setEdit: React.Dispatch>; - setIsEditing: React.Dispatch>; + setIsEditing: React.Dispatch< + React.SetStateAction<{ activated: boolean; categoryId: number }> + >; } const EditCategoryName = ({ diff --git a/src/components/layout/footer/SendEmail.tsx b/src/components/layout/footer/SendEmail.tsx index 08520a0..85522c4 100644 --- a/src/components/layout/footer/SendEmail.tsx +++ b/src/components/layout/footer/SendEmail.tsx @@ -4,10 +4,12 @@ import { useState } from 'react'; import SendEmailImage from '@/assets/mail.png'; import SuccessSendEmailImage from '@/assets/success-mail.png'; import { postFeedback } from '@/apis/feedback'; +import useCreateToast from '@/hooks/useCreateToast'; const SendEmail = () => { const [feedback, setFeedback] = useState(''); const [successSend, setSuccessSend] = useState(false); + const { createToast } = useCreateToast(); const handleInputFeedback = (e: React.ChangeEvent) => setFeedback(e.target.value); @@ -19,7 +21,7 @@ const SendEmail = () => { setSuccessSend(true); return; } - alert('피드백을 전송하는 과정에서 오류가 발생했습니다.'); + createToast('피드백을 전송하는 과정에서 오류가 발생했습니다.'); }; return ( diff --git a/src/components/layout/sideBar/SubCategory.tsx b/src/components/layout/sideBar/SubCategory.tsx index 0a1d891..dfcca2c 100644 --- a/src/components/layout/sideBar/SubCategory.tsx +++ b/src/components/layout/sideBar/SubCategory.tsx @@ -6,14 +6,18 @@ import Option from './Option'; import handleDrag from '@/utils/handleDrag'; import { ISubFolderProps } from 'types/category'; import EditCategoryName from '@/components/category/EditCategoryName'; +import useCreateToast from '@/hooks/useCreateToast'; interface ISubCategoryProps { topId: number; subId: number; - categoryId: number; - name: string; - setIsDeleteModalOpen: React.Dispatch>; + subFolder: ISubFolderProps; grabedCategory: React.MutableRefObject; + isEditing: { activated: boolean; categoryId: number }; + setIsEditing: React.Dispatch< + React.SetStateAction<{ activated: boolean; categoryId: number }> + >; + setIsDeleteModalOpen: React.Dispatch>; putCategoryFolder: () => void; setCategoryId: React.Dispatch>; } @@ -21,18 +25,19 @@ interface ISubCategoryProps { const SubCategory = ({ topId, subId, - categoryId, - name, - setIsDeleteModalOpen, + subFolder, grabedCategory, + isEditing, + setIsEditing, + setIsDeleteModalOpen, putCategoryFolder, setCategoryId, }: ISubCategoryProps) => { const [subFolderOptionModalOpen, setSubFolderOptionModalOpen] = useState(false); - const [isEditing, setIsEditing] = useState(false); - const [edit, setEdit] = useState(name); + const [edit, setEdit] = useState(subFolder.name); const [beforeEdit, setBeforeEdit] = useState(edit); + const { createToast } = useCreateToast(); const [subFolderOptionModalRef] = useOutsideClick(() => setSubFolderOptionModalOpen(false), @@ -43,15 +48,20 @@ const SubCategory = ({ const handleOptionClick = (e: React.MouseEvent, option: string) => { e.stopPropagation(); if (option === '수정') { - setIsEditing(true); + if (subFolder.name === '기타') { + createToast(`'기타' 폴더는 수정할 수 없습니다.`); + setSubFolderOptionModalOpen(false); + return; + } + setIsEditing({ activated: true, categoryId: subFolder.categoryId }); setBeforeEdit(edit); } else if (option === '삭제') { - if (name === '기타') { - alert(`'기타' 폴더는 삭제할 수 없습니다.`); + if (subFolder.name === '기타') { + createToast(`'기타' 폴더는 삭제할 수 없습니다.`); setSubFolderOptionModalOpen(false); return; } - setCategoryId(categoryId); + setCategoryId(subFolder.categoryId); setIsDeleteModalOpen(true); } setSubFolderOptionModalOpen(false); @@ -65,8 +75,8 @@ const SubCategory = ({ const handleDragStart = () => (grabedCategory.current = { - categoryId: categoryId, - name, + categoryId: subFolder.categoryId, + name: subFolder.name, topCategoryId: topId, }); @@ -79,10 +89,10 @@ const SubCategory = ({ onDragStart={handleDragStart} onDragEnd={putCategoryFolder} > - {isEditing ? ( + {isEditing.activated && isEditing.categoryId === subFolder.categoryId ? (
{edit} - - - + {!isEditing.activated && ( + + + + )} {subFolderOptionModalOpen && (