From ec25da3148dd644dd9839bbd977ec9ed9dfb22a2 Mon Sep 17 00:00:00 2001
From: chaeseungyun <101871802+chaeseungyun@users.noreply.github.com>
Date: Thu, 13 Feb 2025 18:44:30 +0900
Subject: [PATCH] =?UTF-8?q?[=EB=B9=84=EC=A6=88=EB=8B=88=EC=8A=A4]=20?=
=?UTF-8?q?=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20=ED=94=8C=EB=A1=9C?=
=?UTF-8?q?=EC=9A=B0=20=EB=B0=8F=20=EB=A9=94=EB=89=B4=20=ED=8E=B8=EC=A7=91?=
=?UTF-8?q?=20=EA=B0=9C=EC=84=A0=20(#389)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* feat: validate 성공 시 아이콘 추가
* feat: Auth 공용 Form 컴포넌트 제작
* style: box-sizing 값 조정
* refactor: model 재정의
* refactor: 다음 버튼을 재사용 하도록 변경 및 기본값 추가
* feat: Auth 공용 컴포넌트 제작
* feat: input 숨기기 버튼 컴포넌트화
* refactor: 비밀번호 찾기에 공용 컴포넌트 적용
* refactor: useSteps에서 불필요한 값 제거거
* refactor: 공용 컴포넌트 적용
* refactor: 커스텀훅으로 개편 및 컴포넌트화 적용
* refactor: 불필요한 기능 삭제 및 css 조정
* feat: 회원가입 플로우 개선
* style: secondary 색상 변경
* refactor: 라우팅 세팅
* refactor: 메뉴 편집 개편
* style: 버튼 색상 조정
* feat: 메뉴 편집탭 추가
* style: lint 수정
* style: 메뉴추가 버튼 위치 및 스타일 변경
* style: lint 수정
---
src/App.tsx | 2 +
src/assets/svg/auth/success.svg | 3 +
src/component/common/Header/index.tsx | 11 +-
src/model/auth/index.ts | 2 +
src/page/AddMenu/AddMenu.module.scss | 6 +-
.../MenuCategory/MenuCategory.module.scss | 3 +-
.../AddMenu/components/MenuCategory/index.tsx | 3 +-
.../MenuImage/MenuImage.module.scss | 13 +-
.../AddMenu/components/MenuImage/index.tsx | 37 +-
.../components/MenuName/MenuName.module.scss | 3 +-
.../PriceInput/PriceInput.module.scss | 11 +-
.../MenuPrice/components/PriceInput/index.tsx | 2 +-
.../FindPassword/ChangePassword/index.tsx | 44 +-
src/page/Auth/FindPassword/Verify/index.tsx | 149 ++---
src/page/Auth/FindPassword/index.module.scss | 3 +
src/page/Auth/FindPassword/index.tsx | 2 +-
.../agreeStep/agreeStep.module.scss | 19 +-
.../Signup/components/agreeStep/index.tsx | 22 +-
.../AttachStep/AttachStep.module.scss | 117 ++++
.../components/onwerStep/AttachStep/index.tsx | 188 +++++++
.../onwerStep/common/common.module.scss | 13 +
.../components/onwerStep/common/model.ts | 3 +
.../Signup/components/onwerStep/index.tsx | 28 +
.../components/onwerStep/nameStep/index.tsx | 37 ++
.../onwerStep/onwerNumberStep/index.tsx | 40 ++
.../components/onwerStep/shopStep/index.tsx | 65 +++
.../Signup/components/ownerInfoStep/index.tsx | 106 +---
.../ownerInfoStep/ownerInfoStep.module.scss | 2 -
.../Signup/components/phoneStep/index.tsx | 521 +++++++++++-------
.../phoneStep/phoneStep.module.scss | 77 +--
.../Signup/components/searchShop/index.tsx | 146 +++--
.../searchShop/searchShop.module.scss | 11 +-
src/page/Auth/Signup/index.tsx | 50 +-
.../components/Common/BlindButton/index.tsx | 20 +
.../components/Common/form/form.module.scss | 80 +++
.../Auth/components/Common/form/index.tsx | 115 ++++
.../Auth/components/Common/index.module.scss | 6 +-
src/page/Auth/components/Common/index.tsx | 143 +----
src/page/Auth/hook/useStep.ts | 9 +-
src/page/MyShopPage/MyShopPage.module.scss | 12 +
.../components/EditMenu/index.module.scss | 12 +
.../MyShopPage/components/EditMenu/index.tsx | 29 +
.../MenuTable/MenuTable.module.scss | 17 +-
.../MyShopPage/components/MenuTable/index.tsx | 56 +-
.../components/ShopInfo/ShopInfo.module.scss | 7 +-
src/page/MyShopPage/index.tsx | 25 +-
src/static/routes.ts | 1 +
src/utils/constant/category.ts | 14 +
src/utils/hooks/useClickImage.tsx | 15 +
49 files changed, 1520 insertions(+), 780 deletions(-)
create mode 100644 src/assets/svg/auth/success.svg
create mode 100644 src/page/Auth/Signup/components/onwerStep/AttachStep/AttachStep.module.scss
create mode 100644 src/page/Auth/Signup/components/onwerStep/AttachStep/index.tsx
create mode 100644 src/page/Auth/Signup/components/onwerStep/common/common.module.scss
create mode 100644 src/page/Auth/Signup/components/onwerStep/common/model.ts
create mode 100644 src/page/Auth/Signup/components/onwerStep/index.tsx
create mode 100644 src/page/Auth/Signup/components/onwerStep/nameStep/index.tsx
create mode 100644 src/page/Auth/Signup/components/onwerStep/onwerNumberStep/index.tsx
create mode 100644 src/page/Auth/Signup/components/onwerStep/shopStep/index.tsx
create mode 100644 src/page/Auth/components/Common/BlindButton/index.tsx
create mode 100644 src/page/Auth/components/Common/form/form.module.scss
create mode 100644 src/page/Auth/components/Common/form/index.tsx
create mode 100644 src/page/MyShopPage/components/EditMenu/index.module.scss
create mode 100644 src/page/MyShopPage/components/EditMenu/index.tsx
create mode 100644 src/utils/hooks/useClickImage.tsx
diff --git a/src/App.tsx b/src/App.tsx
index 16d99b4b..792f3085 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -20,6 +20,7 @@ import LogPage from 'component/common/PageLog';
import CommonLayout from 'page/Auth/components/Common';
import FindPassword from 'page/Auth/FindPassword';
import ROUTES from 'static/routes';
+import EditMenu from 'page/MyShopPage/components/EditMenu';
interface ProtectedRouteProps {
userTypeRequired: UserType;
@@ -51,6 +52,7 @@ function App() {
} />
} />
} />
+ } />
} />
} />
} />
diff --git a/src/assets/svg/auth/success.svg b/src/assets/svg/auth/success.svg
new file mode 100644
index 00000000..7dda695c
--- /dev/null
+++ b/src/assets/svg/auth/success.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/component/common/Header/index.tsx b/src/component/common/Header/index.tsx
index e43daf9b..f5f77cbf 100644
--- a/src/component/common/Header/index.tsx
+++ b/src/component/common/Header/index.tsx
@@ -12,8 +12,10 @@ function Header() {
const { isMobile } = useMediaQuery();
if ((pathname === ROUTES.Owner.AddMenu()
- || pathname.startsWith(ROUTES.Owner.ModifyMenu({ isLink: false }))
- || pathname.startsWith(ROUTES.Owner.Event({ isLink: false })))
+ || pathname.includes('modify-menu')
+ || pathname.startsWith(ROUTES.Owner.Event({ isLink: false }))
+ || pathname.startsWith(ROUTES.Owner.EditMenu())
+ )
&& isMobile) {
return (
@@ -26,9 +28,10 @@ function Header() {
- {pathname === ROUTES.Owner.AddMenu() && '메뉴추가'}
- {pathname.startsWith(ROUTES.Owner.ModifyMenu({ isLink: false })) && '메뉴수정'}
+ {pathname === ROUTES.Owner.AddMenu() && '메뉴 추가'}
+ {pathname.includes('modify-menu') && '메뉴 수정'}
{pathname.startsWith(ROUTES.Owner.Event({ isLink: false })) && '이벤트/공지 작성하기'}
+ {pathname.startsWith(ROUTES.Owner.EditMenu()) && '메뉴 편집'}
);
diff --git a/src/model/auth/index.ts b/src/model/auth/index.ts
index aee45b98..9efe17f9 100644
--- a/src/model/auth/index.ts
+++ b/src/model/auth/index.ts
@@ -93,6 +93,8 @@ export interface Register extends FindPassword {
attachment_urls: {
file_url: string
}[],
+ verificationCode: string;
+ shop_call: string;
}
export interface RegisterUser {
diff --git a/src/page/AddMenu/AddMenu.module.scss b/src/page/AddMenu/AddMenu.module.scss
index 724a17c2..0c37aa4d 100644
--- a/src/page/AddMenu/AddMenu.module.scss
+++ b/src/page/AddMenu/AddMenu.module.scss
@@ -33,13 +33,14 @@
width: 94px;
height: 43px;
flex-shrink: 0;
- border: 1px solid #f7941e;
+ border: 1px solid #cacaca;
background-color: white;
- color: #f7941e;
+ color: #cacaca;
text-align: center;
font-family: "Noto Sans CJK KR", sans-serif;
font-size: 18px;
font-weight: 500;
+ border-radius: 8px;
&:hover {
cursor: pointer;
@@ -56,6 +57,7 @@
font-family: "Noto Sans CJK KR", sans-serif;
font-size: 18px;
font-weight: 500;
+ border-radius: 8px;
&:hover {
cursor: pointer;
diff --git a/src/page/AddMenu/components/MenuCategory/MenuCategory.module.scss b/src/page/AddMenu/components/MenuCategory/MenuCategory.module.scss
index 3c74c2ee..8a3f6abb 100644
--- a/src/page/AddMenu/components/MenuCategory/MenuCategory.module.scss
+++ b/src/page/AddMenu/components/MenuCategory/MenuCategory.module.scss
@@ -115,12 +115,13 @@ $button-text-color: #252525;
&__category-button {
height: 39px;
- border: 0.5px solid #898a8d;
+ border: 0.5px solid #cacaca;
background-color: white;
color: $button-text-color;
text-align: center;
font-size: 15px;
font-weight: 500;
+ border-radius: 8px;
&--selected {
background-color: #f7941e;
diff --git a/src/page/AddMenu/components/MenuCategory/index.tsx b/src/page/AddMenu/components/MenuCategory/index.tsx
index ec0ca008..30990a6c 100644
--- a/src/page/AddMenu/components/MenuCategory/index.tsx
+++ b/src/page/AddMenu/components/MenuCategory/index.tsx
@@ -66,10 +66,9 @@ export default function MenuCategory({ isComplete }:MenuCategoryProps) {
메뉴 카테고리
- (최대 선택 n개)
+ (1개 이상 선택)
-
{shopData && shopData.menu_categories.map((category) => (
diff --git a/src/page/AddMenu/components/MenuImage/MenuImage.module.scss b/src/page/AddMenu/components/MenuImage/MenuImage.module.scss
index ab17ce6f..e372e853 100644
--- a/src/page/AddMenu/components/MenuImage/MenuImage.module.scss
+++ b/src/page/AddMenu/components/MenuImage/MenuImage.module.scss
@@ -132,6 +132,7 @@
gap: 16px;
white-space: nowrap;
align-items: center;
+ justify-content: space-around;
height: 170px;
}
@@ -140,8 +141,8 @@
justify-content: center;
align-items: center;
flex-direction: column;
- width: 137px;
- height: 137px;
+ width: 100px;
+ height: 100px;
flex-shrink: 0;
border: 0.5px solid #a1a1a1;
background-color: white;
@@ -168,13 +169,13 @@
justify-content: center;
align-items: center;
position: relative;
- width: 137px;
- height: 137px;
+ width: 100px;
+ height: 100px;
}
&__selected {
- max-width: 137px;
- max-height: 137px;
+ width: 100px;
+ height: 100px;
}
}
diff --git a/src/page/AddMenu/components/MenuImage/index.tsx b/src/page/AddMenu/components/MenuImage/index.tsx
index de1d0867..425a3125 100644
--- a/src/page/AddMenu/components/MenuImage/index.tsx
+++ b/src/page/AddMenu/components/MenuImage/index.tsx
@@ -58,31 +58,32 @@ export default function MenuImage({ isComplete }: MenuImageProps) {
(최대 이미지 3장)
- {!isComplete && (
-
- )}
{imageUrl.map((image, index) => (

{!isComplete && (
-
+
)}
))}
+ {!isComplete && (
+ Array.from({ length: 3 - imageUrl.length }).map(() => (
+
+ )))}
- 사이즈 추가
+ 가격 추가
>
diff --git a/src/page/Auth/FindPassword/ChangePassword/index.tsx b/src/page/Auth/FindPassword/ChangePassword/index.tsx
index be650442..06ce440d 100644
--- a/src/page/Auth/FindPassword/ChangePassword/index.tsx
+++ b/src/page/Auth/FindPassword/ChangePassword/index.tsx
@@ -1,20 +1,50 @@
import { useEffect } from 'react';
-import { useFormContext } from 'react-hook-form';
+import { useFormContext, UseFormSetError } from 'react-hook-form';
import { useOutletContext } from 'react-router-dom';
import { ChangePasswordForm } from 'model/auth';
import { OutletProps } from 'page/Auth/FindPassword/entity';
import Warning from 'assets/svg/auth/warning.svg?react';
import styles from 'page/Auth/FindPassword/index.module.scss';
+import { isKoinError, sendClientError } from '@bcsdlab/koin';
+import { Button } from 'page/Auth/components/Common/form';
+import sha256 from 'utils/ts/SHA-256';
+import { changePassword } from 'api/auth';
+import { useMutation } from '@tanstack/react-query';
+
+interface ChangePasswordProps {
+ setError: UseFormSetError;
+ nextStep: () => void;
+}
+
+const useChangePassword = ({ setError, nextStep } : ChangePasswordProps) => {
+ const mutate = useMutation({
+ mutationFn: async (data: { phone_number: string, password: string }) => {
+ const hashPassword = await sha256(data.password);
+ changePassword({ phone_number: data.phone_number, password: hashPassword });
+ },
+ onError: (e) => {
+ if (isKoinError(e)) {
+ setError('password', { type: 'custom', message: e.message });
+ } else {
+ sendClientError(e);
+ }
+ },
+ onSuccess: () => nextStep(),
+ });
+
+ return mutate;
+};
const passwordRegex = /^(?=.*[a-zA-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{6,18}$/;
export default function ChangePassword() {
const {
- register, formState: { errors, isValid }, getValues, clearErrors,
+ register, formState: { errors, isValid }, getValues, clearErrors, setError,
} = useFormContext();
const steps = useOutletContext();
const { setIsStepComplete } = steps;
+ const mutation = useChangePassword({ setError, nextStep: steps.nextStep });
useEffect(() => {
if (isValid) {
@@ -57,7 +87,6 @@ export default function ChangePassword() {
)
}
-
새 비밀번호 확인
@@ -78,6 +107,15 @@ export default function ChangePassword() {
)
}
+
);
}
diff --git a/src/page/Auth/FindPassword/Verify/index.tsx b/src/page/Auth/FindPassword/Verify/index.tsx
index c88ee542..aec78436 100644
--- a/src/page/Auth/FindPassword/Verify/index.tsx
+++ b/src/page/Auth/FindPassword/Verify/index.tsx
@@ -11,6 +11,7 @@ import styles from 'page/Auth/FindPassword/index.module.scss';
import { OutletProps } from 'page/Auth/FindPassword/entity';
import { useDebounce } from 'utils/hooks/useDebounce';
import showToast from 'utils/ts/showToast';
+import { Button } from 'page/Auth/components/Common/form';
interface SendCode {
getValues: UseFormGetValues;
@@ -43,7 +44,7 @@ interface Verify {
certification_code: string;
}
-export default function Verify() {
+export default function Verify({ nextStep }: { nextStep: () => void }) {
const {
register, getValues, setError, formState: { errors }, watch, clearErrors,
} = useFormContext();
@@ -90,79 +91,80 @@ export default function Verify() {
return (
);
}
diff --git a/src/page/Auth/FindPassword/index.module.scss b/src/page/Auth/FindPassword/index.module.scss
index eb995044..3094ba81 100644
--- a/src/page/Auth/FindPassword/index.module.scss
+++ b/src/page/Auth/FindPassword/index.module.scss
@@ -1,5 +1,8 @@
.container {
height: calc(100vh - 30vh);
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
}
.section {
diff --git a/src/page/Auth/FindPassword/index.tsx b/src/page/Auth/FindPassword/index.tsx
index 30d2ecfd..16567e56 100644
--- a/src/page/Auth/FindPassword/index.tsx
+++ b/src/page/Auth/FindPassword/index.tsx
@@ -8,7 +8,7 @@ export default function FindPassword() {
const { index } = steps;
return (
<>
- {index === 0 && }
+ {index === 0 && }
{index === 1 && }
>
);
diff --git a/src/page/Auth/Signup/components/agreeStep/agreeStep.module.scss b/src/page/Auth/Signup/components/agreeStep/agreeStep.module.scss
index bcca501c..1f40c3ff 100644
--- a/src/page/Auth/Signup/components/agreeStep/agreeStep.module.scss
+++ b/src/page/Auth/Signup/components/agreeStep/agreeStep.module.scss
@@ -6,7 +6,9 @@
.step-one {
height: 100%;
- overflow: scroll;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
&::-webkit-scrollbar {
display: none;
@@ -41,7 +43,6 @@
}
.personal {
- margin-top: 4px;
height: 28vh;
@include media.media-breakpoint-down(mobile) {
@@ -62,16 +63,22 @@
}
&__content {
- margin-top: 5px;
border: 1px dashed #d2dae2;
padding: 12px;
- height: 20vh;
- overflow-y: scroll;
+
+ &--text {
+ overflow-y: auto;
+ font-size: 11px;
+ height: 20vh;
+
+ &::-webkit-scrollbar {
+ display: none;
+ }
+ }
}
}
.koin {
- margin-top: 4px;
height: 28vh;
@include media.media-breakpoint-down(mobile) {
diff --git a/src/page/Auth/Signup/components/agreeStep/index.tsx b/src/page/Auth/Signup/components/agreeStep/index.tsx
index 23f88ec6..7144e446 100644
--- a/src/page/Auth/Signup/components/agreeStep/index.tsx
+++ b/src/page/Auth/Signup/components/agreeStep/index.tsx
@@ -1,6 +1,7 @@
import NonCheck from 'assets/svg/auth/non-check.svg?react';
import Check from 'assets/svg/auth/checked.svg?react';
import TERMS from 'page/Auth/Signup/constant/terms';
+import { Button } from 'page/Auth/components/Common/form';
import styles from './agreeStep.module.scss';
interface SelectOptions {
@@ -11,9 +12,10 @@ interface SelectOptions {
interface AgreeStepProps {
selectItems: SelectOptions;
handleSelect: (option: keyof SelectOptions | 'all') => void;
+ nextStep: () => void;
}
-export default function AgreeStep({ selectItems, handleSelect }: AgreeStepProps) {
+export default function AgreeStep({ selectItems, handleSelect, nextStep }: AgreeStepProps) {
return (
@@ -27,15 +29,29 @@ export default function AgreeStep({ selectItems, handleSelect }: AgreeStepProps)
{selectItems.personal ?
:
}
개인정보 이용약관(필수)
-
{TERMS[0].text}
+
-
{TERMS[1].text}
+
+
);
}
diff --git a/src/page/Auth/Signup/components/onwerStep/AttachStep/AttachStep.module.scss b/src/page/Auth/Signup/components/onwerStep/AttachStep/AttachStep.module.scss
new file mode 100644
index 00000000..49970bc2
--- /dev/null
+++ b/src/page/Auth/Signup/components/onwerStep/AttachStep/AttachStep.module.scss
@@ -0,0 +1,117 @@
+@use "src/utils/styles/mediaQuery" as media;
+
+.container {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ height: 100%;
+}
+
+.owner-info-container {
+ margin-top: 25px;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ padding: 0 1px;
+ box-sizing: border-box;
+}
+
+.owner-info-container input {
+ width: 100%;
+ box-sizing: border-box;
+ border-radius: 4px;
+ background: #f5f5f5;
+ padding: 12px 16px;
+ border: none;
+ margin-top: 8px;
+ margin-bottom: 2px;
+
+ &:focus {
+ outline: 1px solid #4590bb;
+ }
+
+ &:hover {
+ cursor: pointer;
+ }
+}
+
+.owner-info-container button {
+ &:hover {
+ cursor: pointer;
+ }
+}
+
+.owner-info-container span {
+ font-size: 14px;
+ font-style: normal;
+ font-weight: 500;
+ padding-left: 5px;
+ padding-bottom: 8px;
+}
+
+.document-input {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+
+ &__condition {
+ color: #8e8e8e;
+ font-size: 12px;
+ font-weight: 400;
+ display: flex;
+ justify-content: space-between;
+ }
+}
+
+.shop-name {
+ &__input {
+ width: 72% !important;
+ }
+}
+
+.owner-file-input {
+ &__button {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 5px;
+ margin-top: 5px;
+ height: 46px;
+ width: 100%;
+ border-radius: 5px;
+ background: #cacaca;
+ color: #4b4b4b;
+ z-index: 2;
+ }
+}
+
+.file-list {
+ border-radius: 5px;
+ background: #f5f5f5;
+ max-height: 178px;
+ padding: 10px;
+ width: 94%;
+ margin-top: 10px;
+}
+
+.file-card {
+ display: flex;
+ gap: 11px;
+}
+
+.file-name {
+ color: #8e8e8e;
+ font-size: 14px;
+ font-weight: 400;
+}
+
+.delete-button {
+ background-color: #f5f5f5;
+}
+
+.error-message {
+ display: flex;
+ align-items: center;
+ color: #f7941e;
+ font-size: 12px;
+}
diff --git a/src/page/Auth/Signup/components/onwerStep/AttachStep/index.tsx b/src/page/Auth/Signup/components/onwerStep/AttachStep/index.tsx
new file mode 100644
index 00000000..1e1e633d
--- /dev/null
+++ b/src/page/Auth/Signup/components/onwerStep/AttachStep/index.tsx
@@ -0,0 +1,188 @@
+import { Register } from 'model/auth';
+import {
+ Button, Input, Title, ValidationMessage,
+} from 'page/Auth/components/Common/form';
+import { useFormContext } from 'react-hook-form';
+import { useState } from 'react';
+import FileIcon from 'assets/svg/auth/file-icon.svg?react';
+import DeleteFile from 'assets/svg/auth/delete-file.svg?react';
+import FileUploadModal from 'page/Auth/Signup/components/fileUploadModal';
+import useUploadFile from 'query/upload';
+import { isKoinError, sendClientError } from '@bcsdlab/koin';
+import showToast from 'utils/ts/showToast';
+import { toast } from 'react-toastify';
+import { useMutation } from '@tanstack/react-query';
+import { phoneRegisterUser } from 'api/register';
+import sha256 from 'utils/ts/SHA-256';
+import { DefaultProps } from 'page/Auth/Signup/components/onwerStep/common/model';
+import styles from './AttachStep.module.scss';
+
+interface OwnerInfo {
+ name: string;
+ shop_name: string;
+ shop_id: number | null;
+ company_number: string;
+ attachment_urls: { file_url: string }[];
+}
+
+function UploadStep() {
+ const {
+ formState: { errors },
+ setValue,
+ } = useFormContext();
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const { uploadFiles } = useUploadFile();
+ const [fileNames, setFileNames] = useState([]);
+ const [uploadedFiles, setUploadedFiles] = useState<{ file_url: string }[]>([]);
+
+ const openModal = () => setIsModalOpen(true);
+ const closeModal = () => setIsModalOpen(false);
+
+ const handleUpload = async (files: FileList) => {
+ const formData = new FormData();
+ const names = Array.from(files).map((file) => file.name);
+
+ Array.from(files).forEach((file) => formData.append('files', file));
+ try {
+ const response = await uploadFiles(formData);
+ const { file_urls: fileUrls } = response.data;
+ const formattedUrls = fileUrls.map((url: string) => ({ file_url: url }));
+ if (formattedUrls.length + uploadedFiles.length > 5) {
+ toast.error('파일은 최대 5개 등록할 수 있습니다');
+ return;
+ }
+ setUploadedFiles((prev) => [...prev, ...formattedUrls]);
+ setFileNames((prev) => [...prev, ...names]);
+ setValue('attachment_urls', [...uploadedFiles, ...formattedUrls]);
+ } catch (error) {
+ if (isKoinError(error)) {
+ showToast('error', error.message);
+ }
+ sendClientError(error);
+ }
+ };
+
+ const handleDeleteFile = (index: number) => {
+ const newUploadedFiles = [...uploadedFiles];
+ const newFileNames = [...fileNames];
+ newUploadedFiles.splice(index, 1);
+ newFileNames.splice(index, 1);
+ setUploadedFiles(newUploadedFiles);
+ setFileNames(newFileNames);
+ setValue('attachment_urls', newUploadedFiles);
+ };
+
+ return (
+
+
+
사업자 인증 파일
+
+
사업자 등록증, 영업신고증, 통장사본을 첨부하세요.
+
+ {fileNames.length}
+ {' '}
+ / 5
+
+
+ {fileNames.length > 0 && (
+
+ {fileNames.map((name, index) => (
+
+
+
+ {name}
+
+
+ ))}
+
+ )}
+
+ {errors.attachment_urls &&
{errors.attachment_urls.message}}
+
+ {isModalOpen &&
}
+
+ );
+}
+interface PhoneRegister {
+ company_number: string,
+ name: string,
+ password: string,
+ phone_number: string,
+ shop_id: number | null,
+ shop_name: string,
+ attachment_urls: { file_url: string }[],
+}
+
+const useRegister = (onSuccess: () => void) => useMutation({
+ mutationFn: (data: PhoneRegister) => phoneRegisterUser(data),
+ onError: (error) => {
+ if (isKoinError(error)) {
+ showToast('error', error.message);
+ }
+ sendClientError(error);
+ },
+ onSuccess,
+});
+
+export default function AttachStep({ nextStep }: DefaultProps) {
+ const {
+ register, formState: { errors }, watch, handleSubmit,
+ } = useFormContext();
+ const [shopCall, attachmentUrls] = watch(['shop_call', 'attachment_urls']);
+ const mutation = useRegister(nextStep);
+ const onwerSignup = async (data: Register) => {
+ const hashedPassword = await sha256(data.password);
+ const shopId = Number(data.shop_id) ? Number(data.shop_id) : null;
+ const companyNumber = `${data.company_number.slice(0, 3)}-${data.company_number.slice(3, 5)}-${data.company_number.slice(5)}`;
+ const processedData = {
+ company_number: companyNumber,
+ name: data.name,
+ password: hashedPassword,
+ phone_number: data.phone_number,
+ shop_id: shopId,
+ shop_name: data.shop_name,
+ attachment_urls: data.attachment_urls,
+ };
+ mutation.mutate(processedData);
+ };
+
+ return (
+
+ );
+}
diff --git a/src/page/Auth/Signup/components/onwerStep/common/common.module.scss b/src/page/Auth/Signup/components/onwerStep/common/common.module.scss
new file mode 100644
index 00000000..4e5442dc
--- /dev/null
+++ b/src/page/Auth/Signup/components/onwerStep/common/common.module.scss
@@ -0,0 +1,13 @@
+.container {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ height: 100%;
+}
+
+.center {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 20px;
+}
diff --git a/src/page/Auth/Signup/components/onwerStep/common/model.ts b/src/page/Auth/Signup/components/onwerStep/common/model.ts
new file mode 100644
index 00000000..03225d49
--- /dev/null
+++ b/src/page/Auth/Signup/components/onwerStep/common/model.ts
@@ -0,0 +1,3 @@
+export interface DefaultProps {
+ nextStep: () => void;
+}
diff --git a/src/page/Auth/Signup/components/onwerStep/index.tsx b/src/page/Auth/Signup/components/onwerStep/index.tsx
new file mode 100644
index 00000000..1f9747f8
--- /dev/null
+++ b/src/page/Auth/Signup/components/onwerStep/index.tsx
@@ -0,0 +1,28 @@
+import { useState } from 'react';
+import AttachStep from './AttachStep';
+import NameStep from './nameStep';
+import OnwerNumberStep from './onwerNumberStep';
+import ShopStep from './shopStep';
+
+interface Props {
+ complete: () => void;
+}
+
+export default function OwnerStep({ complete }: Props) {
+ const [steps, setSteps] = useState(0);
+ const nextStep = () => setSteps((prev) => prev + 1);
+
+ if (steps === 0) return nextStep()} />;
+
+ if (steps === 1) {
+ return nextStep()} />;
+ }
+
+ if (steps === 2) {
+ return nextStep()} />;
+ }
+
+ return (
+
+ );
+}
diff --git a/src/page/Auth/Signup/components/onwerStep/nameStep/index.tsx b/src/page/Auth/Signup/components/onwerStep/nameStep/index.tsx
new file mode 100644
index 00000000..fe3f59c7
--- /dev/null
+++ b/src/page/Auth/Signup/components/onwerStep/nameStep/index.tsx
@@ -0,0 +1,37 @@
+import { Register } from 'model/auth';
+import {
+ Button, Input, Title, ValidationMessage,
+} from 'page/Auth/components/Common/form';
+import { useFormContext } from 'react-hook-form';
+import styles from 'page/Auth/Signup/components/onwerStep/common/common.module.scss';
+import { DefaultProps } from 'page/Auth/Signup/components/onwerStep/common/model';
+
+export default function NameStep({ nextStep }: DefaultProps) {
+ const { register, formState: { errors }, watch } = useFormContext();
+ const name = watch('name');
+
+ return (
+
+ );
+}
diff --git a/src/page/Auth/Signup/components/onwerStep/onwerNumberStep/index.tsx b/src/page/Auth/Signup/components/onwerStep/onwerNumberStep/index.tsx
new file mode 100644
index 00000000..91726306
--- /dev/null
+++ b/src/page/Auth/Signup/components/onwerStep/onwerNumberStep/index.tsx
@@ -0,0 +1,40 @@
+import {
+ Button, Input, Title, ValidationMessage,
+} from 'page/Auth/components/Common/form';
+import { useFormContext } from 'react-hook-form';
+import styles from 'page/Auth/Signup/components/onwerStep/common/common.module.scss';
+import { DefaultProps } from 'page/Auth/Signup/components/onwerStep/common/model';
+import { Register } from 'model/auth';
+
+export default function OnwerNumberStep({ nextStep } : DefaultProps) {
+ const { register, formState: { errors }, watch } = useFormContext();
+ const ownerNumber = watch('company_number');
+
+ return (
+
+ );
+}
diff --git a/src/page/Auth/Signup/components/onwerStep/shopStep/index.tsx b/src/page/Auth/Signup/components/onwerStep/shopStep/index.tsx
new file mode 100644
index 00000000..35cd9e70
--- /dev/null
+++ b/src/page/Auth/Signup/components/onwerStep/shopStep/index.tsx
@@ -0,0 +1,65 @@
+import { Register } from 'model/auth';
+import {
+ Button, Input, Title, ValidationMessage,
+} from 'page/Auth/components/Common/form';
+import { useFormContext } from 'react-hook-form';
+import SearchIcon from 'assets/svg/auth/search-glasses.svg?url';
+import styles from 'page/Auth/Signup/components/onwerStep/common/common.module.scss';
+import { DefaultProps } from 'page/Auth/Signup/components/onwerStep/common/model';
+import { useEffect, useState } from 'react';
+import SearchShop from 'page/Auth/Signup/components/searchShop';
+
+export default function ShopStep({ nextStep }: DefaultProps) {
+ const {
+ register, formState: { errors }, watch, clearErrors, setValue,
+ } = useFormContext();
+ const [isShowSearch, setIsShowSearch] = useState(false);
+ const shopName = watch('shop_name');
+
+ const onChange = () => {
+ setValue('shop_id', null);
+ };
+
+ useEffect(() => {
+ if (shopName) {
+ clearErrors('shop_name');
+ }
+ }, [shopName, clearErrors]);
+
+ if (isShowSearch) return setIsShowSearch(false)} />;
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/page/Auth/Signup/components/ownerInfoStep/index.tsx b/src/page/Auth/Signup/components/ownerInfoStep/index.tsx
index bda6e0c8..e1b7e6d6 100644
--- a/src/page/Auth/Signup/components/ownerInfoStep/index.tsx
+++ b/src/page/Auth/Signup/components/ownerInfoStep/index.tsx
@@ -1,5 +1,5 @@
import { useFormContext } from 'react-hook-form';
-import { useState, useEffect, ChangeEvent } from 'react';
+import { useState } from 'react';
import FileIcon from 'assets/svg/auth/file-icon.svg?react';
import DeleteFile from 'assets/svg/auth/delete-file.svg?react';
import FileUploadModal from 'page/Auth/Signup/components/fileUploadModal';
@@ -17,15 +17,8 @@ interface OwnerInfo {
attachment_urls: { file_url: string }[];
}
-interface OwnerInfoStepProps {
- onSearch: () => void;
- setIsStepComplete: (state: boolean) => void;
-}
-
-export default function OwnerInfoStep({ onSearch, setIsStepComplete }: OwnerInfoStepProps) {
+export default function UploadStep() {
const {
- register,
- watch,
formState: { errors },
setValue,
} = useFormContext();
@@ -71,103 +64,8 @@ export default function OwnerInfoStep({ onSearch, setIsStepComplete }: OwnerInfo
setValue('attachment_urls', newUploadedFiles);
};
- const formatCompanyNumber = (value: string) => {
- const cleaned = value.replace(/\D/g, '');
- const match = cleaned.match(/^(\d{0,3})(\d{0,2})(\d{0,5})$/);
- if (match) {
- return [match[1], match[2], match[3]].filter(Boolean).join('-');
- }
- return value;
- };
-
- const handleCompanyNumberChange = (e: ChangeEvent) => {
- const inputValue = e.target.value;
- const cleanedValue = inputValue.replace(/\D/g, '');
- if (cleanedValue.length <= 10) {
- const formattedValue = formatCompanyNumber(cleanedValue);
- setValue('company_number', formattedValue);
- }
- if (cleanedValue.length > 10) {
- e.target.value = cleanedValue.slice(0, 10);
- const formattedValue = formatCompanyNumber(e.target.value);
- setValue('company_number', formattedValue);
- }
- };
-
- const watchedValues = watch(['name', 'shop_name', 'company_number', 'attachment_urls']);
-
- useEffect(() => {
- const values = watch();
- const isComplete = values.name && values.shop_name
- && values.company_number
- && values.attachment_urls;
- setIsStepComplete(!!isComplete);
- }, [watchedValues, setIsStepComplete, watch]);
-
return (
-
- 대표자명(실명)
-
- {errors.name && {errors.name.message}}
-
-
-
가게명
-
-
-
-
- {errors.shop_name &&
{errors.shop_name.message}}
-
-
- 사업자 등록번호
-
- {errors.company_number && {errors.company_number.message}}
-
사업자 인증 파일
diff --git a/src/page/Auth/Signup/components/ownerInfoStep/ownerInfoStep.module.scss b/src/page/Auth/Signup/components/ownerInfoStep/ownerInfoStep.module.scss
index 0c4a86bd..e6374789 100644
--- a/src/page/Auth/Signup/components/ownerInfoStep/ownerInfoStep.module.scss
+++ b/src/page/Auth/Signup/components/ownerInfoStep/ownerInfoStep.module.scss
@@ -69,8 +69,6 @@
.owner-file-input {
&__button {
- position: absolute;
- bottom: -40px;
display: flex;
align-items: center;
justify-content: center;
diff --git a/src/page/Auth/Signup/components/phoneStep/index.tsx b/src/page/Auth/Signup/components/phoneStep/index.tsx
index d5bacaf9..6aadf860 100644
--- a/src/page/Auth/Signup/components/phoneStep/index.tsx
+++ b/src/page/Auth/Signup/components/phoneStep/index.tsx
@@ -1,113 +1,183 @@
-import Error from 'assets/svg/auth/error-icon.svg?react';
import {
useFormContext, UseFormGetValues, UseFormSetError,
} from 'react-hook-form';
import {
- useState, useEffect, ChangeEvent,
+ useState, ChangeEvent, useEffect,
} from 'react';
-import cn from 'utils/ts/className';
import { verificationAuthCode, getPhoneAuthCode } from 'api/register';
import { isKoinError, sendClientError } from '@bcsdlab/koin';
-import { useDebounce } from 'utils/hooks/useDebounce';
import { PhoneNumberRegisterParam } from 'model/register';
import showToast from 'utils/ts/showToast';
+import {
+ Button, Input, Title, ValidationMessage,
+} from 'page/Auth/components/Common/form';
+import { useMutation } from '@tanstack/react-query';
+import BlindButton from 'page/Auth/components/Common/BlindButton';
+import { Register } from 'model/auth';
+import { Link } from 'react-router-dom';
+import ROUTES from 'static/routes';
import styles from './phoneStep.module.scss';
-interface PhoneStepProps {
- setIsStepComplete: React.Dispatch
>;
-}
-
-interface Verify {
- phone_number: string;
- attachment_urls: {
- file_url: string
- }[];
- verificationCode: string;
- password: string;
- passwordConfirm: string;
+interface SendCodeParams {
+ getValues: UseFormGetValues;
+ onError: (e: unknown) => void;
+ onSucess?: () => void;
+ onClick?: () => void;
}
-interface SendCodeParams {
- getValues: UseFormGetValues;
- setError: UseFormSetError;
- setIsSent: React.Dispatch>;
- setIsClick: React.Dispatch>;
+interface TimerProps {
+ seconds: number;
+ setSeconds: React.Dispatch>;
+ time?: number;
+ timeOut?: () => void;
+ onMid?: () => void;
+ stop?: boolean;
}
-const code = ({
- getValues, setError, setIsSent, setIsClick,
+const useSendCode = ({
+ getValues, onError, onSucess, onClick,
}: SendCodeParams) => {
+ if (onClick) onClick();
const phoneNumber = getValues('phone_number');
const phoneNumberParam: PhoneNumberRegisterParam = { phone_number: phoneNumber };
- getPhoneAuthCode(phoneNumberParam)
- .then(() => {
- setIsSent(true);
- setIsClick(false);
+ const sendCode = useMutation({
+ mutationKey: ['sendCode'],
+ mutationFn: () => getPhoneAuthCode(phoneNumberParam),
+ onError: (e) => onError(e),
+ onSuccess: () => {
+ if (onSucess) onSucess();
showToast('success', '인증번호를 발송했습니다');
- })
- .catch((e) => {
- if (isKoinError(e)) {
- setError('phone_number', { type: 'custom', message: e.message });
- }
- });
+ },
+ });
+
+ return sendCode;
};
const useCheckCode = (
- getValues: UseFormGetValues,
- setError: UseFormSetError,
+ verificationCode: string,
+ phoneNumber: string,
+ setError: UseFormSetError,
) => {
const [isCertified, setIsCertified] = useState(false);
+ const mutation = useMutation({
+ mutationFn: () => verificationAuthCode({
+ certification_code: verificationCode,
+ phone_number: phoneNumber,
+ }),
+ onError: (e) => {
+ if (isKoinError(e)) {
+ setError('verificationCode', { type: 'error', message: e.message });
+ } else {
+ sendClientError(e);
+ }
+ },
+ onSuccess: (res) => {
+ sessionStorage.setItem('access_token', res.token);
+ setIsCertified(true);
+ },
+ });
+
+ return { mutation, isCertified };
+};
+
+function Timer({
+ seconds, setSeconds, time = 180, timeOut, onMid, stop,
+}: TimerProps) {
+ useEffect(() => {
+ let interval: NodeJS.Timeout;
- const checkCode = () => {
- const certificationCode = getValues('verificationCode');
- if (certificationCode.length === 6) {
- verificationAuthCode({
- certification_code: certificationCode,
- phone_number: getValues('phone_number'),
- })
- .then((res) => {
- sessionStorage.setItem('access_token', res.token);
- setIsCertified(true);
- showToast('success', '인증번호 확인 성공');
- })
- .catch((e) => {
- if (isKoinError(e)) {
- setError('verificationCode', { type: 'error', message: e.message });
- } else {
- sendClientError(e);
- }
- });
+ if (seconds <= 0 && timeOut) {
+ timeOut();
}
- };
- return { checkCode, isCertified };
-};
+ if (seconds === time - 30 && onMid) {
+ // 30초 지났을 때
+ onMid();
+ }
+
+ if (seconds > 0 && !stop) {
+ interval = setInterval(() => {
+ setSeconds((prev) => prev - 1);
+ }, 1000);
+ }
-export default function PhoneStep({ setIsStepComplete }: PhoneStepProps) {
+ return () => clearInterval(interval);
+ }, [seconds, setSeconds, timeOut, time, onMid, stop]);
+
+ return (
+
+ {seconds / 60 >= 10 ? Math.floor(seconds / 60) : `0${Math.floor(seconds / 60)}`}
+ {' : '}
+ {seconds % 60 >= 10 ? seconds % 60 : `0${seconds % 60}`}
+
+ );
+}
+
+interface InquiryButtonProps {
+ title: string;
+ link: string;
+}
+
+function InquiryButton({ title, link }:InquiryButtonProps) {
+ return (
+
+ );
+}
+
+function PhoneStep({ nextStep }: { nextStep: () => void }) {
const {
register, formState: { errors }, getValues, setError, watch, setValue, clearErrors,
- } = useFormContext();
+ } = useFormContext();
+
+ const [steps, setSteps] = useState(0);
+ const [isShowInquiry, setIsShowInquiry] = useState(false);
+ const [seconds, setSeconds] = useState(180);
+ const [hasConfilct, setHasConflict] = useState(false);
+ const [phoneNumber, verificationCode] = watch(['phone_number', 'verificationCode']);
+ const onSendCodeSucess = () => {
+ setSteps((prev) => prev + 1);
+ setHasConflict(false);
+ };
+ const onSendCodeError = (e: unknown) => {
+ if (isKoinError(e)) {
+ if (e.status === 409) {
+ setError('phone_number', { type: 'custom', message: '이미 가입된 전화번호입니다.' });
+ setHasConflict(true);
+ return;
+ }
+ setError('phone_number', { type: 'custom', message: e.message });
+ }
+ };
+
+ const onRevalidateCodeSuccess = () => {
+ setSeconds(180);
+ clearErrors();
+ };
+
+ const sendCode = useSendCode({
+ getValues, onError: onSendCodeError, onSucess: onSendCodeSucess,
+ });
- const [isSent, setIsSent] = useState(false);
- const [isClick, setIsClick] = useState(false);
- const debounce = useDebounce(code, {
- getValues, setError, setIsSent, setIsClick,
+ const revalidataCode = useSendCode({
+ getValues, onError: onSendCodeError, onSucess: onRevalidateCodeSuccess,
});
- const { checkCode, isCertified } = useCheckCode(
- getValues,
+ const { mutation, isCertified } = useCheckCode(
+ verificationCode,
+ phoneNumber,
setError,
);
- const sendCode = () => {
- setIsClick(true);
- if (!getValues('phone_number')) {
- setError('phone_number', { type: 'custom', message: '필수 입력 항목입니다.' });
- return;
- }
- debounce();
- };
-
const setCode = (e: ChangeEvent) => setValue('verificationCode', e.target.value);
const handlePhoneNumberChange = (e: ChangeEvent) => {
@@ -116,155 +186,204 @@ export default function PhoneStep({ setIsStepComplete }: PhoneStepProps) {
value = value.slice(0, 11);
}
setValue('phone_number', value);
- setIsClick(false);
clearErrors();
};
- const watchedValues = watch(['attachment_urls', 'verificationCode', 'password', 'passwordConfirm']);
-
useEffect(() => {
- const values = getValues();
- const isComplete = (
- values.password === values.passwordConfirm
- && values.password.length > 0
- ) && isCertified && !!errors;
- if (isComplete) {
- setIsStepComplete(true);
- } else {
- setIsStepComplete(false);
+ if (verificationCode.length === 6 && mutation.isIdle && sendCode.isSuccess) {
+ mutation.mutate();
}
- }, [watchedValues, isCertified, getValues, setIsStepComplete, errors]);
+ }, [verificationCode, mutation, sendCode]);
return (
-
-
전화번호
-
-
+ {steps >= 0 && (
+
+
+
-
+
-
-
-
- {errors.phone_number && }
- {errors.phone_number?.message}
+ 인증번호 발송
+
+ {hasConfilct && (
+
+ )}
-
-
-
인증번호
-
-
= 1 && (
+
+
+ setError('verificationCode', { type: 'error', message: '유효시간이 지났습니다. 인증번호를 재발송 해주세요.' })}
+ onMid={() => setIsShowInquiry(true)}
+ stop={isCertified}
+ />
+)}
/>
-
-
-
- {errors.verificationCode && (
- <>
-
- {errors.verificationCode.message}
- >
+
+ {!isCertified && (
+ <>
+
+ {isShowInquiry && (
+
+ )}
+ >
)}
+ )}
-
- 비밀번호
- {
+ setValue('verificationCode', '');
+ nextStep();
+ }}
+ >
+ 다음
+
+
+ );
+}
+
+interface Props {
+ nextStep: () => void;
+}
+
+interface PasswordParams {
+ password: string;
+ passwordConfirm: string;
+}
+
+function PasswordStep({ nextStep }: Props) {
+ const {
+ register, formState: { errors }, watch,
+ } = useFormContext
();
+
+ const [isBlind, setIsBlind] = useState({
+ password: false,
+ passwordConfirm: false,
+ });
+
+ const toggleBlindState = (key: 'password' | 'passwordConfirm') => {
+ setIsBlind((prev) => ({
+ ...prev, [key]: !prev[key],
+ }));
+ };
+
+ const [password, passwordConfirm] = watch(['password', 'passwordConfirm']);
+
+ const isValidPassword = password.length >= 6 && !errors.password;
+ return (
+
+
+
+
toggleBlindState('password')} />}
/>
-
- {errors.password && (
- <>
-
- {errors.password.message}
- >
- )}
-
-
-
+
);
}
+
+export default function AuthenticationStep({ nextStep }: Props) {
+ const [steps, setSteps] = useState(0);
+
+ if (steps === 0) {
+ return (
+ setSteps((prev) => prev + 1)} />
+ );
+ }
+
+ return ;
+}
diff --git a/src/page/Auth/Signup/components/phoneStep/phoneStep.module.scss b/src/page/Auth/Signup/components/phoneStep/phoneStep.module.scss
index 07ddb98c..f9e22a69 100644
--- a/src/page/Auth/Signup/components/phoneStep/phoneStep.module.scss
+++ b/src/page/Auth/Signup/components/phoneStep/phoneStep.module.scss
@@ -1,53 +1,10 @@
@use "src/utils/styles/mediaQuery" as media;
.default-info {
- width: 98%;
- margin-left: 1%;
- margin-top: 25px;
display: flex;
flex-direction: column;
- gap: 27px;
- height: calc(100vh - 45vh);
-
- @include media.media-breakpoint-down(mobile) {
- height: calc(100vh - 40vh);
- }
-}
-
-.default-info span {
- font-size: 14px;
- font-style: normal;
- font-weight: 500;
-}
-
-.default-info input {
- width: 100%;
- box-sizing: border-box;
- border-radius: 4px;
- background: #f5f5f5;
- padding: 12px 16px;
- border: none;
-
- &:focus {
- outline: 1px solid #4590bb;
- }
-
- &:hover {
- cursor: pointer;
- }
-}
-
-.default-info button {
- width: 33.33%;
- padding: 8px 12px;
- border-radius: 4px;
- background: #eee;
- color: #4b4b4b;
- font-size: 13px;
-
- &:hover {
- cursor: pointer;
- }
+ height: 100%;
+ justify-content: space-between;
}
.phone-number__input {
@@ -87,15 +44,29 @@
gap: 7px;
}
-.error-border {
- &:focus {
- outline: 1px solid #f7941e !important;
+.inquiry {
+ display: flex;
+ justify-content: space-between;
+
+ &__quote {
+ color: #727272;
+ }
+
+ &__link {
+ color: #175c8e;
+ text-decoration: none;
}
}
-.error-message {
- display: flex;
- align-items: center;
- color: #f7941e;
- font-size: 12px;
+.wrapper {
+ position: relative;
+}
+
+.login-link {
+ color: #175c8e;
+ text-decoration: none;
+ position: absolute;
+ right: 10px;
+ top: 50%;
+ transform: translateY(-50%);
}
diff --git a/src/page/Auth/Signup/components/searchShop/index.tsx b/src/page/Auth/Signup/components/searchShop/index.tsx
index fe11e5c8..58caa7dc 100644
--- a/src/page/Auth/Signup/components/searchShop/index.tsx
+++ b/src/page/Auth/Signup/components/searchShop/index.tsx
@@ -2,115 +2,109 @@ import SearchIcon from 'assets/svg/auth/search-icon.svg?react';
import useShopList from 'query/shops';
import { ChangeEvent, useEffect, useState } from 'react';
import { useFormContext } from 'react-hook-form';
-import { useOutletContext } from 'react-router-dom';
import cn from 'utils/ts/className';
-import { OutletProps } from 'page/Auth/FindPassword/entity';
+import { Button } from 'page/Auth/components/Common/form';
import styles from './searchShop.module.scss';
interface ShopInfo {
shop_name: string;
shop_id: number | null;
}
-export default function SearchShop() {
- const [searchText, setSearchText] = useState('');
- const steps = useOutletContext();
+
+export default function SearchShop({ nextStep }: { nextStep: () => void }) {
const { shopList, isError } = useShopList();
const {
watch, setValue,
} = useFormContext();
+
function handleClickShop(e: React.MouseEvent) {
const { name, id } = JSON.parse(e.currentTarget.value);
setValue('shop_name', name);
setValue('shop_id', id);
- steps.setIsShopSelect(true);
}
const [filteredShopList, setFilteredShopList] = useState(shopList?.shops);
- function handleSearch() {
- if (searchText !== '') {
- setFilteredShopList(shopList?.shops.filter(({ name }) => name.includes(searchText)));
- }
- }
-
function handleChangeSearchText(e: ChangeEvent) {
- setSearchText(e.target.value);
- }
-
- function handleKeyDown(e: React.KeyboardEvent) {
- if (e.key === 'Enter') {
- handleSearch();
+ if (e.target.value !== '') {
+ setFilteredShopList(shopList?.shops.filter(({ name }) => name.includes(e.target.value)));
+ } else {
+ setFilteredShopList(shopList?.shops);
}
}
useEffect(() => {
- if (searchText === '') {
+ if (shopList?.shops) {
setFilteredShopList(shopList?.shops);
}
- }, [searchText, shopList?.shops]);
+ }, [shopList]);
return (
-
-
- handleSearch()}
- />
-
-
- {isError &&
에러가 발생했습니다.
}
- {filteredShopList?.map((shop) => (
-
+
+ 다음
+
);
}
diff --git a/src/page/Auth/Signup/components/searchShop/searchShop.module.scss b/src/page/Auth/Signup/components/searchShop/searchShop.module.scss
index 94871000..7891fcd5 100644
--- a/src/page/Auth/Signup/components/searchShop/searchShop.module.scss
+++ b/src/page/Auth/Signup/components/searchShop/searchShop.module.scss
@@ -1,5 +1,12 @@
@use "src/utils/styles/mediaQuery" as media;
+.search-shop-container {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ justify-content: space-between;
+}
+
.search-input-container {
border: none;
border-radius: 4px;
@@ -37,8 +44,8 @@
flex-direction: column;
gap: 8px;
- @include media.media-breakpoint-down(mobile) {
- height: calc(100vh - 40vh);
+ &::-webkit-scrollbar {
+ display: none;
}
}
diff --git a/src/page/Auth/Signup/index.tsx b/src/page/Auth/Signup/index.tsx
index 44339ac9..e8af513d 100644
--- a/src/page/Auth/Signup/index.tsx
+++ b/src/page/Auth/Signup/index.tsx
@@ -1,10 +1,9 @@
-import { useEffect, useState } from 'react';
import { useOutletContext } from 'react-router-dom';
import { OutletProps } from 'page/Auth/FindPassword/entity';
+import { useState } from 'react';
+import OwnerStep from './components/onwerStep';
import AgreeStep from './components/agreeStep';
-import OwnerInfoStep from './components/ownerInfoStep';
-import PhoneStep from './components/phoneStep';
-import SearchShop from './components/searchShop';
+import AuthenticationStep from './components/phoneStep';
interface SelectOptions {
personal: boolean;
@@ -19,7 +18,6 @@ const initialSelectOption: SelectOptions = {
export default function SignUp() {
const [selectItems, setSelectItems] = useState(initialSelectOption);
const steps = useOutletContext();
- const [stepPhoneComplete, setStepPhoneComplete] = useState(false);
const handleSelect = (option: keyof SelectOptions | 'all') => {
if (option === 'all') {
@@ -36,44 +34,22 @@ export default function SignUp() {
}
};
- useEffect(() => {
- setSelectItems(initialSelectOption);
- }, [steps.index]);
-
- useEffect(() => {
- if (selectItems.koin && selectItems.personal) {
- steps.setIsStepComplete(true);
- } else {
- steps.setIsStepComplete(false);
- }
- }, [selectItems, steps]);
-
- useEffect(() => {
- }, [steps.setIsStepComplete]);
-
- useEffect(() => {
- if (stepPhoneComplete) {
- steps.setIsStepComplete(true);
- }
- }, [stepPhoneComplete, steps]);
-
return (
<>
+
{steps.index === 0 && (
-
+
)}
{steps.index === 1 && (
-
+
)}
- {steps.index === 2 && (steps.isSearch
- ? (
-
- ) : (
- steps.setIsSearch(true)}
- setIsStepComplete={setStepPhoneComplete}
- />
- )
+
+ {steps.index === 2 && (
+ steps.setIsComplete(true)} />
)}
>
);
diff --git a/src/page/Auth/components/Common/BlindButton/index.tsx b/src/page/Auth/components/Common/BlindButton/index.tsx
new file mode 100644
index 00000000..3deff54f
--- /dev/null
+++ b/src/page/Auth/components/Common/BlindButton/index.tsx
@@ -0,0 +1,20 @@
+import ShowIcon from 'assets/svg/auth/show.svg?react';
+import BlindIcon from 'assets/svg/auth/blind.svg?react';
+
+interface Props {
+ isBlind: boolean;
+ onClick: () => void;
+ classname?: string;
+}
+
+export default function BlindButton({ isBlind, onClick, classname }: Props) {
+ return (
+
+ {isBlind ? : }
+
+ );
+}
diff --git a/src/page/Auth/components/Common/form/form.module.scss b/src/page/Auth/components/Common/form/form.module.scss
new file mode 100644
index 00000000..4d1d996f
--- /dev/null
+++ b/src/page/Auth/components/Common/form/form.module.scss
@@ -0,0 +1,80 @@
+.title {
+ font-weight: 500;
+ font-size: 20px;
+ line-height: 32px;
+ margin: 25px 0;
+ white-space: pre-line;
+}
+
+.input-container {
+ position: relative;
+}
+
+.input {
+ width: 100%;
+ border-radius: 4px;
+ background: #f5f5f5;
+ padding: 12px 16px;
+ border: none;
+ box-sizing: border-box;
+
+ &::placeholder {
+ color: #8e8e8e;
+ font-size: 12px;
+ font-weight: 400;
+ }
+}
+
+.timer {
+ position: absolute;
+ right: 10px;
+ top: 50%;
+ transform: translateY(-50%);
+}
+
+.button {
+ background-color: #175c8e;
+ color: #fff;
+ width: 100%;
+ border-radius: 4px;
+ padding: 12px 16px;
+ cursor: pointer;
+
+ &__secondary {
+ background-color: #4590bb;
+ }
+
+ &__info {
+ background-color: #f5f5f5;
+ color: #727272;
+ }
+
+ &__disabled {
+ background-color: #e1e1e1;
+ color: #000;
+ cursor: default;
+ }
+}
+
+%message-container {
+ font-size: 12px;
+ font-weight: 400;
+ height: 30px;
+ display: flex;
+ align-items: center;
+ gap: 3px;
+}
+
+.message-container {
+ &--error {
+ color: #f7941e;
+
+ @extend %message-container;
+ }
+
+ &--success {
+ color: #00a86b;
+
+ @extend %message-container;
+ }
+}
diff --git a/src/page/Auth/components/Common/form/index.tsx b/src/page/Auth/components/Common/form/index.tsx
new file mode 100644
index 00000000..30a3d790
--- /dev/null
+++ b/src/page/Auth/components/Common/form/index.tsx
@@ -0,0 +1,115 @@
+import cn from 'utils/ts/className';
+import Error from 'assets/svg/auth/error-icon.svg?react';
+import Success from 'assets/svg/auth/success.svg?react';
+import styles from './form.module.scss';
+
+interface InputProps {
+ type?: string;
+ placeholder?: string;
+ register: any;
+ name: string;
+ onChange?: (e: React.ChangeEvent) => void;
+ inputMode?: string;
+ required?: boolean;
+ pattern?: RegExp;
+ requiredMessage?: string;
+ patternMessage?: string;
+ maxLength?: number;
+ component?: React.ReactNode;
+}
+
+interface ButtonProps {
+ disabled?: boolean;
+ children: React.ReactNode;
+ onClick?: () => void;
+ onSubmit?: () => void;
+ type?: 'button' | 'submit' | 'reset';
+ primary?: boolean;
+ secondary?: boolean;
+ info?: boolean;
+}
+
+interface ValidationMessageProps {
+ message?: string;
+ isError: boolean;
+}
+
+export function Title({ title }: { title: string }) {
+ return (
+ {title}
+ );
+}
+
+export function ValidationMessage({ message, isError }: ValidationMessageProps) {
+ if (isError) {
+ return (
+
+
+ {message}
+
+ );
+ }
+
+ return (
+
+ {message && }
+ {message}
+
+ );
+}
+
+export function Input({
+ type = 'text', placeholder, register, onChange, inputMode, name, required, pattern,
+ requiredMessage, patternMessage, maxLength, component,
+}: InputProps) {
+ return (
+
+ );
+}
+
+export function Button({
+ disabled = false, children, onClick, type = 'button', onSubmit, primary = true, secondary = false, info = false,
+}: ButtonProps) {
+ return (
+ {
+ e.preventDefault();
+ onSubmit();
+ }) : undefined}
+ disabled={disabled}
+ className={cn({
+ [styles.button]: primary,
+ [styles.button__secondary]: secondary,
+ [styles.button__info]: info,
+ [styles.button__disabled]: disabled,
+ })}
+ >
+ {children}
+
+ );
+}
diff --git a/src/page/Auth/components/Common/index.module.scss b/src/page/Auth/components/Common/index.module.scss
index bdc9e0ea..9293bba0 100644
--- a/src/page/Auth/components/Common/index.module.scss
+++ b/src/page/Auth/components/Common/index.module.scss
@@ -64,18 +64,14 @@
}
.content {
- height: calc(100vh - 35vh);
box-sizing: border-box;
padding: 5px 0;
overflow: scroll;
+ height: 75dvh;
&::-webkit-scrollbar {
display: none;
}
-
- @include media.media-breakpoint-down(mobile) {
- height: calc(100vh - 28vh);
- }
}
.button {
diff --git a/src/page/Auth/components/Common/index.tsx b/src/page/Auth/components/Common/index.tsx
index f1f2c228..692a35c9 100644
--- a/src/page/Auth/components/Common/index.tsx
+++ b/src/page/Auth/components/Common/index.tsx
@@ -1,130 +1,43 @@
import BackArrow from 'assets/svg/common/back-arrow.svg?react';
-import { FormProvider, useForm, UseFormSetError } from 'react-hook-form';
-import { Outlet, useLocation, useNavigate } from 'react-router-dom';
-import cn from 'utils/ts/className';
-import { Register, RegisterUser } from 'model/auth';
-import { changePassword } from 'api/auth';
-import { phoneRegisterUser } from 'api/register';
-import { isKoinError, sendClientError } from '@bcsdlab/koin';
+import { FormProvider, useForm } from 'react-hook-form';
+import { Outlet, useLocation } from 'react-router-dom';
+import { Register } from 'model/auth';
import { useStep } from 'page/Auth/hook/useStep';
-import sha256 from 'utils/ts/SHA-256';
-import { useDebounce } from 'utils/hooks/useDebounce';
-import ROUTES from 'static/routes';
import Done from 'page/Auth/components/Done';
import styles from './index.module.scss';
-const setNewPassword = async (
- phone_number: string,
- password: string,
- setError: UseFormSetError,
-) => {
- const hashPassword = await sha256(password);
- changePassword({ phone_number, password: hashPassword })
- .catch((e) => {
- if (isKoinError(e)) {
- setError('password', { type: 'custom', message: e.message });
- } else {
- sendClientError(e);
- }
- });
-};
-
-interface RegisterParam {
- company_number: string,
- name: string,
- password: string,
- phone_number: string,
- shop_id: number | null,
- shop_name: string,
- attachment_urls: { file_url: string; }[],
- setError: UseFormSetError,
-}
-
-const registerUser = (
- {
- company_number,
- name,
- password,
- phone_number,
- shop_id,
- shop_name,
- attachment_urls,
- setError,
- }: RegisterParam,
-) => {
- phoneRegisterUser({
- company_number, name, password, phone_number, shop_id, shop_name, attachment_urls,
- }).then(() => sessionStorage.removeItem('accessToken'))
- .catch((e) => {
- if (isKoinError(e)) {
- setError('password', { type: 'custom', message: e.message });
- } else {
- sendClientError(e);
- }
- });
-};
-
export default function CommonLayout() {
const location = useLocation();
- const navigate = useNavigate();
const isFindPassword = location.pathname.includes('find');
const title = isFindPassword ? '비밀번호 찾기' : '회원가입';
const method = useForm({
mode: 'onChange',
+ defaultValues: {
+ company_number: '',
+ name: '',
+ password: '',
+ phone_number: '',
+ shop_id: null,
+ shop_name: '',
+ attachment_urls: [],
+ verificationCode: '',
+ },
});
- const {
- formState: { errors }, setError, getValues,
- } = method;
const steps = useStep(isFindPassword ? 'find' : 'register');
const {
- nextStep,
previousStep,
currentStep,
index,
totalStep,
isComplete,
- isStepComplete,
- isSearch,
- setIsSearch,
} = steps;
// eslint-disable-next-line
const progressPercentage = (index + 1) / totalStep * 100;
- const stepCheck = async () => {
- if (isComplete) navigate(ROUTES.Login());
- if (!errors.root) {
- if (index + 1 === totalStep && isFindPassword) {
- setNewPassword(getValues('phone_number'), getValues('password'), setError);
- } else if (index + 1 === totalStep && !isFindPassword) {
- const hash = await sha256(getValues('password'));
- registerUser(
- {
- company_number: getValues('company_number'),
- name: getValues('name'),
- password: hash,
- phone_number: getValues('phone_number'),
- shop_id: getValues('shop_id'),
- shop_name: getValues('shop_name'),
- attachment_urls: getValues('attachment_urls'),
- setError,
- },
- );
- }
- nextStep();
- }
- };
-
- const debounce = useDebounce(stepCheck, null);
-
- const handleSelectComplete = () => {
- setIsSearch(false);
- steps.setIsStepComplete(true);
- };
-
return (
@@ -164,36 +77,6 @@ export default function CommonLayout() {
?
: }
- {!isComplete && !isSearch && (
-
- {index + 1 === totalStep ? '완료' : '다음'}
-
- )}
- {isSearch && (
-
- 선택완료
-
- )}
diff --git a/src/page/Auth/hook/useStep.ts b/src/page/Auth/hook/useStep.ts
index 84f40e82..50352a04 100644
--- a/src/page/Auth/hook/useStep.ts
+++ b/src/page/Auth/hook/useStep.ts
@@ -14,15 +14,13 @@ export const useStep = (type: Type) => {
const [index, setIndex] = useState(0);
const [isSearch, setIsSearch] = useState(false);
const [isComplete, setIsComplete] = useState(false);
- const [isStepComplete, setIsStepComplete] = useState(false);
const [isShopSelect, setIsShopSelect] = useState(false);
const navigate = useNavigate();
const nextStep = () => {
- if (isStepComplete && index + 1 < target.length) {
+ if (index + 1 < target.length) {
setIndex((prev) => prev + 1);
- setIsStepComplete(false);
- } else if (isStepComplete && index + 1 === target.length) {
+ } else if (index + 1 === target.length) {
setIsComplete(true);
}
};
@@ -33,7 +31,6 @@ export const useStep = (type: Type) => {
setIsSearch(false);
} else {
setIndex((prev) => prev - 1);
- setIsStepComplete(true); // step을 통과한 사람만 뒤로 갈 수 있음
}
} else navigate(-1);
};
@@ -49,8 +46,6 @@ export const useStep = (type: Type) => {
totalStep,
isComplete,
setIsComplete,
- isStepComplete,
- setIsStepComplete,
isSearch,
setIsSearch,
isShopSelect,
diff --git a/src/page/MyShopPage/MyShopPage.module.scss b/src/page/MyShopPage/MyShopPage.module.scss
index 2547707b..083b9227 100644
--- a/src/page/MyShopPage/MyShopPage.module.scss
+++ b/src/page/MyShopPage/MyShopPage.module.scss
@@ -112,3 +112,15 @@ a {
}
}
}
+
+.edit-wrapper {
+ padding: 10px 20px;
+ border-bottom: 1.5px solid #eee;
+}
+
+.center {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 10px;
+}
diff --git a/src/page/MyShopPage/components/EditMenu/index.module.scss b/src/page/MyShopPage/components/EditMenu/index.module.scss
new file mode 100644
index 00000000..5dd91537
--- /dev/null
+++ b/src/page/MyShopPage/components/EditMenu/index.module.scss
@@ -0,0 +1,12 @@
+.menu__add {
+ display: flex;
+ width: 100%;
+ justify-content: flex-end;
+ padding-right: 10px;
+ box-sizing: border-box;
+ margin-top: 10px;
+
+ &--button {
+ color: #175c8e;
+ }
+}
diff --git a/src/page/MyShopPage/components/EditMenu/index.tsx b/src/page/MyShopPage/components/EditMenu/index.tsx
new file mode 100644
index 00000000..35e67c9a
--- /dev/null
+++ b/src/page/MyShopPage/components/EditMenu/index.tsx
@@ -0,0 +1,29 @@
+import useMyShop from 'query/shop';
+import { Link } from 'react-router-dom';
+import MenuTable from 'page/MyShopPage/components/MenuTable';
+import { useClickImage } from 'utils/hooks/useClickImage';
+import styles from './index.module.scss';
+
+export default function EditMenu() {
+ const { menusData } = useMyShop();
+ const { onClickImage } = useClickImage();
+
+ if (!menusData) return null;
+
+ return (
+
+ );
+}
diff --git a/src/page/MyShopPage/components/MenuTable/MenuTable.module.scss b/src/page/MyShopPage/components/MenuTable/MenuTable.module.scss
index a6578d30..c43b7ed5 100644
--- a/src/page/MyShopPage/components/MenuTable/MenuTable.module.scss
+++ b/src/page/MyShopPage/components/MenuTable/MenuTable.module.scss
@@ -2,6 +2,7 @@
.categories {
display: flex;
+ justify-content: space-between;
position: sticky;
top: 77px;
gap: 13px;
@@ -53,8 +54,7 @@
}
.menu-info {
- display: grid;
- grid-template-columns: repeat(2, 1fr);
+ display: flex;
padding: 20px;
gap: 20px;
border-bottom: 1px solid #eee;
@@ -85,7 +85,7 @@
@include media.media-breakpoint-down(mobile) {
padding-bottom: 12px;
- width: 250px;
+ width: 200px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
@@ -101,16 +101,19 @@
}
}
}
+
+ &__modify-button {
+ background: none;
+ color: #175c8e;
+ height: min-content;
+ cursor: pointer;
+ }
}
.image {
display: flex;
justify-content: center;
- &:not(:first-child) {
- display: none;
- }
-
&__button {
border: none;
background-color: transparent;
diff --git a/src/page/MyShopPage/components/MenuTable/index.tsx b/src/page/MyShopPage/components/MenuTable/index.tsx
index a3327747..6985c45f 100644
--- a/src/page/MyShopPage/components/MenuTable/index.tsx
+++ b/src/page/MyShopPage/components/MenuTable/index.tsx
@@ -2,19 +2,21 @@ import { useEffect, useState } from 'react';
import { MenuCategory } from 'model/shopInfo/menuCategory';
import cn from 'utils/ts/className';
import MENU_CATEGORY from 'utils/constant/menu';
-import { Link } from 'react-router-dom';
+import { Link, useNavigate } from 'react-router-dom';
import ROUTES from 'static/routes';
import styles from './MenuTable.module.scss';
interface MenuTableProps {
shopMenuCategories: MenuCategory[];
onClickImage: (img: string[], index: number) => void;
+ isEdit?: boolean;
}
const HEADER_OFFSET = 133; // categories 높이
-function MenuTable({ shopMenuCategories, onClickImage }: MenuTableProps) {
+function MenuTable({ shopMenuCategories, onClickImage, isEdit = false }: MenuTableProps) {
const [categoryType, setCategoryType] = useState(shopMenuCategories[0].name);
+ const navigate = useNavigate();
const handleScroll = () => {
shopMenuCategories.forEach((menu) => {
@@ -29,6 +31,10 @@ function MenuTable({ shopMenuCategories, onClickImage }: MenuTableProps) {
});
};
+ const goEditPage = (id: number) => {
+ navigate(ROUTES.Owner.ModifyMenu({ id: String(id), isLink: true }));
+ };
+
useEffect(() => {
shopMenuCategories.forEach((menu) => {
const element = document.getElementById(menu.name);
@@ -99,25 +105,6 @@ function MenuTable({ shopMenuCategories, onClickImage }: MenuTableProps) {
{menuCategories.menus.map((menu) => (
menu.option_prices === null ? (
- {menu.image_urls.length > 0 ? (
- menu.image_urls.map((img, idx) => (
-
-
onClickImage(menu.image_urls, idx)}
- >
-
-
-
- ))) : (
-
-

-
- )}
{menu.name}
@@ -129,6 +116,33 @@ function MenuTable({ shopMenuCategories, onClickImage }: MenuTableProps) {
+ {isEdit && (
+
goEditPage(menu.id)}
+ >
+ 변경
+
+ )}
+ {menu.image_urls.length > 0 ? (
+
+
onClickImage(menu.image_urls, 0)}
+ >
+
+
+
+ ) : (
+
+

+
+ )}
) : (
menu.option_prices.map((item) => (
diff --git a/src/page/MyShopPage/components/ShopInfo/ShopInfo.module.scss b/src/page/MyShopPage/components/ShopInfo/ShopInfo.module.scss
index 74ea5b52..bd07a1a8 100644
--- a/src/page/MyShopPage/components/ShopInfo/ShopInfo.module.scss
+++ b/src/page/MyShopPage/components/ShopInfo/ShopInfo.module.scss
@@ -138,14 +138,12 @@
margin-right: 8px;
box-sizing: border-box;
padding: 10px;
- width: auto;
- height: 24px;
border: 1px solid #f7941e;
border-radius: 13.5px;
color: #f7941e;
font-family: "Noto Sans", sans-serif;
font-weight: 500;
- font-size: 12px;
+ font-size: 10px;
justify-content: center;
display: flex;
align-items: center;
@@ -157,13 +155,12 @@
box-sizing: border-box;
padding: 10px;
width: auto;
- height: 24px;
border: 1px solid #f7941e;
border-radius: 13.5px;
color: #f7941e;
font-family: "Noto Sans", sans-serif;
font-weight: 500;
- font-size: 12px;
+ font-size: 10px;
justify-content: center;
display: flex;
align-items: center;
diff --git a/src/page/MyShopPage/index.tsx b/src/page/MyShopPage/index.tsx
index c23058dc..05c11e50 100644
--- a/src/page/MyShopPage/index.tsx
+++ b/src/page/MyShopPage/index.tsx
@@ -5,12 +5,12 @@ import { Link, useNavigate } from 'react-router-dom';
import useBooleanState from 'utils/hooks/useBooleanState';
import { useEffect, useState } from 'react';
import cn from 'utils/ts/className';
-import { Portal } from 'component/common/Modal/PortalProvider';
-import useModalPortal from 'utils/hooks/useModalPortal';
import showToast from 'utils/ts/showToast';
-import ImageModal from 'component/common/Modal/ImageModal';
import useLogger from 'utils/hooks/useLogger';
import ROUTES from 'static/routes';
+import { Button } from 'page/Auth/components/Common/form';
+import EditEventIcon from 'assets/svg/myshop/edit-event-icon.svg?react';
+import { useClickImage } from 'utils/hooks/useClickImage';
import CatagoryMenuList from './components/CatagoryMenuList';
import ShopInfo from './components/ShopInfo';
import styles from './MyShopPage.module.scss';
@@ -35,6 +35,7 @@ export default function MyShopPage() {
setFalse: closeEditShopInfoModal,
value: isEditShopInfoModalOpen,
} = useBooleanState(false);
+ const { onClickImage } = useClickImage();
const logger = useLogger();
@@ -45,7 +46,6 @@ export default function MyShopPage() {
}
const [tapType, setTapType] = useState('메뉴');
- const portalManager = useModalPortal();
useEffect(() => {
refetchShopData();
@@ -57,12 +57,6 @@ export default function MyShopPage() {
}
}, [shopData, navigate, isLoading]);
- const onClickImage = (img: string[], index: number) => {
- portalManager.open((portalOption: Portal) => (
-
- ));
- };
-
if (isMobile && shopData && isEditShopInfoModalOpen) {
return (
+
+
navigate(ROUTES.Owner.EditMenu())}
+ >
+
+
+ 메뉴 편집하기
+
+
+
{tapType === '메뉴' ? (
menusData && menusData.menu_categories.length > 0 && (
'/owner',
ShopRegistration: () => '/owner/shop-registration',
AddMenu: () => '/owner/add-menu',
+ EditMenu: () => '/owner/edit-menu',
ModifyMenu: ({ id, isLink }: ROUTESParams<'id'>) => (isLink ? `/owner/modify-menu/${id}` : '/owner/modify-menu/:id'),
ModifyInfo: () => '/owner/modify-info',
MenuManagement: () => '/owner/menu-management',
diff --git a/src/utils/constant/category.ts b/src/utils/constant/category.ts
index 9c644974..16acb9d5 100644
--- a/src/utils/constant/category.ts
+++ b/src/utils/constant/category.ts
@@ -35,6 +35,20 @@ export const CATEGORY_OWNER: HeaderCategory[] = [
planFlag: true,
tag: null,
},
+ {
+ title: '메뉴 편집',
+ link: ROUTES.Owner.EditMenu(),
+ newFlag: true,
+ planFlag: true,
+ tag: null,
+ },
+ {
+ title: '메뉴 추가',
+ link: ROUTES.Owner.AddMenu(),
+ newFlag: true,
+ planFlag: true,
+ tag: null,
+ },
// {
// title: '메뉴관리',
// link: '/owner/menu-management',
diff --git a/src/utils/hooks/useClickImage.tsx b/src/utils/hooks/useClickImage.tsx
new file mode 100644
index 00000000..79cd72eb
--- /dev/null
+++ b/src/utils/hooks/useClickImage.tsx
@@ -0,0 +1,15 @@
+import { Portal } from 'component/common/Modal/PortalProvider';
+import ImageModal from 'component/common/Modal/ImageModal';
+import useModalPortal from 'utils/hooks/useModalPortal';
+
+export const useClickImage = () => {
+ const portalManager = useModalPortal();
+
+ const onClickImage = (img: string[], index: number) => {
+ portalManager.open((portalOption: Portal) => (
+
+ ));
+ };
+
+ return { onClickImage };
+};