diff --git a/src/api/index.ts b/src/api/index.ts index a28c87b3..9be7995f 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -3,6 +3,7 @@ import axios, { AxiosResponse, InternalAxiosRequestConfig } from 'axios'; import API_PATH from 'config/constants'; import { RefreshParams, RefreshResponse } from 'model/auth'; +import useUploadToken from 'store/uploadToken'; const client = axios.create({ baseURL: `${API_PATH}`, @@ -14,6 +15,15 @@ const accessClient = axios.create({ timeout: 2000, }); +const multipartClient = axios.create({ + baseURL: `${API_PATH}`, + timeout: 2000, + headers: { + Authorization: `Bearer ${localStorage.getItem('upload_token')}`, + 'Content-Type': 'multipart/form-data', + }, +}); + const refresh = async (config: InternalAxiosRequestConfig) => { const refreshToken = localStorage.getItem('refresh_token'); @@ -63,4 +73,12 @@ accessClient.interceptors.response.use( }, ); -export { client, accessClient }; +multipartClient.interceptors.request.use( + (config) => { + const { uploadToken } = useUploadToken(); + config.headers.Authorization = `Bearer ${uploadToken}`; + config.headers['Content-Type'] = 'multipart/form-data'; + return config; + }, +); +export { client, accessClient, multipartClient }; diff --git a/src/api/register/index.ts b/src/api/register/index.ts index c143dfea..595bf4f5 100644 --- a/src/api/register/index.ts +++ b/src/api/register/index.ts @@ -1,6 +1,11 @@ -import { client } from 'api'; +import { client, multipartClient } from 'api'; import { - AuthCodeParam, AuthCodeResponse, EmailRegisterParam, EmailRegisterResponse, + AuthCodeParam, + AuthCodeResponse, + EmailRegisterParam, + EmailRegisterResponse, + FilesResponse, + RegisterParam, } from 'model/register'; export const getEmailDuplicate = async (param: string) => { @@ -17,3 +22,13 @@ export const verificationAuthCode = async (param:AuthCodeParam) => { const { data } = await client.post('/owners/verification/code', param); return AuthCodeResponse.parse(data); }; + +export const registerUser = async (param:RegisterParam) => { + const { status } = await client.post('/owners/register', param); + return status; +}; + +export const getFileUrls = async (param:FormData) => { + const { data } = await multipartClient.post('/owners/upload/files', param); + return FilesResponse.parse(data); +}; diff --git a/src/assets/svg/auth/default-file.svg b/src/assets/svg/auth/default-file.svg new file mode 100644 index 00000000..5477f31e --- /dev/null +++ b/src/assets/svg/auth/default-file.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/auth/search-glasses.svg b/src/assets/svg/auth/search-glasses.svg new file mode 100644 index 00000000..5515b005 --- /dev/null +++ b/src/assets/svg/auth/search-glasses.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/component/common/CustomModal/CustomModal.module.scss b/src/component/common/CustomModal/CustomModal.module.scss index 5ecd8d2c..0808852c 100644 --- a/src/component/common/CustomModal/CustomModal.module.scss +++ b/src/component/common/CustomModal/CustomModal.module.scss @@ -3,8 +3,8 @@ display: flex; align-items: center; justify-content: center; - width: 100%; - height: 100%; + width: 100vw; + height: 100vh; left: 0; top: 0; background-color: #00000066; diff --git a/src/model/register/index.ts b/src/model/register/index.ts index 8613e5d9..666c91a2 100644 --- a/src/model/register/index.ts +++ b/src/model/register/index.ts @@ -20,9 +20,22 @@ export const AuthCodeResponse = z.object({ export type AuthCodeResponse = z.infer; export const RegisterParam = z.object({ + attachment_urls: z.array(z.object({ + file_url: z.string(), + })), + company_number: z.string(), + shop_id: z.number().nullable(), email: z.string(), + name: z.string(), password: z.string(), - isAuthentication: z.boolean().refine((value) => value === true), + phone_number: z.string(), + shop_name: z.string(), }); export type RegisterParam = z.infer; + +export const FilesResponse = z.object({ + file_urls: z.array(z.string()), +}); + +export type FilesResponse = z.infer; diff --git a/src/page/Auth/Signup/SignUp.module.scss b/src/page/Auth/Signup/SignUp.module.scss index 8991256a..633327c4 100644 --- a/src/page/Auth/Signup/SignUp.module.scss +++ b/src/page/Auth/Signup/SignUp.module.scss @@ -36,7 +36,6 @@ &__steps { width: calc(100% + 2px); - height: 681px; display: flex; flex-direction: column; justify-content: space-between; diff --git a/src/page/Auth/Signup/component/CustomButton/CustomButton.module.scss b/src/page/Auth/Signup/component/CustomButton/CustomButton.module.scss index 0be3788a..111821a9 100644 --- a/src/page/Auth/Signup/component/CustomButton/CustomButton.module.scss +++ b/src/page/Auth/Signup/component/CustomButton/CustomButton.module.scss @@ -51,4 +51,22 @@ background-color: #c4c4c4; } } + + &--mobile-small { + width: 83px; + height: 37px; + font-weight: 500; + font-size: 15px; + background-color: #175c8e; + color: #ffffff; + border: none; + + &:hover { + cursor: pointer; + } + + &:disabled { + background-color: #c4c4c4; + } + } } diff --git a/src/page/Auth/Signup/component/CustomButton/index.tsx b/src/page/Auth/Signup/component/CustomButton/index.tsx index 1d0c1bee..37dc019a 100644 --- a/src/page/Auth/Signup/component/CustomButton/index.tsx +++ b/src/page/Auth/Signup/component/CustomButton/index.tsx @@ -2,7 +2,8 @@ import styles from './CustomButton.module.scss'; interface ButtonProps { content:string, - buttonSize: 'small' | 'mobile' | 'large', + // 80px | 부모 요소의 절반 width | 부모요소 전체 width | 83px + buttonSize: 'small' | 'mobile' | 'large' | 'mobile-small', disable?: boolean, submit?:boolean onClick?: () => void diff --git a/src/page/Auth/Signup/component/ErrorMessage/ErrorMessage.module.scss b/src/page/Auth/Signup/component/ErrorMessage/ErrorMessage.module.scss index 8a994870..a0457b0f 100644 --- a/src/page/Auth/Signup/component/ErrorMessage/ErrorMessage.module.scss +++ b/src/page/Auth/Signup/component/ErrorMessage/ErrorMessage.module.scss @@ -1,10 +1,9 @@ .warn { display: flex; align-items: center; - margin-top: 9px; - padding-bottom: 16px; gap: 7px; height: 11px; + margin: 8px 0 0; &--phrase { display: block; diff --git a/src/page/Auth/Signup/component/UserEmail/UserEmail.module.scss b/src/page/Auth/Signup/component/UserEmail/UserEmail.module.scss index 2b2c9e5e..8caf8eef 100644 --- a/src/page/Auth/Signup/component/UserEmail/UserEmail.module.scss +++ b/src/page/Auth/Signup/component/UserEmail/UserEmail.module.scss @@ -26,14 +26,12 @@ display: flex; flex-direction: column; margin-top: 8px; - margin-bottom: 16px; - gap: 8px; } .input { width: calc(100% - 32px); border: 1px solid #d2dae2; - height: calc(48px - 30px); + height: 18px; padding: 14px 16px; &::-webkit-inner-spin-button { @@ -55,6 +53,34 @@ border-bottom: 1px solid #f7941e; } } + + &--code { + width: calc(100% - 32px); + border: 1px solid #d2dae2; + height: calc(48px - 30px); + padding: 14px 16px; + margin-top: 8px; + + &::-webkit-inner-spin-button { + appearance: none; + margin: 0; + } + + @include media.media-breakpoint-down(mobile) { + width: 100%; + margin-top: 31px; + padding: 0; + padding-bottom: 8px; + font-size: 15px; + border: none; + border-bottom: 1px solid #d2dae2; + outline: none; + + &:focus { + border-bottom: 1px solid #f7941e; + } + } + } } &__alert { @@ -92,3 +118,7 @@ bottom: 0; } } + +.button { + margin-top: 18px; +} diff --git a/src/page/Auth/Signup/component/UserEmail/index.tsx b/src/page/Auth/Signup/component/UserEmail/index.tsx index 80771aa3..19e46ea5 100644 --- a/src/page/Auth/Signup/component/UserEmail/index.tsx +++ b/src/page/Auth/Signup/component/UserEmail/index.tsx @@ -1,30 +1,25 @@ import useMediaQuery from 'utils/hooks/useMediaQuery'; import CustomButton from 'page/Auth/Signup/component/CustomButton'; -import { RegisterData } from 'page/Auth/Signup/types/RegisterData'; import useValidateEmail from 'page/Auth/Signup/hooks/useValidateEmail'; import useAuthCheck from 'page/Auth/Signup/hooks/useAuthCheck'; import useVerification from 'page/Auth/Signup/hooks/useVerification'; import ErrorMessage from 'page/Auth/Signup/component/ErrorMessage'; +import useRegisterInfo from 'store/registerStore'; import styles from './UserEmail.module.scss'; -type ButtonClickEvent = { - goNext?: () => void; - userData: RegisterData, - setAuthenticate: (data:RegisterData) => void -}; - -export default function UserEmail({ userData, setAuthenticate }:ButtonClickEvent) { +export default function UserEmail() { const { isMobile } = useMediaQuery(); + const { userInfo: userData } = useRegisterInfo(); const { - emailHandleSubmit, errors: formErrors, emailDuplicateRegister, watch, + emailHandleSubmit, errors, emailDuplicateRegister, } = useValidateEmail(); const { - isOpen, onSubmit, errorMessage: requestError, email, refetch, + isOpen, onSubmit, email, refetch, } = useAuthCheck(userData.email ? userData.email : '', isMobile); const { verificationCode, codeInput, errorMessage: verificateError, - } = useVerification(email, setAuthenticate, userData); + } = useVerification(email); return ( !isMobile @@ -33,28 +28,30 @@ export default function UserEmail({ userData, setAuthenticate }:ButtonClickEvent 이메일 인증
- {isOpen && } + { errors.email && } + {isOpen && }
- { formErrors.email && } - {(!formErrors.email && requestError && email === watch().email) - && } - { verificateError && } {isOpen ? ( <> - * 제한시간 5 : 00 + { verificateError ? + : * 제한시간 5 : 00} +
+ +
+ + ) : ( +
- - ) : ( - +
)} ) diff --git a/src/page/Auth/Signup/component/UserId/UserId.module.scss b/src/page/Auth/Signup/component/UserId/UserId.module.scss index 1949f2d6..d31250e2 100644 --- a/src/page/Auth/Signup/component/UserId/UserId.module.scss +++ b/src/page/Auth/Signup/component/UserId/UserId.module.scss @@ -18,10 +18,6 @@ color: #858585; font-size: 11px; margin-top: 9px; - - // @include media.media-breakpoint-down(mobile) { - // margin: 0; - // } } &__warn { diff --git a/src/page/Auth/Signup/component/UserId/index.tsx b/src/page/Auth/Signup/component/UserId/index.tsx index 32578de1..9c20aa83 100644 --- a/src/page/Auth/Signup/component/UserId/index.tsx +++ b/src/page/Auth/Signup/component/UserId/index.tsx @@ -1,24 +1,18 @@ import useMediaQuery from 'utils/hooks/useMediaQuery'; import CustomButton from 'page/Auth/Signup/component/CustomButton'; import useValidateEmail from 'page/Auth/Signup/hooks/useValidateEmail'; -import { RegisterData } from 'page/Auth/Signup/types/RegisterData'; import useCheckEmailDuplicate from 'page/Auth/Signup/hooks/useCheckEmailDuplicate'; import ErrorMessage from 'page/Auth/Signup/component/ErrorMessage'; import styles from './UserId.module.scss'; -interface EmailInputProps { - setId: (data:RegisterData) => void, - userData: RegisterData -} - -export default function UserId({ setId, userData }:EmailInputProps) { +export default function UserId() { const { isMobile } = useMediaQuery(); const { emailHandleSubmit, errors: formErros, emailDuplicateRegister, watch, } = useValidateEmail(); const { status, onSubmit, onMobileSubmit, email, errorMessage: requestError, - } = useCheckEmailDuplicate(userData, setId, isMobile); + } = useCheckEmailDuplicate(isMobile); return (
void, - userData?: RegisterData -} - -export default function UserPassword({ setPassword, userData }:PasswordInputProps) { +export default function UserPassword() { const { isMobile } = useMediaQuery(); + const { userInfo: userData, setUserInfo: setPassword } = useRegisterInfo(); const { value: isBlind, changeValue: changeIsBlind } = useBooleanState(true); const { passwordRegister, passwordConfirmRegister, errors: formErrors, handleSubmit, } = usePasswordConfirm(); - const onSubmit:SubmitHandler = (data) => { + const onSubmit:SubmitHandler = (data) => { setPassword({ ...userData, password: data.password }); }; return ( diff --git a/src/page/Auth/Signup/hooks/useAuthCheck.ts b/src/page/Auth/Signup/hooks/useAuthCheck.ts index 89488bcf..475266d6 100644 --- a/src/page/Auth/Signup/hooks/useAuthCheck.ts +++ b/src/page/Auth/Signup/hooks/useAuthCheck.ts @@ -1,7 +1,7 @@ import { useGenerateAuthCode } from 'query/register'; import { useEffect, useState } from 'react'; import { SubmitHandler } from 'react-hook-form'; -import { RegisterData } from 'page/Auth/Signup/types/RegisterData'; +import { User } from 'page/Auth/Signup/types/User'; export default function useAuthCheck(userEmail:string, isMobile:boolean) { const [email, setEmail] = useState(''); @@ -11,7 +11,7 @@ export default function useAuthCheck(userEmail:string, isMobile:boolean) { status, refetch, isError, error, } = useGenerateAuthCode(email); - const onSubmit:SubmitHandler = (data) => { + const onSubmit:SubmitHandler = (data) => { setEmail(() => (data.email ? data.email : '')); }; diff --git a/src/page/Auth/Signup/hooks/useCheckEmailDuplicate.ts b/src/page/Auth/Signup/hooks/useCheckEmailDuplicate.ts index d7afb1bb..816656b9 100644 --- a/src/page/Auth/Signup/hooks/useCheckEmailDuplicate.ts +++ b/src/page/Auth/Signup/hooks/useCheckEmailDuplicate.ts @@ -1,21 +1,19 @@ import { useCheckDuplicate } from 'query/register'; import { useEffect, useState } from 'react'; import { SubmitHandler } from 'react-hook-form'; -import { RegisterData } from 'page/Auth/Signup/types/RegisterData'; +import { User } from 'page/Auth/Signup/types/User'; +import useRegisterInfo from 'store/registerStore'; -export default function useCheckEmailDuplicate( - userData:RegisterData, - setId: (data:RegisterData) => void, - isMobile: boolean, -) { +export default function useCheckEmailDuplicate(isMobile: boolean) { const [email, setEmail] = useState(''); const [errorMessage, setMessage] = useState(''); const { status, refetch, error } = useCheckDuplicate(email); + const { userInfo: userData, setUserInfo: setId } = useRegisterInfo(); - const onSubmit:SubmitHandler = (data) => { + const onSubmit:SubmitHandler = (data) => { setEmail(() => (data.email ? data.email : '')); }; - const onMobileSubmit:SubmitHandler = (data) => { + const onMobileSubmit:SubmitHandler = (data) => { if (isMobile) { onSubmit(data); } diff --git a/src/page/Auth/Signup/hooks/useCheckNext.ts b/src/page/Auth/Signup/hooks/useCheckNext.ts new file mode 100644 index 00000000..8fef90bb --- /dev/null +++ b/src/page/Auth/Signup/hooks/useCheckNext.ts @@ -0,0 +1,28 @@ +import { useState } from 'react'; +import { User, UserParam } from 'page/Auth/Signup/types/User'; +import { Owner, OwnerParam } from 'page/Auth/Signup/types/Owner'; + +export default function useCheckNext() { + const [isDone, setDone] = useState(false); + + const checkUserData = (userData:User) => { + if (UserParam.safeParse(userData).success) { + setDone(true); + } + }; + + const checkOwnerData = (data:Owner) => { + const ownerData = { + ownerName: data.ownerName, + shopName: data.shopName, + phoneNumber: `${data.phoneFront}-${data.phoneMiddle}-${data.phoneEnd}`, + registrationNumber: `${data.registrationNumberFront}-${data.registrationNumberMiddle}-${data.registrationNumberEnd}`, + registerFiles: data.registerFiles ? Array.from(data.registerFiles) : null, + }; + if (OwnerParam.safeParse(ownerData).success) { + setDone(true); + } + }; + + return { isDone, checkUserData, checkOwnerData }; +} diff --git a/src/page/Auth/Signup/hooks/useCheckNextStep.ts b/src/page/Auth/Signup/hooks/useCheckNextStep.ts deleted file mode 100644 index 342d6f90..00000000 --- a/src/page/Auth/Signup/hooks/useCheckNextStep.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { useState } from 'react'; -import { RegisterData } from 'page/Auth/Signup/types/RegisterData'; -import { RegisterParam } from 'model/register'; - -export default function useCheckNextStep() { - const [isDone, setDone] = useState(false); - - const checkNextStep = (userData:RegisterData) => { - if (RegisterParam.safeParse(userData).success) { - setDone(true); - } - }; - - return { isDone, checkNextStep }; -} diff --git a/src/page/Auth/Signup/hooks/useFileController.ts b/src/page/Auth/Signup/hooks/useFileController.ts new file mode 100644 index 00000000..e7c723f6 --- /dev/null +++ b/src/page/Auth/Signup/hooks/useFileController.ts @@ -0,0 +1,31 @@ +import useRegisterInfo from 'store/registerStore'; + +export default function useFileController() { + const { ownerInfo, setOwnerInfo } = useRegisterInfo(); + + const addFiles = (files:File[]) => { + if (ownerInfo.registerFiles) { + setOwnerInfo({ + ...ownerInfo, + registerFiles: [...files, ...ownerInfo.registerFiles].slice(0, 5), + }); + } else { + setOwnerInfo({ + ...ownerInfo, + registerFiles: Array.from(files).slice(0, 5), + }); + } + }; + + const deleteFile = (fileIndex:number) => { + const files = ownerInfo.registerFiles!.filter((file, index) => fileIndex !== index); + setOwnerInfo({ + ...ownerInfo, + registerFiles: files.length > 0 ? files : null, + }); + }; + + return { + deleteFile, addFiles, + }; +} diff --git a/src/page/Auth/Signup/hooks/useOwnerData.ts b/src/page/Auth/Signup/hooks/useOwnerData.ts new file mode 100644 index 00000000..6c64f990 --- /dev/null +++ b/src/page/Auth/Signup/hooks/useOwnerData.ts @@ -0,0 +1,233 @@ +import { useForm } from 'react-hook-form'; +import useModalStore from 'store/modalStore'; +import { useCallback, useEffect } from 'react'; +import useRegisterInfo from 'store/registerStore'; +import useFileController from './useFileController'; + +const VALIDATIONMESSAGE = { + ownerName: '대표자명을 입력해주세요', + shopName: '가게명을 입력해주세요', + registraionNumber: '사업자 등록번호를 입력해주세요', + phoneNumber: '대표자 연락처를 입력해주세요', + file: '파일을 첨부해주세요', +}; + +type OwnerInfo = { + ownerName:string, + shopName:string, + registrationNumberFront:string, + registrationNumberMiddle:string, + registrationNumberEnd:string, + registrationNumberMobile:string, + phoneFront:string, + phoneMiddle:string, + phoneEnd:string, + phoneMobile:string, + registerFiles:File[] +}; + +export default function useCheckOwnerData(isMobile:boolean) { + const { + register, + handleSubmit, + formState: { errors }, + watch, + setValue, + setFocus, + } = useForm({ mode: 'onSubmit' }); + const { ownerInfo: ownerData, setOwnerInfo: setOwnerData } = useRegisterInfo(); + const { addFiles } = useFileController(); + const ownerNameRegister = register('ownerName', { + required: { value: true, message: VALIDATIONMESSAGE.ownerName }, + onBlur: () => { + if (watch('ownerName') !== '') { + setOwnerData({ ...ownerData, ownerName: watch('ownerName') }); + } + }, + }); + + const shopNameRegister = register('shopName', { + required: { value: true, message: VALIDATIONMESSAGE.shopName }, + onBlur: () => { + if (watch('shopName') !== '') { + setOwnerData({ ...ownerData, shopName: watch('shopName') }); + } + }, + }); + + const registrationNumberRegister = { + front: register('registrationNumberFront', { + required: { value: !isMobile, message: VALIDATIONMESSAGE.registraionNumber }, + pattern: { + value: /[0-9]{3,3}$/, + message: '3자리 숫자를 입력해주세요.', + }, + onBlur: () => { + if (watch('registrationNumberFront') !== '') { + setOwnerData({ ...ownerData, registrationNumberFront: watch('registrationNumberFront') }); + } + }, + }), + middle: register('registrationNumberMiddle', { + required: { value: !isMobile, message: VALIDATIONMESSAGE.registraionNumber }, + pattern: { + value: /[0-9]{2,2}$/, + message: '2자리 숫자를 입력해주세요.', + }, + onBlur: () => { + if (watch('registrationNumberMiddle') !== '') { + setOwnerData({ ...ownerData, registrationNumberMiddle: watch('registrationNumberMiddle') }); + } + }, + }), + end: register('registrationNumberEnd', { + required: { value: !isMobile, message: VALIDATIONMESSAGE.registraionNumber }, + pattern: { + value: /[0-9]{5,5}$/, + message: '5자리 숫자를 입력해주세요.', + }, + onBlur: () => { + if (watch('registrationNumberEnd') !== '') { + setOwnerData({ ...ownerData, registrationNumberEnd: watch('registrationNumberEnd') }); + } + }, + }), + mobile: register('registrationNumberMobile', { + required: { value: isMobile, message: VALIDATIONMESSAGE.registraionNumber }, + pattern: { + value: /[0-9]{10,10}$/, + message: '-를 제외하고 모두 입력해주세요.', + }, + onBlur: () => { + if (watch('registrationNumberMobile') !== '') { + setOwnerData({ ...ownerData, registrationNumberMobile: watch('registrationNumberMobile') }); + } + }, + }), + message: errors.registrationNumberFront?.message + || errors.registrationNumberMiddle?.message + || errors.registrationNumberEnd?.message + || errors.registrationNumberMobile?.message, + }; + + const phoneNumberRegister = { + front: register('phoneFront', { + required: { value: !isMobile, message: VALIDATIONMESSAGE.phoneNumber }, + pattern: { + value: /[0-9]{3,3}$/, + message: '3자리 숫자를 입력해주세요.', + }, + onBlur: () => { + if (watch('phoneFront') !== '') { + setOwnerData({ ...ownerData, phoneFront: watch('phoneFront') }); + } + }, + }), + middle: register('phoneMiddle', { + required: { value: !isMobile, message: VALIDATIONMESSAGE.phoneNumber }, + pattern: { + value: /[0-9]{4,4}$/, + message: '4자리 숫자를 입력해주세요.', + }, + onBlur: () => { + if (watch('registrationNumberMiddle') !== '') { + setOwnerData({ ...ownerData, phoneMiddle: watch('phoneMiddle') }); + } + }, + }), + end: register('phoneEnd', { + required: { value: !isMobile, message: VALIDATIONMESSAGE.phoneNumber }, + pattern: { + value: /[0-9]{4,4}$/, + message: '4자리 숫자를 입력해주세요.', + }, + onBlur: () => { + if (watch('phoneEnd') !== '') { + setOwnerData({ ...ownerData, phoneEnd: watch('phoneEnd') }); + } + }, + }), + mobile: register('phoneMobile', { + required: { value: isMobile, message: VALIDATIONMESSAGE.phoneNumber }, + pattern: { + value: /[0-9]$/, + message: '-를 제외하고 모두 입력해주세요.', + }, + onBlur: () => { + if (watch('phoneMobile') !== '') { + setOwnerData({ ...ownerData, phoneMobile: watch('phoneMobile') }); + } + }, + }), + message: errors.phoneFront?.message + || errors.phoneMiddle?.message + || errors.phoneEnd?.message + || errors.phoneMobile?.message, + }; + + const fileRegister = register('registerFiles', { + required: { value: true, message: VALIDATIONMESSAGE.file }, + onChange: () => { + addFiles(watch('registerFiles')); + }, + validate: () => (ownerData.registerFiles && ownerData.registerFiles.length >= 3) || '파일을 3개 이상 첨부해주세요', + }); + + const getPhoneNumber = useCallback(() => [watch('phoneFront'), watch('phoneMiddle'), watch('phoneEnd')], [watch]); + const getRegisterationNumber = useCallback(() => [watch('registrationNumberFront'), watch('registrationNumberMiddle'), watch('registrationNumberEnd')], [watch]); + + const { searchShopState } = useModalStore(); + + useEffect(() => { + setValue('shopName', searchShopState); + }, [searchShopState, setValue]); + + const registerNumberFront = watch('registrationNumberFront') ? watch('registrationNumberFront') : null; + const registerNumberMiddle = watch('registrationNumberMiddle') ? watch('registrationNumberMiddle') : null; + + useEffect(() => { + if (registerNumberFront?.length === 3) { + setFocus('registrationNumberMiddle'); + } + }, [setFocus, registerNumberFront, setValue]); + + useEffect(() => { + if (registerNumberMiddle?.length === 2) { + setFocus('registrationNumberEnd'); + } + }, [setFocus, registerNumberMiddle]); + + const phoneFront = watch('phoneFront'); + const phoneMiddle = watch('phoneMiddle'); + + useEffect(() => { + if (phoneFront?.length === 3) { + setFocus('phoneMiddle'); + } + }, [phoneFront, setFocus]); + useEffect(() => { + if (phoneMiddle?.length === 4) { + setFocus('phoneEnd'); + } + }, [phoneMiddle, setFocus]); + + const shopName = watch('shopName'); + + useEffect(() => { + setFocus('shopName'); + }, [shopName, setFocus]); + + return { + ownerNameRegister, + shopNameRegister, + phoneNumberRegister, + registrationNumberRegister, + fileRegister, + errors, + watch, + handleSubmit, + setValue, + getPhoneNumber, + getRegisterationNumber, + }; +} diff --git a/src/page/Auth/Signup/hooks/usePasswordConfirm.ts b/src/page/Auth/Signup/hooks/usePasswordConfirm.ts index e9f7163b..70e6c2e4 100644 --- a/src/page/Auth/Signup/hooks/usePasswordConfirm.ts +++ b/src/page/Auth/Signup/hooks/usePasswordConfirm.ts @@ -11,8 +11,8 @@ export default function usePasswordConfirm() { const { register, formState: { errors }, - getValues, handleSubmit, + getValues, } = useForm(); const passwordRegister = register('password', { diff --git a/src/page/Auth/Signup/hooks/useValidateEmail.ts b/src/page/Auth/Signup/hooks/useValidateEmail.ts index 9f86a00b..3ba89cd5 100644 --- a/src/page/Auth/Signup/hooks/useValidateEmail.ts +++ b/src/page/Auth/Signup/hooks/useValidateEmail.ts @@ -1,5 +1,5 @@ import { useForm } from 'react-hook-form'; -import { RegisterData } from 'page/Auth/Signup/types/RegisterData'; +import { User } from 'page/Auth/Signup/types/User'; const REG_EX = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i; @@ -9,7 +9,7 @@ export default function useValidateEmail() { handleSubmit: emailHandleSubmit, formState: { errors }, watch, - } = useForm({ mode: 'onSubmit' }); + } = useForm({ mode: 'onSubmit' }); const emailDuplicateRegister = emailRegister('email', { required: { value: true, message: '이메일을 입력해주세요.' }, pattern: { diff --git a/src/page/Auth/Signup/hooks/useVerification.ts b/src/page/Auth/Signup/hooks/useVerification.ts index 1395b26f..00cdaef2 100644 --- a/src/page/Auth/Signup/hooks/useVerification.ts +++ b/src/page/Auth/Signup/hooks/useVerification.ts @@ -1,15 +1,14 @@ import { useVerificationAuthCode } from 'query/register'; import { useEffect, useRef, useState } from 'react'; -import { RegisterData } from 'page/Auth/Signup/types/RegisterData'; +import useRegisterInfo from 'store/registerStore'; export default function useVerification( eamil:string, - setAuthenticate:(data:RegisterData) => void, - userData:RegisterData, ) { const [code, setCode] = useState(''); const [errorMessage, setMessage] = useState(null); const codeInput = useRef(null); + const { userInfo: userData, setUserInfo: setAuthenticate } = useRegisterInfo(); const { status, refetch, isError, error, } = useVerificationAuthCode(code, eamil); diff --git a/src/page/Auth/Signup/index.tsx b/src/page/Auth/Signup/index.tsx index fc9e04b7..699dd463 100644 --- a/src/page/Auth/Signup/index.tsx +++ b/src/page/Auth/Signup/index.tsx @@ -21,7 +21,7 @@ export default function Signup() { const STEPS = [ , , - , + , ]; return (
diff --git a/src/page/Auth/Signup/types/Owner.ts b/src/page/Auth/Signup/types/Owner.ts new file mode 100644 index 00000000..b261bec8 --- /dev/null +++ b/src/page/Auth/Signup/types/Owner.ts @@ -0,0 +1,30 @@ +import { z } from 'zod'; + +interface Data { + ownerName:string, + shopName:string, + registrationNumberFront:string, + registrationNumberMiddle:string, + registrationNumberEnd:string, + registrationNumberMobile:string, + phoneFront:string, + phoneMiddle:string, + phoneEnd:string, + phoneMobile:string, + registerFiles:File[] | null +} +export type Owner = Partial; + +const File = z.object({ + name: z.string(), + size: z.number(), + type: z.string(), +}); + +export const OwnerParam = z.object({ + phoneNumber: z.string(), + ownerName: z.string(), + shopName: z.string(), + registrationNumber: z.string(), + registerFiles: z.array(File), +}); diff --git a/src/page/Auth/Signup/types/Register.ts b/src/page/Auth/Signup/types/Register.ts new file mode 100644 index 00000000..e361f78e --- /dev/null +++ b/src/page/Auth/Signup/types/Register.ts @@ -0,0 +1,7 @@ +import { Owner } from './Owner'; +import { User } from './User'; + +export type RegisterData = { + owner: Owner, + user: User +}; diff --git a/src/page/Auth/Signup/types/RegisterData.ts b/src/page/Auth/Signup/types/RegisterData.ts deleted file mode 100644 index db08d368..00000000 --- a/src/page/Auth/Signup/types/RegisterData.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type RegisterData = { - email?: string, - password?: string, - isAuthentication?: boolean -}; diff --git a/src/page/Auth/Signup/types/User.ts b/src/page/Auth/Signup/types/User.ts new file mode 100644 index 00000000..e9f33c27 --- /dev/null +++ b/src/page/Auth/Signup/types/User.ts @@ -0,0 +1,15 @@ +import { z } from 'zod'; + +interface Data { + email: string, + password: string, + isAuthentication: boolean +} + +export type User = Partial; + +export const UserParam = z.object({ + email: z.string(), + password: z.string(), + isAuthentication: z.boolean(), +}); diff --git a/src/page/Auth/Signup/utils/parseRegisterData.ts b/src/page/Auth/Signup/utils/parseRegisterData.ts new file mode 100644 index 00000000..1ce2e807 --- /dev/null +++ b/src/page/Auth/Signup/utils/parseRegisterData.ts @@ -0,0 +1,27 @@ +import { User } from 'page/Auth/Signup/types/User'; +import { Owner } from 'page/Auth/Signup/types/Owner'; + +const parseRegisterData = ( + userInfo:User, + ownerInfo:Owner, + fileUrls:string[] | undefined, + selectedShopId:string, + searchShopState:string, +) => { + const companyNumber = ownerInfo.registrationNumberMobile ? ownerInfo.registrationNumberMobile : `${ownerInfo.registrationNumberFront}-${ownerInfo.registrationNumberMiddle}-${ownerInfo.registrationNumberEnd}`; + const phoneNumber = ownerInfo.phoneMobile ? ownerInfo.phoneMobile : `${ownerInfo.phoneFront}-${ownerInfo.phoneMiddle}-${ownerInfo.phoneEnd}`; + const attachmentUrls = fileUrls!.map((file) => ({ file_url: `https://${file}` })); + const shopId = ownerInfo.shopName === searchShopState ? Number(selectedShopId) : null; + return { + attachment_urls: attachmentUrls, + company_number: companyNumber, + shop_id: shopId, + email: userInfo.email!, + name: ownerInfo.ownerName!, + password: userInfo.password!, + phone_number: phoneNumber, + shop_name: ownerInfo.shopName!, + }; +}; + +export default parseRegisterData; diff --git a/src/page/Auth/Signup/view/OwnerDataPage/OwnerData.module.scss b/src/page/Auth/Signup/view/OwnerDataPage/OwnerData.module.scss index 89f1060b..a1fa471e 100644 --- a/src/page/Auth/Signup/view/OwnerDataPage/OwnerData.module.scss +++ b/src/page/Auth/Signup/view/OwnerDataPage/OwnerData.module.scss @@ -3,16 +3,12 @@ .form { display: flex; flex-direction: column; - gap: 16px; - - @include media.media-breakpoint-down(mobile) { - gap: 24px; - } &__label { display: block; font-size: 18px; margin-bottom: 8px; + margin-top: 16px; @include media.media-breakpoint-down(mobile) { display: none; @@ -23,6 +19,8 @@ width: calc(100% - 34px); border: 1px solid #d2dae2; padding: 13px 16px; + height: 22px; + font-size: 14px; @include media.media-breakpoint-down(mobile) { width: 100%; @@ -33,6 +31,7 @@ padding: 0; font-size: 15px; padding-bottom: 8px; + margin-top: 24px; &::placeholder { color: #d2dae2; @@ -42,6 +41,10 @@ border-bottom: 1px solid #252525; color: #252525; } + + &:nth-child(1) { + margin-top: 0; + } } &__registration-number { @@ -54,6 +57,7 @@ text-align: center; width: calc(88px - 34px); padding: 10px 16px; + height: 22px; } &--middle { @@ -62,6 +66,7 @@ text-align: center; width: calc(72px - 52px); padding: 10px 26px; + height: 22px; } &--last { @@ -70,6 +75,48 @@ text-align: center; width: calc(144px - 94px); padding: 10px 47px; + height: 22px; + } + + &::after { + display: block; + content: ""; + width: 16px; + height: 0; + border: 1px solid black; + margin: 6px; + } + } + + &__phone-number { + display: flex; + align-items: center; + + &--first { + border: 1px solid #d2dae2; + font-size: 18px; + text-align: center; + width: calc(88px - 34px); + padding: 10px 16px; + height: 22px; + } + + &--middle { + border: 1px solid #d2dae2; + font-size: 18px; + text-align: center; + width: 108px; + padding: 10px 0; + height: 22px; + } + + &--last { + border: 1px solid #d2dae2; + font-size: 18px; + text-align: center; + width: 108px; + padding: 10px 0; + height: 22px; } &::after { @@ -87,18 +134,93 @@ .input { display: flex; align-items: center; + + &__shop-name { + &--wrapper { + display: flex; + align-items: end; + } + + width: calc(272px - 34px); + font-size: 14px; + border: 1px solid #d2dae2; + padding: 13px 16px; + margin-right: 16px; + height: 22px; + margin-top: 24px; + + @include media.media-breakpoint-down(mobile) { + width: 77%; + outline: none; + border: none; + border-bottom: 1px solid #d2dae2; + color: #d2dae2; + padding: 0; + font-size: 15px; + padding-bottom: 8px; + + &::placeholder { + color: #d2dae2; + } + + &:focus { + border-bottom: 1px solid #252525; + color: #252525; + } + } + } } .file-box { - width: calc(368px - 64px); - height: calc(218px - 85px); + display: block; + width: calc(368px - 50px); + height: calc(218px - 48px); border: 1px dashed #d2dae2; - padding: 45px 31px 40px; + padding: 24px; + overflow-y: scroll; + z-index: 999; + + &:hover { + border: 1px dashed #252525; + } + + &__input { + display: none; + } + + &.active { + background-color: #efeef3; + border: 1px dashed #252525; + } @include media.media-breakpoint-down(mobile) { width: calc(100% - 78px); height: calc(125px - 68px); - padding: 36px 38px 32px; + padding: 26px 38px 32px; + margin-top: 16px; + } + + &__files { + display: flex; + flex-direction: column; + gap: 16px; + } + + &__file { + all: unset; + display: flex; + gap: 8px; + align-items: center; + justify-content: start; + + &--name { + width: 90%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + font-size: 15px; + color: #858585; + } } &__command { @@ -106,6 +228,7 @@ font-size: 14px; display: block; margin-top: 21px; + pointer-events: none; @include media.media-breakpoint-down(mobile) { font-size: 12px; @@ -119,6 +242,7 @@ font-size: 14px; display: block; color: #858585; + pointer-events: none; @include media.media-breakpoint-down(mobile) { margin: 0; @@ -132,10 +256,12 @@ border: 1px solid #f7941e; border-radius: 13%; margin: 0 auto; + margin-top: 20px; display: flex; justify-content: center; align-items: center; position: relative; + pointer-events: none; @include media.media-breakpoint-down(mobile) { width: 18px; @@ -192,9 +318,7 @@ background-color: #ffffff; display: flex; justify-content: space-between; - position: absolute; - bottom: 0; - margin: 0; + margin-top: 16px; } &__mobile-button { diff --git a/src/page/Auth/Signup/view/OwnerDataPage/index.tsx b/src/page/Auth/Signup/view/OwnerDataPage/index.tsx index 8c21e041..6e004919 100644 --- a/src/page/Auth/Signup/view/OwnerDataPage/index.tsx +++ b/src/page/Auth/Signup/view/OwnerDataPage/index.tsx @@ -1,59 +1,179 @@ +/* eslint-disable jsx-a11y/label-has-associated-control */ import useMediaQuery from 'utils/hooks/useMediaQuery'; import CustomButton from 'page/Auth/Signup/component/CustomButton'; +import useBooleanState from 'utils/hooks/useBooleanState'; +import SearchShop from 'page/ShopRegistration/component/Modal/SearchShop'; +import { ReactComponent as FileImage } from 'assets/svg/auth/default-file.svg'; +import CustomModal from 'component/common/CustomModal'; +import useCheckOwnerData from 'page/Auth/Signup/hooks/useOwnerData'; +import ErrorMessage from 'page/Auth/Signup/component/ErrorMessage'; +import useFileController from 'page/Auth/Signup/hooks/useFileController'; +import useCheckNext from 'page/Auth/Signup/hooks/useCheckNext'; +import { useEffect } from 'react'; +import useRegisterInfo from 'store/registerStore'; +import { useGetFileUrls } from 'query/register'; import styles from './OwnerData.module.scss'; type ButtonClickEvent = { - clickEvent: () => void; + goNext: () => void; }; -export default function OwnerData({ clickEvent }:ButtonClickEvent) { +export default function OwnerData({ goNext }:ButtonClickEvent) { const { isMobile } = useMediaQuery(); + const { isDone, checkOwnerData } = useCheckNext(); + const { ownerInfo: ownerData } = useRegisterInfo(); + const fileMuation = useGetFileUrls(goNext); + const { + value: isOpen, + setTrue: openSearchShop, + setFalse: closeSearchShop, + } = useBooleanState(false); + const { value: isActive, setTrue: setActive, setFalse: setUnActive } = useBooleanState(false); + const { + ownerNameRegister, + shopNameRegister, + registrationNumberRegister, + fileRegister, + phoneNumberRegister, + handleSubmit, + errors, + } = useCheckOwnerData(isMobile); + + const { addFiles, deleteFile } = useFileController(); + + useEffect(() => { + checkOwnerData(ownerData); + }, [checkOwnerData, ownerData]); + + const onSubmit = () => { + fileMuation.mutate(); + }; + + const onClick = (index:number, event:React.MouseEvent) => { + event.preventDefault(); + deleteFile(index); + }; + + const handleDragOver = (event:React.DragEvent) => { + event.preventDefault(); + setActive(); + }; + + const handleDrop = (event:React.DragEvent) => { + event.preventDefault(); + setUnActive(); + const file = Array.from(event.dataTransfer.files); + addFiles(file); + }; + return ( <>
대표자명 - + +
+ {errors.ownerName && } +
+ 가게명 +
+ + +
+ {errors.shopName && }
사업자등록번호 {!isMobile ? (
- +
- +
- +
) - : } + : }
+ {(registrationNumberRegister.message) + && } +
+ 개인 연락처 + {!isMobile ? ( +
+
+ +
+
+ +
+
+ +
+
+ ) + : } +
+ {(phoneNumberRegister.message) && }
파일첨부 -
-
- - {!isMobile ? '파일을 마우스로 끌어 오세요.' : '파일을 첨부해주세요'} - - {!isMobile ? '사업자등록증, 영업신고증, 통장사본 이미지 필수10mb 이하의 PDF 혹은 이미지 형식의 파일(e.g. jpg, png, gif 등)로 5개까지 업로드 가능합니다.' : '사업자등록증, 영업신고증, 통장사본 이미지 첨부'} - -
+
- +
+ {isOpen && ( + + + + )} ); } diff --git a/src/page/Auth/Signup/view/TermsOfServicePage/TermsOfService.module.scss b/src/page/Auth/Signup/view/TermsOfServicePage/TermsOfService.module.scss index 70cc8e62..77ed2694 100644 --- a/src/page/Auth/Signup/view/TermsOfServicePage/TermsOfService.module.scss +++ b/src/page/Auth/Signup/view/TermsOfServicePage/TermsOfService.module.scss @@ -53,6 +53,8 @@ width: 100%; @include media.media-breakpoint-down(mobile) { + position: absolute; + bottom: 0; width: 100%; background-color: #ffffff; display: flex; diff --git a/src/page/Auth/Signup/view/UserDataPage/UserData.module.scss b/src/page/Auth/Signup/view/UserDataPage/UserData.module.scss index a079d42b..39583a04 100644 --- a/src/page/Auth/Signup/view/UserDataPage/UserData.module.scss +++ b/src/page/Auth/Signup/view/UserDataPage/UserData.module.scss @@ -7,13 +7,16 @@ } .buttons { + margin-top: 145px; + @include media.media-breakpoint-down(mobile) { + position: absolute; + bottom: 0; width: 100%; + margin: 0; background-color: #ffffff; display: flex; justify-content: space-between; - position: absolute; - bottom: 0; } &__mobile-button { diff --git a/src/page/Auth/Signup/view/UserDataPage/index.tsx b/src/page/Auth/Signup/view/UserDataPage/index.tsx index 4b7b7782..c0397062 100644 --- a/src/page/Auth/Signup/view/UserDataPage/index.tsx +++ b/src/page/Auth/Signup/view/UserDataPage/index.tsx @@ -4,33 +4,32 @@ import UserPassword from 'page/Auth/Signup/component/UserPassword'; import useMediaQuery from 'utils/hooks/useMediaQuery'; import CustomButton from 'page/Auth/Signup/component/CustomButton'; import { useEffect, useState } from 'react'; -import { RegisterData } from 'page/Auth/Signup/types/RegisterData'; +import { User, UserParam } from 'page/Auth/Signup/types/User'; import useStepStore from 'store/useStepStore'; -import useCheckNextStep from 'page/Auth/Signup/hooks/useCheckNextStep'; -import { RegisterParam } from 'model/register'; +import useCheckNext from 'page/Auth/Signup/hooks/useCheckNext'; +import useRegisterInfo from 'store/registerStore'; import styles from './UserData.module.scss'; type ButtonClickEventProps = { goNext: () => void; }; -const useCheckEmailStep = () => { +const useCheckEmail = () => { const [isFilled, setIsFilled] = useState(false); - const checkEmailStep = (userData:RegisterData) => { - if (RegisterParam.pick({ email: true, password: true }).safeParse(userData).success) { + const checkEmail = (userData:User) => { + if (UserParam.pick({ email: true, password: true }).safeParse(userData).success) { setIsFilled(true); } }; - return { isFilled, checkEmailStep }; + return { isFilled, checkEmail }; }; export default function UserData({ goNext }:ButtonClickEventProps) { const { isMobile } = useMediaQuery(); - const [userData, setData] = useState({}); - const { isDone, checkNextStep } = useCheckNextStep(); + const { userInfo } = useRegisterInfo(); + const { isDone, checkUserData } = useCheckNext(); const { increaseStep, step } = useStepStore(); const [registerStep, setRegisterStep] = useState(0); - const { isFilled, checkEmailStep } = useCheckEmailStep(); - + const { isFilled, checkEmail } = useCheckEmail(); useEffect(() => { if (step === 1) { setRegisterStep(0); @@ -39,10 +38,10 @@ export default function UserData({ goNext }:ButtonClickEventProps) { useEffect(() => { if (isMobile) { - checkEmailStep(userData); + checkEmail(userInfo); } - checkNextStep(userData); - }, [userData, checkNextStep, isMobile, checkEmailStep]); + checkUserData(userInfo); + }, [userInfo, checkUserData, isMobile, checkEmail]); const goEmailAuth = () => { setRegisterStep(1); @@ -54,9 +53,9 @@ export default function UserData({ goNext }:ButtonClickEventProps) { ? ( <>
- - - + + +
@@ -68,10 +67,10 @@ export default function UserData({ goNext }:ButtonClickEventProps) {
{registerStep === 0 ? ( <> - - + + - ) : } + ) : }
{registerStep === 0 && } diff --git a/src/page/ShopRegistration/component/Modal/ConfirmShop/ConfirmShop.module.scss b/src/page/ShopRegistration/component/Modal/ConfirmShop/ConfirmShop.module.scss index 2ad85174..12697662 100644 --- a/src/page/ShopRegistration/component/Modal/ConfirmShop/ConfirmShop.module.scss +++ b/src/page/ShopRegistration/component/Modal/ConfirmShop/ConfirmShop.module.scss @@ -15,6 +15,10 @@ } &__title { + display: inline-block; + max-width: 200px; + max-height: 93px; + overflow-y: hidden; font-size: 20px; font-weight: 600; } @@ -36,6 +40,7 @@ &__select-button { width: 144px; + max-width: 144px; height: 48px; background-color: #175c8e; margin-right: 31px; diff --git a/src/page/ShopRegistration/component/Modal/ConfirmShop/index.tsx b/src/page/ShopRegistration/component/Modal/ConfirmShop/index.tsx index 03074942..be03d1e4 100644 --- a/src/page/ShopRegistration/component/Modal/ConfirmShop/index.tsx +++ b/src/page/ShopRegistration/component/Modal/ConfirmShop/index.tsx @@ -4,6 +4,7 @@ import styles from './ConfirmShop.module.scss'; interface ShopInfo { name: string; phone: string; + id: string; } interface ConfirmShopProps { @@ -13,7 +14,7 @@ interface ConfirmShopProps { } export default function ConfirmShop({ open, onCancel, selectedShop }: ConfirmShopProps) { - const { setSearchShopState: setSearchStoreState } = useModalStore(); + const { setSearchShopState: setSearchStoreState, setSelectedShopId } = useModalStore(); if (!open) return null; return (
@@ -29,6 +30,7 @@ export default function ConfirmShop({ open, onCancel, selectedShop }: ConfirmSho className={styles['container__select-button']} onClick={() => { setSearchStoreState(selectedShop.name); + setSelectedShopId(selectedShop.id); onCancel(); }} > diff --git a/src/page/ShopRegistration/component/Modal/SearchShop/SearchShop.module.scss b/src/page/ShopRegistration/component/Modal/SearchShop/SearchShop.module.scss index 5111c3f6..7213a482 100644 --- a/src/page/ShopRegistration/component/Modal/SearchShop/SearchShop.module.scss +++ b/src/page/ShopRegistration/component/Modal/SearchShop/SearchShop.module.scss @@ -56,6 +56,9 @@ } &__title { + display: block; + max-width: 220px; + text-align: start; font-size: 15px; } diff --git a/src/page/ShopRegistration/component/Modal/SearchShop/index.tsx b/src/page/ShopRegistration/component/Modal/SearchShop/index.tsx index 06c1ad1e..885d9e54 100644 --- a/src/page/ShopRegistration/component/Modal/SearchShop/index.tsx +++ b/src/page/ShopRegistration/component/Modal/SearchShop/index.tsx @@ -15,6 +15,7 @@ export default function SearchShop({ open, onCancel }: SearchShopProps) { const [selectedStore, setSelectedStore] = useState({ name: '', phone: '', + id: '', }); const { value: showConfirmStore, setValue: setConfirmStore } = useBooleanState(false); const [searchText, setSearchText] = useState(''); @@ -22,16 +23,18 @@ export default function SearchShop({ open, onCancel }: SearchShopProps) { const { shopList, isError } = useShopList(); function handleClickStore(e: React.MouseEvent) { - const { name, phone } = JSON.parse(e.currentTarget.value); + const { name, phone, id } = JSON.parse(e.currentTarget.value); if (!showConfirmStore) { setSelectedStore({ name, phone, + id, }); } else { setSelectedStore({ name: '', phone: '', + id: '', }); } } @@ -94,6 +97,7 @@ export default function SearchShop({ open, onCancel }: SearchShopProps) { value={JSON.stringify({ name: shop.name, phone: shop.phone, + id: shop.id, })} type="button" onClick={(e) => { diff --git a/src/page/StoreRegistration/component/Modal/ConfirmStore/index.tsx b/src/page/StoreRegistration/component/Modal/ConfirmStore/index.tsx new file mode 100644 index 00000000..af12e62c --- /dev/null +++ b/src/page/StoreRegistration/component/Modal/ConfirmStore/index.tsx @@ -0,0 +1,32 @@ +import styles from './ConfirmStore.module.scss'; + +interface ConfirmStoreProps { + open: boolean; + onCancel: () => void; + store:{ + name:string, + tel:string + } +} + +export default function ConfirmStore({ open, onCancel, store }: ConfirmStoreProps) { + if (!open) return null; + return ( +
+
+ {store.name} +
+ 전화번호 + {store.tel} +
+
+ +
+ ); +} diff --git a/src/page/StoreRegistration/hooks/useSearchStoreName.ts b/src/page/StoreRegistration/hooks/useSearchStoreName.ts new file mode 100644 index 00000000..1aecffdc --- /dev/null +++ b/src/page/StoreRegistration/hooks/useSearchStoreName.ts @@ -0,0 +1,29 @@ +import { useRef, useState } from 'react'; + +export default function useSearchStoreName(stores :Array<{ + name:string, tel: string, card:boolean, deliver:boolean, account:boolean +}>) { + const [fillteredStores, setStores] = useState(stores); + const searchInput = useRef(null); + + const onClickSearchButton = (event:React.FormEvent) => { + event.preventDefault(); + if (searchInput.current!.value !== '') { + setStores(stores.filter(( + store, + ) => store.name.includes(searchInput.current!.value))); + } else { + setStores(stores); + } + }; + + const onKeyPress = (e:React.KeyboardEvent) => { + if (e.key === 'Enter') { + onClickSearchButton(e); + } + }; + + return { + searchInput, onClickSearchButton, fillteredStores, onKeyPress, + }; +} diff --git a/src/query/register.ts b/src/query/register.ts index cd2df470..56edf765 100644 --- a/src/query/register.ts +++ b/src/query/register.ts @@ -1,5 +1,10 @@ -import { useQuery } from '@tanstack/react-query'; -import { getEmailAuthCode, getEmailDuplicate, verificationAuthCode } from 'api/register'; +import { useMutation, useQuery } from '@tanstack/react-query'; +import { + getEmailAuthCode, getEmailDuplicate, getFileUrls, registerUser, verificationAuthCode, +} from 'api/register'; +import parseRegisterData from 'page/Auth/Signup/utils/parseRegisterData'; +import useModalStore from 'store/modalStore'; +import useRegisterInfo from 'store/registerStore'; import useUploadToken from 'store/uploadToken'; export const useCheckDuplicate = (email:string) => { @@ -42,3 +47,52 @@ export const useVerificationAuthCode = (code:string, email:string) => { status, refetch, isError, error, }; }; + +export const useRegisterUser = (goNext:()=>void) => { + const { userInfo, ownerInfo, resetRegisterInfo } = useRegisterInfo(); + const { selectedShopId, searchShopState } = useModalStore(); + const register = useMutation({ + mutationKey: ['registerUser'], + mutationFn: (fileUrls:string[]) => ( + registerUser(parseRegisterData( + userInfo, + ownerInfo, + fileUrls, + selectedShopId, + searchShopState, + )) + ), + onSuccess: () => { + goNext(); + resetRegisterInfo(); + }, + onError: () => { + alert('에러가 발생했습니다. 처음부터 다시 진행해주세요'); + resetRegisterInfo(); + }, + }); + return { register }; +}; + +export const useGetFileUrls = (goNext:()=>void) => { + const { ownerInfo, resetRegisterInfo } = useRegisterInfo(); + const { register } = useRegisterUser(goNext); + const formData = new FormData(); + ownerInfo.registerFiles?.forEach((file) => { + formData.append('files', file); + }); + const fileMutation = useMutation({ + mutationKey: ['getFileUrls'], + mutationFn: () => getFileUrls(formData), + onSuccess: (data) => { + register.mutate(data.file_urls); + }, + onError: () => { + alert('에러가 발생했습니다. 처음부터 다시 진행해주세요'); + resetRegisterInfo(); + }, + + }); + + return fileMutation; +}; diff --git a/src/store/modalStore.ts b/src/store/modalStore.ts index db155d39..ef1de221 100644 --- a/src/store/modalStore.ts +++ b/src/store/modalStore.ts @@ -6,11 +6,13 @@ type OperatingTime = { [key in typeof WEEK[number]]: string | null }; interface ModalStore { categoryState: [string, number]; searchShopState: string; + selectedShopId:string; openTimeState: OperatingTime; closeTimeState: OperatingTime; shopClosedState: { [key: string]: boolean }; setCategoryState: (state: [string, number]) => void; setSearchShopState: (state: string) => void; + setSelectedShopId:(state:string) => void; setOpenTimeState: (state: OperatingTime) => void; setCloseTimeState: (state: OperatingTime) => void; setShopClosedState: (state: { [key: string]: boolean }) => void; @@ -39,11 +41,13 @@ const initialShopClosed = { const useModalStore = create((set) => ({ categoryState: ['', 0], searchShopState: '', + selectedShopId: '', openTimeState: initialOperatingTime, closeTimeState: initialOperatingTime, shopClosedState: initialShopClosed, setCategoryState: (state) => set({ categoryState: state }), setSearchShopState: (state) => set({ searchShopState: state }), + setSelectedShopId: (state) => set({ selectedShopId: state }), setOpenTimeState: (state) => set(() => ({ openTimeState: state })), setCloseTimeState: (state) => set(() => ({ closeTimeState: state })), setShopClosedState: (state) => set({ shopClosedState: state }), diff --git a/src/store/registerStore.ts b/src/store/registerStore.ts new file mode 100644 index 00000000..f98d1536 --- /dev/null +++ b/src/store/registerStore.ts @@ -0,0 +1,24 @@ +import { Owner } from 'page/Auth/Signup/types/Owner'; +import { User } from 'page/Auth/Signup/types/User'; +import { create } from 'zustand'; + +interface RegisterInfo { + userInfo:User, + ownerInfo:Owner + setOwnerInfo:(data:Owner) => void, + setUserInfo:(data:User) => void, + resetRegisterInfo: () => void +} + +const useRegisterInfo = create((set) => ({ + userInfo: {}, + ownerInfo: {}, + setUserInfo: (data) => set({ userInfo: data }), + setOwnerInfo: (data) => set({ ownerInfo: data }), + resetRegisterInfo: () => set({ + userInfo: {}, + ownerInfo: {}, + }), +})); + +export default useRegisterInfo;