diff --git a/src/apis/uploadS3.ts b/src/apis/uploadS3.ts index 597df25..36dbdf0 100644 --- a/src/apis/uploadS3.ts +++ b/src/apis/uploadS3.ts @@ -8,6 +8,7 @@ const { VITE_S3_SECRET_ACCESS_KEY, VITE_S3_BUCKET_NAME, VITE_S3_REGION, + VITE_S3_DOMAIN, } = import.meta.env; AWS.config.update({ @@ -19,12 +20,10 @@ const s3 = new AWS.S3(); const bucketName = VITE_S3_BUCKET_NAME; export const uploadFile = async (file: File) => { - const { type, name } = file; + const { type } = file; const params = { Bucket: bucketName, - Key: `${name}/${v4().toString().replace('-', '')}.${ - file.type.split('/')[1] - }`, + Key: `${v4().toString().replaceAll('-', '')}.${type.split('/')[1]}`, Body: file, ContentType: type, ACL: 'public-read', @@ -36,3 +35,18 @@ export const uploadFile = async (file: File) => { alert((err as AxiosError).response?.data.message); } }; + +export const deleteFile = (urls: string[]) => { + urls.map(async (url) => { + try { + await s3 + .deleteObject({ + Bucket: bucketName, + Key: url.split(VITE_S3_DOMAIN)[1].slice(1), + }) + .promise(); + } catch (err) { + console.error('이미지를 삭제하는데 실패했습니다.'); + } + }); +}; diff --git a/src/components/Community/Board/index.tsx b/src/components/Community/Board/index.tsx index aed0db2..f14726f 100644 --- a/src/components/Community/Board/index.tsx +++ b/src/components/Community/Board/index.tsx @@ -2,7 +2,6 @@ import { useNavigate } from 'react-router-dom'; import dayjs from 'dayjs'; import { CommunityBoardType } from '@/types/Board/communityType'; import { getCategoryName, getPrefixCategoryName } from '@/utils/utils'; -import { THUMBNAIL_SIZE_OPTION } from '@/constants/image'; import styles from './styles.module.scss'; type CommunityBoardProps = Omit; @@ -52,7 +51,8 @@ export default function CommunityBoard({ {imageUrl && ( thumbnail )} diff --git a/src/components/Community/Quill/index.tsx b/src/components/Community/Quill/index.tsx index 9fd1db2..007c086 100644 --- a/src/components/Community/Quill/index.tsx +++ b/src/components/Community/Quill/index.tsx @@ -9,7 +9,10 @@ import { QueryKeys, restFetcher } from '@/queryClient'; import { BoardFormType } from '@/types/Board/boardType'; import { CommunityBoardDetailType } from '@/types/Board/communityType'; import getImageUrls from '@/utils/Quill/getImageUrls'; +import getNotUsedImageUrl from '@/utils/Quill/getNotUsedImageUrl'; import { PostBoardAPI } from '@/apis/boards'; +import { deleteFile } from '@/apis/uploadS3'; +import { imageStore } from '@/store/imageStore'; import useModalState from '@/hooks/useModalState'; import useQuillModules from '@/hooks/useQuillModules'; import useToastMessageType from '@/hooks/useToastMessageType'; @@ -25,19 +28,22 @@ type CommunityQuillProps = { // TODO: 이미지 10개 이상 등록 불가 export default function CommunityQuill({ queryParam }: CommunityQuillProps) { + const { images, setImages, resetImages } = imageStore(); const navigate = useNavigate(); const location = useLocation(); const boardData: CommunityBoardDetailType | null = location.state; const { modalState, handleModalOpen, handleModalClose } = useModalState(); const { toastMessageProps, handleToastMessageProps } = useToastMessageType(); - const QuillRef = useRef(); - const [title, setTitle] = useState(boardData ? boardData.title : ''); const [contents, setContents] = useState(''); const [category, setCategory] = useState(boardData ? boardData.category : ''); const [isProcessing, setIsProcessing] = useState(false); + const QuillRef = useRef(); + const imagesRef = useRef(images); + const isProcessingRef = useRef(isProcessing); + const prefixCategory = queryParam === 'free_board' ? 'DEFAULT' : 'ADVERTISEMENT'; const categoryList = @@ -75,15 +81,20 @@ export default function CommunityQuill({ queryParam }: CommunityQuillProps) { ); // 이미지를 업로드 하기 위한 함수 - const modules = useQuillModules(QuillRef); + const modules = useQuillModules(QuillRef, setImages); const onChange = (content: string) => { setContents(content); }; const onPost = async () => { setIsProcessing(true); + if (!checkBeforePost(title, contents)) { + setIsProcessing(false); + return; + } + const imageUrls = [...getImageUrls(contents)]; - if (!checkBeforePost(title, contents)) return; + const notUsedImageUrls = getNotUsedImageUrl(images, imageUrls); const BoardForm: BoardFormType = { title, @@ -95,9 +106,12 @@ export default function CommunityQuill({ queryParam }: CommunityQuillProps) { prefixCategory, fixed: false, }; + try { const response = await PostBoardAPI(BoardForm); if (response?.code === 'SUCCESS') { + deleteFile(notUsedImageUrls); + resetImages(); handleToastMessageProps('POST_CREATE_SUCCESS', () => { handleModalClose(); navigate(`/community/${queryParam}`); @@ -109,23 +123,43 @@ export default function CommunityQuill({ queryParam }: CommunityQuillProps) { } } catch (error) { console.error(error); - } finally { setIsProcessing(false); } }; const onUpdate = () => { + setIsProcessing(true); + if (!checkBeforePost(title, contents)) { + setIsProcessing(false); + return; + } + + const imageUrls = [...getImageUrls(contents)]; + const notUsedImageUrls = getNotUsedImageUrl(images, imageUrls); + const BoardForm: BoardFormType = { title, code: contents, category, - imageUrls: [...getImageUrls(contents)], + imageUrls, prefixCategory, fixed: false, }; - mutate(BoardForm); + try { + mutate(BoardForm); + deleteFile(notUsedImageUrls); + resetImages(); + } catch (error) { + console.error(error); + setIsProcessing(false); + } }; + useEffect(() => { + imagesRef.current = images; + isProcessingRef.current = isProcessing; + }, [images, isProcessing]); + useEffect(() => { // 개발모드에선 StricMode 때문에 같은글이 두번 넣어짐. StrictMode를 해제하고 테스트하자 if (boardData) { @@ -133,6 +167,12 @@ export default function CommunityQuill({ queryParam }: CommunityQuillProps) { ?.getEditor() .clipboard.dangerouslyPasteHTML(0, boardData.code); } + return () => { + if (!isProcessingRef.current) { + deleteFile(imagesRef.current); + resetImages(); + } + }; }, []); return ( diff --git a/src/components/Introduce/Quill/index.tsx b/src/components/Introduce/Quill/index.tsx index 3a04e17..1124da9 100644 --- a/src/components/Introduce/Quill/index.tsx +++ b/src/components/Introduce/Quill/index.tsx @@ -10,8 +10,10 @@ import { QueryKeys, restFetcher } from '@/queryClient'; import { BoardFormType } from '@/types/Board/boardType'; import { IntroBoardDetailType } from '@/types/Board/introType'; import getImageUrls from '@/utils/Quill/getImageUrls'; +import getNotUsedImageUrl from '@/utils/Quill/getNotUsedImageUrl'; import { PostBoardAPI } from '@/apis/boards'; -import { uploadFile } from '@/apis/uploadS3'; +import { deleteFile, uploadFile } from '@/apis/uploadS3'; +import { imageStore } from '@/store/imageStore'; import useModalState from '@/hooks/useModalState'; import useQuillModules from '@/hooks/useQuillModules'; import useToastMessageType from '@/hooks/useToastMessageType'; @@ -25,15 +27,13 @@ import styles from './styles.module.scss'; const { VITE_S3_DOMAIN } = import.meta.env; export default function IntroduceQuill() { + const { images, setImages, resetImages } = imageStore(); const navigate = useNavigate(); const location = useLocation(); const boardData: IntroBoardDetailType | null = location.state; const { modalState, handleModalOpen, handleModalClose } = useModalState(); const { toastMessageProps, handleToastMessageProps } = useToastMessageType(); - const QuillRef = useRef(); - const thumbnailRef = useRef(null); - const [title, setTitle] = useState(boardData ? boardData.title : ''); const [contents, setContents] = useState(''); const [category, setCategory] = useState(boardData ? boardData.category : ''); @@ -45,6 +45,11 @@ export default function IntroduceQuill() { ); const [isProcessing, setIsProcessing] = useState(false); + const QuillRef = useRef(); + const thumbnailRef = useRef(null); + const imagesRef = useRef(images); + const isProcessingRef = useRef(isProcessing); + const queryClient = useQueryClient(); const { mutate, isLoading: isUpdateLoading } = useMutation( @@ -86,6 +91,7 @@ export default function IntroduceQuill() { // const imageUrl = VITE_CLOUD_FRONT_DOMAIN + imageName + DEFAULT_OPTIONS; const imageUrl = VITE_S3_DOMAIN + imageName; setThumbnail(imageUrl); + setImages(thumbnail); } catch (error) { const err = error as AxiosError; return { ...err.response, success: false }; @@ -94,7 +100,7 @@ export default function IntroduceQuill() { }; // 이미지를 업로드 하기 위한 함수 - const modules = useQuillModules(QuillRef); + const modules = useQuillModules(QuillRef, setImages); const onChange = (content: string) => { setContents(content); }; @@ -102,7 +108,12 @@ export default function IntroduceQuill() { const onPost = async () => { setIsProcessing(true); const imageUrls = [thumbnail, ...getImageUrls(contents)]; - if (!checkBeforePost(title, contents, imageUrls)) return; + if (!checkBeforePost(title, contents, thumbnail)) { + setIsProcessing(false); + return; + } + + const notUsedImageUrls = getNotUsedImageUrl(images, imageUrls); const BoardForm: BoardFormType = { title, @@ -115,6 +126,8 @@ export default function IntroduceQuill() { try { const response = await PostBoardAPI(BoardForm); if (response?.code === 'SUCCESS') { + deleteFile(notUsedImageUrls); + resetImages(); handleToastMessageProps('POST_CREATE_SUCCESS', () => { handleModalClose(); navigate(`/introduce`); @@ -126,23 +139,43 @@ export default function IntroduceQuill() { } } catch (error) { console.error(error); - } finally { - setIsProcessing(false); } }; const onUpdate = () => { + setIsProcessing(true); + const imageUrls = [thumbnail, ...getImageUrls(contents)]; + + if (!checkBeforePost(title, contents, thumbnail)) { + setIsProcessing(false); + return; + } + + const notUsedImageUrls = getNotUsedImageUrl(images, imageUrls); + const BoardForm: BoardFormType = { title, code: contents, category, - imageUrls: [thumbnail, ...getImageUrls(contents)], + imageUrls, prefixCategory: 'INTRO', fixed: false, }; - mutate(BoardForm); + try { + mutate(BoardForm); + deleteFile(notUsedImageUrls); + resetImages(); + } catch (error) { + console.error(error); + setIsProcessing(false); + } }; + useEffect(() => { + imagesRef.current = images; + isProcessingRef.current = isProcessing; + }, [images, isProcessing]); + useEffect(() => { // 개발모드에선 StricMode 때문에 같은글이 두번 넣어짐. StrictMode를 해제하고 테스트하자 if (boardData) { @@ -150,6 +183,12 @@ export default function IntroduceQuill() { ?.getEditor() .clipboard.dangerouslyPasteHTML(0, boardData.code); } + return () => { + if (!isProcessingRef.current) { + deleteFile(imagesRef.current); + resetImages(); + } + }; }, []); return ( diff --git a/src/components/Introduce/ReviewBoard/index.tsx b/src/components/Introduce/ReviewBoard/index.tsx index 63c789c..efdfead 100644 --- a/src/components/Introduce/ReviewBoard/index.tsx +++ b/src/components/Introduce/ReviewBoard/index.tsx @@ -1,6 +1,5 @@ import { useNavigate } from 'react-router-dom'; import { IntroBoardType } from '@/types/Board/introType'; -import { THUMBNAIL_SIZE_OPTION } from '@/constants/image'; import styles from './styles.module.scss'; type ReviewBoardProps = IntroBoardType; @@ -13,7 +12,8 @@ export default function ReviewBoard(props: ReviewBoardProps) { className={styles.wrapper} onClick={() => navigate(`/intro_board/${props.boardId}`)} > - backgroundImg + {/* backgroundImg */} + backgroundImg

{props.title}

{props.oneLineContent}...

diff --git a/src/components/Introduce/TrendBoard/index.tsx b/src/components/Introduce/TrendBoard/index.tsx index cd90613..6735dfb 100644 --- a/src/components/Introduce/TrendBoard/index.tsx +++ b/src/components/Introduce/TrendBoard/index.tsx @@ -2,7 +2,6 @@ import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { AnimatePresence, motion } from 'framer-motion'; import { IntroBoardType } from '@/types/Board/introType'; -import { THUMBNAIL_SIZE_OPTION } from '@/constants/image'; import { updownVariants } from '@/constants/variants'; import styles from './styles.module.scss'; @@ -17,12 +16,19 @@ export default function TrendBoard(props: TrendBoardProps) { onClick={() => navigate(`/intro_board/${props.boardId}`)} onMouseOver={() => setIsMouseOver(true)} onMouseLeave={() => setIsMouseOver(false)} + // style={{ + // backgroundImage: isMouseOver + // ? `linear-gradient(rgba(0, 0, 0, 0.35), rgba(0, 0, 0, 0.35)), + // url('${props.imageUrl + THUMBNAIL_SIZE_OPTION}')` + // : `linear-gradient(rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2)), + // url('${props.imageUrl + THUMBNAIL_SIZE_OPTION}')`, + // }} style={{ backgroundImage: isMouseOver ? `linear-gradient(rgba(0, 0, 0, 0.35), rgba(0, 0, 0, 0.35)), - url('${props.imageUrl + THUMBNAIL_SIZE_OPTION}')` + url('${props.imageUrl}')` : `linear-gradient(rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2)), - url('${props.imageUrl + THUMBNAIL_SIZE_OPTION}')`, + url('${props.imageUrl}')`, }} >

{props.title}

diff --git a/src/components/MyPage/MySetting/EditInfo/EditEmailInfo/index.tsx b/src/components/MyPage/MySetting/EditInfo/EditEmailInfo/index.tsx index 4575341..8f8895a 100644 --- a/src/components/MyPage/MySetting/EditInfo/EditEmailInfo/index.tsx +++ b/src/components/MyPage/MySetting/EditInfo/EditEmailInfo/index.tsx @@ -140,7 +140,7 @@ export default function EditEmailInfo({ !/^([\w\.\_\-])*[a-zA-Z0-9]+([\w\.\_\-])*([a-zA-Z0-9])+([\w\.\_\-])+@([a-zA-Z0-9]+\.)+[a-zA-Z0-9]{2,8}$/g.test( watch('email'), ) || isCheckEmail - ? styles.buttonStyleDisabled + ? styles.Disabled : styles.buttonStyleActive } disabled={ diff --git a/src/components/MyPage/MySetting/EditInfo/EditEmailInfo/styles.module.scss b/src/components/MyPage/MySetting/EditInfo/EditEmailInfo/styles.module.scss index bf7a593..9d33806 100644 --- a/src/components/MyPage/MySetting/EditInfo/EditEmailInfo/styles.module.scss +++ b/src/components/MyPage/MySetting/EditInfo/EditEmailInfo/styles.module.scss @@ -63,11 +63,11 @@ &:hover { opacity: 0.8; } -} -.buttonStyle:disabled { - cursor: auto; - &:hover { - opacity: 1; + &:disabled { + cursor: not-allowed; + &:hover { + opacity: 1; + } } } diff --git a/src/components/MyPage/MySetting/EditInfo/EditNicknameInfo/styles.module.scss b/src/components/MyPage/MySetting/EditInfo/EditNicknameInfo/styles.module.scss index 6ed0e4b..e552c4d 100644 --- a/src/components/MyPage/MySetting/EditInfo/EditNicknameInfo/styles.module.scss +++ b/src/components/MyPage/MySetting/EditInfo/EditNicknameInfo/styles.module.scss @@ -61,11 +61,11 @@ &:hover { opacity: 0.8; } -} -.buttonStyle:disabled { - cursor: auto; - &:hover { - opacity: 1; + &:disabled { + cursor: not-allowed; + &:hover { + opacity: 1; + } } } diff --git a/src/components/MyPage/MySetting/EditInfo/EditPhoneInfo/styles.module.scss b/src/components/MyPage/MySetting/EditInfo/EditPhoneInfo/styles.module.scss index bf7a593..9d33806 100644 --- a/src/components/MyPage/MySetting/EditInfo/EditPhoneInfo/styles.module.scss +++ b/src/components/MyPage/MySetting/EditInfo/EditPhoneInfo/styles.module.scss @@ -63,11 +63,11 @@ &:hover { opacity: 0.8; } -} -.buttonStyle:disabled { - cursor: auto; - &:hover { - opacity: 1; + &:disabled { + cursor: not-allowed; + &:hover { + opacity: 1; + } } } diff --git a/src/components/Trade/Quill/index.tsx b/src/components/Trade/Quill/index.tsx index 1710763..7a78834 100644 --- a/src/components/Trade/Quill/index.tsx +++ b/src/components/Trade/Quill/index.tsx @@ -1,4 +1,4 @@ -import { useRef, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import ReactQuill from 'react-quill'; import { useLocation, useNavigate } from 'react-router-dom'; import { useMutation, useQueryClient } from '@tanstack/react-query'; @@ -7,7 +7,10 @@ import ToastMessageModal from '@/components/Common/ToastMessageModal'; import { QueryKeys, restFetcher } from '@/queryClient'; import { TradeBoardDetailType, TradeBoardForm } from '@/types/Board/tradeType'; import getImageUrls from '@/utils/Quill/getImageUrls'; +import getNotUsedImageUrl from '@/utils/Quill/getNotUsedImageUrl'; import { PostHouseAPI } from '@/apis/houses'; +import { deleteFile } from '@/apis/uploadS3'; +import { imageStore } from '@/store/imageStore'; import userStore from '@/store/userStore'; import useModalState from '@/hooks/useModalState'; import useQuillModules from '@/hooks/useQuillModules'; @@ -29,17 +32,21 @@ export default function TradeQuill({ setForm, thumbnail, }: TradeQuillProps) { + const { images, setImages, resetImages } = imageStore(); const { user } = userStore(); const navigate = useNavigate(); const { modalState, handleModalOpen, handleModalClose } = useModalState(); const { toastMessageProps, handleToastMessageProps } = useToastMessageType(); const { state }: { state: { data: TradeBoardDetailType } } = useLocation(); - const QuillRef = useRef(); - // 이미지를 업로드 하기 위한 함수 - const modules = useQuillModules(QuillRef); const [isProcessing, setIsProcessing] = useState(false); + const QuillRef = useRef(); + const imagesRef = useRef(images); + const isProcessingRef = useRef(isProcessing); + + const modules = useQuillModules(QuillRef, setImages); + const queryClient = useQueryClient(); const { mutate, isLoading: isUpdateLoading } = useMutation( (tradeBoardForm: TradeBoardForm) => @@ -69,6 +76,7 @@ export default function TradeQuill({ const onPost = async ({ isTempSave }: { isTempSave: boolean }) => { setIsProcessing(true); const imageUrls = [thumbnail, ...getImageUrls(form.code)]; + const notUsedImageUrls = getNotUsedImageUrl(images, imageUrls); const extractedYear = form.createdDate.match(/\d{4}/); const createdDate = extractedYear ? extractedYear[0] : ''; @@ -91,6 +99,8 @@ export default function TradeQuill({ try { await PostHouseAPI(newForm); + deleteFile(notUsedImageUrls); + resetImages(); if (isTempSave) { alert('게시글이 임시저장 되었습니다.'); queryClient.refetchQueries([QueryKeys.MY_SAVES]); @@ -111,6 +121,7 @@ export default function TradeQuill({ const onUpdate = async ({ isTempSave }: { isTempSave: boolean }) => { const imageUrls = [thumbnail, ...getImageUrls(form.code)]; + const notUsedImageUrls = getNotUsedImageUrl(images, imageUrls); const extractedYear = form.createdDate.match(/\d{4}/); const createdDate = extractedYear ? extractedYear[0] : '2002'; @@ -128,6 +139,8 @@ export default function TradeQuill({ } try { mutate(newForm); + deleteFile(notUsedImageUrls); + resetImages(); if (isTempSave) { alert('게시글이 임시저장 되었습니다.'); } else { @@ -146,6 +159,20 @@ export default function TradeQuill({ const isUpdating = Boolean(state && !state.data.tmpYn); // 등록된 글을 수정하는 상태 const isSaving = Boolean(state && state.data.tmpYn); // 임시저장된 글을 작성하는 상태 + useEffect(() => { + imagesRef.current = images; + isProcessingRef.current = isProcessing; + }, [images, isProcessing]); + + useEffect(() => { + return () => { + if (!isProcessingRef.current) { + deleteFile(imagesRef.current); + resetImages(); + } + }; + }, []); + return (
@@ -169,7 +196,6 @@ export default function TradeQuill({ QuillRef.current = element; } }} - value={form.code} onChange={(value) => setForm((prev) => ({ ...prev, code: value }))} modules={modules} placeholder="사진 5장 이상은 필수입니다. 5장 이상(건물 외관, 내부 포함) 업로드 되지 않을 시, 반려됩니다." diff --git a/src/hooks/useQuillModules.ts b/src/hooks/useQuillModules.ts index 3e4073f..0b19ad6 100644 --- a/src/hooks/useQuillModules.ts +++ b/src/hooks/useQuillModules.ts @@ -12,6 +12,7 @@ Quill.register('modules/imageResize', ImageResize); const useQuillModules = ( QuillRef: React.MutableRefObject, + setImages: (imageUrl: string) => void, ) => { const modules = useMemo( () => ({ @@ -80,7 +81,7 @@ const useQuillModules = ( ['image'], ['clean'], ], - handlers: { image: () => imageHandler(QuillRef) }, + handlers: { image: () => imageHandler(QuillRef, setImages) }, }, }), [], diff --git a/src/pages/Community/Detail/index.tsx b/src/pages/Community/Detail/index.tsx index a1c541d..2c29fb1 100644 --- a/src/pages/Community/Detail/index.tsx +++ b/src/pages/Community/Detail/index.tsx @@ -19,7 +19,6 @@ export default function CommunityBoardDetailPage() { const { user } = userStore(); const { category, id } = useParams(); const navigate = useNavigate(); - const queryClient = useQueryClient(); const { data: boardData, isError } = useQuery< ApiResponseWithDataType diff --git a/src/pages/Main/index.tsx b/src/pages/Main/index.tsx index 1d68074..8acd749 100644 --- a/src/pages/Main/index.tsx +++ b/src/pages/Main/index.tsx @@ -14,7 +14,6 @@ import { CommunityBoardType } from '@/types/Board/communityType'; import { IntroBoardType } from '@/types/Board/introType'; import { getPrefixCategoryName } from '@/utils/utils'; import { ApiResponseWithDataType } from '@/types/apiResponseType'; -import { THUMBNAIL_SIZE_OPTION } from '@/constants/image'; import { jumbotronData } from '@/constants/main_dummy'; import { opacityVariants } from '@/constants/variants'; import styles from './styles.module.scss'; @@ -181,7 +180,8 @@ export default function MainPage() {
ThumbnailImage @@ -223,9 +223,13 @@ export default function MainPage() {
{ navigate( diff --git a/src/pages/SignUp/AgentSignUp/index.tsx b/src/pages/SignUp/AgentSignUp/index.tsx index 9162b86..d684ad5 100644 --- a/src/pages/SignUp/AgentSignUp/index.tsx +++ b/src/pages/SignUp/AgentSignUp/index.tsx @@ -572,7 +572,7 @@ export default function AgentSignUpPage() { />
-

+

+ {isCheckEmail && '인증코드가 발송되었습니다.'} {errors.company_email && errors.company_email.message} {!errors.company_email && emailErrMessage && emailErrMessage}

+ {/* 이메일 인증 */} {isCheckEmail && (
@@ -656,11 +669,16 @@ export default function AgentSignUpPage() { @@ -702,11 +720,17 @@ export default function AgentSignUpPage() { @@ -809,11 +833,16 @@ export default function AgentSignUpPage() { className={ /^(?=.*[a-zA-Z0-9가-힣])[A-Za-z0-9가-힣]{1,20}$/g.test( watch('nick_name'), - ) + ) && !nicknameCheck ? signUpStyles.buttonStyleActive : signUpStyles.buttonStyle } onClick={nicknameCheckHandler} + disabled={ + !/^(?=.*[a-zA-Z0-9가-힣])[A-Za-z0-9가-힣]{1,20}$/g.test( + watch('nick_name'), + ) || nicknameCheck + } > 중복확인 @@ -849,16 +878,28 @@ export default function AgentSignUpPage() {
-

+

+ {isCheckNum && '인증문자가 발송되었습니다.'} {errors.phone_num && errors.phone_num.message} {!errors.phone_num && phoneErrMessage && phoneErrMessage}

@@ -884,11 +925,16 @@ export default function AgentSignUpPage() { diff --git a/src/pages/SignUp/index.tsx b/src/pages/SignUp/index.tsx index 5649681..5feb4b0 100644 --- a/src/pages/SignUp/index.tsx +++ b/src/pages/SignUp/index.tsx @@ -380,11 +380,17 @@ export default function SignUpPage() { @@ -479,11 +485,16 @@ export default function SignUpPage() { className={ /^(?=.*[a-zA-Z0-9가-힣])[A-Za-z0-9가-힣]{1,20}$/g.test( watch('nick_name'), - ) + ) && !nicknameCheck ? styles.buttonStyleActive : styles.buttonStyle } onClick={nicknameCheckHandler} + disabled={ + !/^(?=.*[a-zA-Z0-9가-힣])[A-Za-z0-9가-힣]{1,20}$/g.test( + watch('nick_name'), + ) || nicknameCheck + } > 중복확인 @@ -517,16 +528,24 @@ export default function SignUpPage() { -

+

+ {isCheckNum && '인증문자가 발송되었습니다.'} {errors.phone_num && errors.phone_num.message} {!errors.phone_num && phoneErrMessage}

@@ -552,11 +571,16 @@ export default function SignUpPage() { @@ -594,16 +618,26 @@ export default function SignUpPage() { className={ /^([\w\.\_\-])*[a-zA-Z0-9]+([\w\.\_\-])*([a-zA-Z0-9])+([\w\.\_\-])+@([a-zA-Z0-9]+\.)+[a-zA-Z0-9]{2,8}$/g.test( watch('email'), - ) + ) && !isCheckEmail ? styles.buttonStyleActive : styles.buttonStyle } onClick={onSendEmail} + disabled={ + !/^([\w\.\_\-])*[a-zA-Z0-9]+([\w\.\_\-])*([a-zA-Z0-9])+([\w\.\_\-])+@([a-zA-Z0-9]+\.)+[a-zA-Z0-9]{2,8}$/g.test( + watch('email'), + ) || isCheckEmail + } > 인증요청 -

+

+ {isCheckEmail && '인증코드가 발송되었습니다.'} {errors.email && errors.email.message} {!errors.email && emailErrMessage && emailErrMessage}

@@ -629,11 +663,16 @@ export default function SignUpPage() { diff --git a/src/pages/SignUp/styles.module.scss b/src/pages/SignUp/styles.module.scss index dbe5414..366c91c 100644 --- a/src/pages/SignUp/styles.module.scss +++ b/src/pages/SignUp/styles.module.scss @@ -208,6 +208,12 @@ &:hover { opacity: 0.8; } + &:disabled { + cursor: not-allowed; + &:hover { + opacity: 1; + } + } } .buttonStyleActive { @extend .buttonStyle; diff --git a/src/pages/Trade/Write/index.tsx b/src/pages/Trade/Write/index.tsx index d9c4140..a20f7c2 100644 --- a/src/pages/Trade/Write/index.tsx +++ b/src/pages/Trade/Write/index.tsx @@ -12,6 +12,7 @@ import { TradeBoardForm, } from '@/types/Board/tradeType'; import { uploadFile } from '@/apis/uploadS3'; +import { imageStore } from '@/store/imageStore'; import userStore from '@/store/userStore'; import { getRentalPriceType } from '@/utils/utils'; // import { DEFAULT_OPTIONS } from '@/constants/image'; @@ -24,6 +25,7 @@ const { VITE_S3_DOMAIN } = import.meta.env; export default function TradeWritePage() { const { user } = userStore(); + const { setImages } = imageStore(); const { state }: { state: { data: TradeBoardDetailType } } = useLocation(); @@ -53,6 +55,7 @@ export default function TradeWritePage() { const [thumbnailTitle, setThumbnailTitle] = useState( state ? state.data.imageUrls[0].split('/')[3] : '', ); + const thumbnailRef = useRef(null); // 매물특징 const [isPostcodeOpen, setIsPostcodeOpen] = useState(false); @@ -81,6 +84,7 @@ export default function TradeWritePage() { // const imageUrl = VITE_CLOUD_FRONT_DOMAIN + imageName + DEFAULT_OPTIONS; const imageUrl = VITE_S3_DOMAIN + imageName; setThumbnail(imageUrl); + setImages(imageUrl); } catch (error) { const err = error as AxiosError; return { ...err.response, success: false }; diff --git a/src/store/imageStore.ts b/src/store/imageStore.ts new file mode 100644 index 0000000..6e8d6ae --- /dev/null +++ b/src/store/imageStore.ts @@ -0,0 +1,13 @@ +import { create } from 'zustand'; + +export type ImageStore = { + images: string[]; + setImages: (imageUrl: string) => void; + resetImages: () => void; +}; + +export const imageStore = create((set, get) => ({ + images: [], + setImages: (imageUrl: string) => set({ images: [...get().images, imageUrl] }), + resetImages: () => set({ images: [] }), +})); diff --git a/src/utils/Quill/getNotUsedImageUrl.ts b/src/utils/Quill/getNotUsedImageUrl.ts new file mode 100644 index 0000000..40b0c92 --- /dev/null +++ b/src/utils/Quill/getNotUsedImageUrl.ts @@ -0,0 +1,5 @@ +const getNotUsedImageUrl = (origin: string[], final: string[]) => { + return origin.filter((url) => !final.includes(url)); +}; + +export default getNotUsedImageUrl; diff --git a/src/utils/Quill/imageHandler.ts b/src/utils/Quill/imageHandler.ts index ce6479a..c5d6596 100644 --- a/src/utils/Quill/imageHandler.ts +++ b/src/utils/Quill/imageHandler.ts @@ -9,7 +9,9 @@ const { VITE_S3_DOMAIN } = import.meta.env; const imageHandler = ( QuillRef: React.MutableRefObject, + setImages: (imageUrl: string) => void, ) => { + // const { setImages } = imageStore(); const input = document.createElement('input'); input.setAttribute('type', 'file'); @@ -26,6 +28,8 @@ const imageHandler = ( // const imageUrl = VITE_CLOUD_FRONT_DOMAIN + imageName + DEFAULT_OPTIONS; const imageUrl = VITE_S3_DOMAIN + imageName; + setImages(imageUrl); + const range = QuillRef.current?.getEditor().getSelection()?.index; if (range !== null && range !== undefined) { const quill = QuillRef.current?.getEditor(); diff --git a/src/utils/utils.ts b/src/utils/utils.ts index e94435b..763c734 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -108,7 +108,7 @@ export const getMoveInType = (isCompleted: boolean) => { export const checkBeforePost = ( title: string, contents: string, - imageUrl?: string[], + thumbnail?: string, ) => { if (title === '') { alert('제목을 입력해주세요.'); @@ -118,7 +118,7 @@ export const checkBeforePost = ( alert('내용을 입력해주세요.'); return false; } - if (imageUrl && imageUrl[0] === '') { + if (thumbnail === '') { alert('썸네일을 등록해주세요.'); return false; }