diff --git a/.eslintrc.json b/.eslintrc.json index 8c50b063..4762015d 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -30,6 +30,7 @@ "react/jsx-props-no-spreading": "off", "react/require-default-props": "off", "@typescript-eslint/no-redeclare" : "off", + "import/prefer-default-export": "off", "no-restricted-imports": [ "error", { diff --git a/src/App.tsx b/src/App.tsx index 7fea2c1d..d15dd324 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,7 +8,7 @@ import CompleteChangePassword from 'page/Auth/FindPassword/CompleteChangePasswor import AuthLayout from 'layout/AuthLayout'; import MyStorePage from 'page/MyShopPage'; -import StoreRegistration from 'page/StoreRegistration'; +import ShopRegistration from 'page/ShopRegistration'; import AddMenu from 'page/AddMenu'; function App() { @@ -16,7 +16,7 @@ function App() { }> } /> - } /> + } /> } /> }> diff --git a/src/api/category/index.ts b/src/api/category/index.ts index 7e00de1c..d3416d79 100644 --- a/src/api/category/index.ts +++ b/src/api/category/index.ts @@ -1,9 +1,9 @@ import { accessClient } from 'api'; import { StoreCategory } from 'model/category/storeCategory'; -const getStoreCategory = async () => { +const getShopCategory = async () => { const { data } = await accessClient.get('/shops/categories'); return StoreCategory.parse(data); }; -export default getStoreCategory; +export default getShopCategory; diff --git a/src/api/shop/index.ts b/src/api/shop/index.ts index f3e4a859..23629969 100644 --- a/src/api/shop/index.ts +++ b/src/api/shop/index.ts @@ -1,10 +1,12 @@ -import { MyShopList, MyShopInfoRes, MyShopParam } from 'model/shopInfo/myShopInfo'; +import { MyShopListRes, MyShopInfoRes, MyShopParam } from 'model/shopInfo/myShopInfo'; import { MenuInfoRes } from 'model/shopInfo/menuCategory'; -import { accessClient } from 'api'; +import { ShopListRes } from 'model/shopInfo/allShopInfo'; +import { accessClient, client } from 'api'; +import { OwnerShop } from 'model/shopInfo/ownerShop'; export const getMyShopList = async () => { - const { data } = await accessClient.get('/owner/shops'); - return MyShopList.parse(data); + const { data } = await accessClient.get('/owner/shops'); + return MyShopListRes.parse(data); }; export const getShopInfo = async (param: MyShopParam) => { @@ -16,3 +18,10 @@ export const getMenuInfoList = async (param: MyShopParam) => { const { data } = await accessClient.get(`/owner/shops/${param.id}/menus`); return MenuInfoRes.parse(data); }; + +export const getShopList = async () => { + const { data } = await client.get('/shops'); + return ShopListRes.parse(data); +}; + +export const postShop = (data: OwnerShop) => accessClient.post('/owner/shops', data); diff --git a/src/assets/svg/StoreRegistration/close-x.svg b/src/assets/svg/shopRegistration/close-x.svg similarity index 100% rename from src/assets/svg/StoreRegistration/close-x.svg rename to src/assets/svg/shopRegistration/close-x.svg diff --git a/src/assets/svg/StoreRegistration/cutlery.svg b/src/assets/svg/shopRegistration/cutlery.svg similarity index 100% rename from src/assets/svg/StoreRegistration/cutlery.svg rename to src/assets/svg/shopRegistration/cutlery.svg diff --git a/src/assets/svg/StoreRegistration/magnifier.svg b/src/assets/svg/shopRegistration/magnifier.svg similarity index 100% rename from src/assets/svg/StoreRegistration/magnifier.svg rename to src/assets/svg/shopRegistration/magnifier.svg diff --git a/src/assets/svg/StoreRegistration/memo.svg b/src/assets/svg/shopRegistration/memo.svg similarity index 100% rename from src/assets/svg/StoreRegistration/memo.svg rename to src/assets/svg/shopRegistration/memo.svg diff --git a/src/assets/svg/StoreRegistration/mobile-checked.svg b/src/assets/svg/shopRegistration/mobile-checked.svg similarity index 100% rename from src/assets/svg/StoreRegistration/mobile-checked.svg rename to src/assets/svg/shopRegistration/mobile-checked.svg diff --git a/src/assets/svg/StoreRegistration/mobile-empty-img.svg b/src/assets/svg/shopRegistration/mobile-empty-img.svg similarity index 100% rename from src/assets/svg/StoreRegistration/mobile-empty-img.svg rename to src/assets/svg/shopRegistration/mobile-empty-img.svg diff --git a/src/component/common/CustomModal/CustomModal.module.scss b/src/component/common/CustomModal/CustomModal.module.scss index 167edeb9..5ecd8d2c 100644 --- a/src/component/common/CustomModal/CustomModal.module.scss +++ b/src/component/common/CustomModal/CustomModal.module.scss @@ -11,13 +11,63 @@ } .container { - display: flex; - flex-direction: column; - align-items: center; - width: 430px; - background-color: #ffffff; + &__small { + display: flex; + flex-direction: column; + align-items: center; + width: 430px; + height: 434px; + background-color: #ffffff; + overflow: auto; + + &--visible { + overflow: visible; + } + + &::-webkit-scrollbar { + display: none; + } + } + + &__medium { + display: flex; + flex-direction: column; + align-items: center; + width: 430px; + height: 536px; + background-color: #ffffff; + overflow: auto; + + &--visible { + overflow: visible; + } + + &::-webkit-scrollbar { + display: none; + } + } + + &__large { + display: flex; + flex-direction: column; + align-items: center; + width: 430px; + height: 75vh; + background-color: #ffffff; + overflow: auto; + + &--visible { + overflow: visible; + } + + &::-webkit-scrollbar { + display: none; + } + } &__header { + position: sticky; + top: 0; display: flex; align-items: center; justify-content: space-between; @@ -25,6 +75,7 @@ height: 30px; background-color: #175c8e; padding: 24px 16px; + z-index: 3; } &__title { diff --git a/src/component/common/CustomModal/index.tsx b/src/component/common/CustomModal/index.tsx index 89fca10d..80d40c07 100644 --- a/src/component/common/CustomModal/index.tsx +++ b/src/component/common/CustomModal/index.tsx @@ -1,25 +1,49 @@ -import { ReactComponent as XClose } from 'assets/svg/StoreRegistration/close-x.svg'; +import { ReactComponent as XClose } from 'assets/svg/shopRegistration/close-x.svg'; import CustomButton from 'page/Auth/Signup/component/CustomButton'; import { createPortal } from 'react-dom'; +import cn from 'utils/ts/className'; +import { useEffect } from 'react'; import styles from './CustomModal.module.scss'; interface CustomModalProps { buttonText?: string; title: string; - height: string; + modalSize: string; hasFooter: boolean; isOpen: boolean; + isOverflowVisible: boolean; onCancel: () => void; children: React.ReactNode } export default function CustomModal({ - buttonText = '', title, height, hasFooter, isOpen, onCancel, children, + buttonText = '', title, modalSize, hasFooter, isOpen, isOverflowVisible, onCancel, children, }: CustomModalProps) { + useEffect(() => { + if (isOpen) { + document.body.style.cssText = ` + position: fixed; + top: -${window.scrollY}px; + overflow-y: scroll; + width: 100%;`; + return () => { + const scrollY = document.body.style.top; + document.body.style.cssText = ''; + window.scrollTo(0, parseInt(scrollY || '0', 10) * -1); + }; + } + return undefined; + }, [isOpen]); + if (!isOpen) return null; return createPortal(
-
+
{title} ; + +export const ShopListRes = z.object({ + count: z.number(), + shops: z.array(Shop), +}); + +export type ShopListRes = z.infer; diff --git a/src/model/shopInfo/myShopInfo.ts b/src/model/shopInfo/myShopInfo.ts index a9786aee..f48d1cee 100644 --- a/src/model/shopInfo/myShopInfo.ts +++ b/src/model/shopInfo/myShopInfo.ts @@ -7,12 +7,12 @@ export const MyShop = z.object({ export type MyShop = z.infer; -export const MyShopList = z.object({ +export const MyShopListRes = z.object({ count: z.number(), shops: z.array(MyShop), }); -export type MyShopList = z.infer; +export type MyShopListRes = z.infer; export const OpenInfo = z.object({ day_of_week: z.string(), diff --git a/src/model/shopInfo/ownerShop.ts b/src/model/shopInfo/ownerShop.ts new file mode 100644 index 00000000..1a7fe023 --- /dev/null +++ b/src/model/shopInfo/ownerShop.ts @@ -0,0 +1,11 @@ +import z from 'zod'; +import { Shop } from './allShopInfo'; + +export const OwnerShop = Shop.omit({ id: true }).extend({ + address: z.string(), + description: z.string(), + delivery_price: z.string(), + image_urls: z.array(z.string()), +}); + +export type OwnerShop = z.infer; diff --git a/src/page/StoreRegistration/component/ConfirmPopup/ConfirmPopup.module.scss b/src/page/ShopRegistration/component/ConfirmPopup/ConfirmPopup.module.scss similarity index 100% rename from src/page/StoreRegistration/component/ConfirmPopup/ConfirmPopup.module.scss rename to src/page/ShopRegistration/component/ConfirmPopup/ConfirmPopup.module.scss diff --git a/src/page/StoreRegistration/component/ConfirmPopup/index.tsx b/src/page/ShopRegistration/component/ConfirmPopup/index.tsx similarity index 80% rename from src/page/StoreRegistration/component/ConfirmPopup/index.tsx rename to src/page/ShopRegistration/component/ConfirmPopup/index.tsx index e0db05f5..3de5d1b2 100644 --- a/src/page/StoreRegistration/component/ConfirmPopup/index.tsx +++ b/src/page/ShopRegistration/component/ConfirmPopup/index.tsx @@ -1,5 +1,3 @@ -import { createPortal } from 'react-dom'; -import useStepStore from 'store/useStepStore'; import styles from './ConfirmPopup.module.scss'; interface ConfirmPopupProps { @@ -8,11 +6,9 @@ interface ConfirmPopupProps { } export default function ConfirmPopup({ isOpen, onCancel }: ConfirmPopupProps) { - const { setStep } = useStepStore(); - if (!isOpen) return null; - return createPortal( + return (
가게 정보를 저장하시겠습니까? @@ -27,15 +23,13 @@ export default function ConfirmPopup({ isOpen, onCancel }: ConfirmPopupProps) { 취소
-
, - document.body, +
); } diff --git a/src/page/StoreRegistration/component/InputBox/InputBox.module.scss b/src/page/ShopRegistration/component/InputBox/InputBox.module.scss similarity index 100% rename from src/page/StoreRegistration/component/InputBox/InputBox.module.scss rename to src/page/ShopRegistration/component/InputBox/InputBox.module.scss diff --git a/src/page/ShopRegistration/component/InputBox/index.tsx b/src/page/ShopRegistration/component/InputBox/index.tsx new file mode 100644 index 00000000..b93b772c --- /dev/null +++ b/src/page/ShopRegistration/component/InputBox/index.tsx @@ -0,0 +1,44 @@ +import type { UseFormRegister } from 'react-hook-form'; +import { OwnerShop } from 'model/shopInfo/ownerShop'; +import { HTMLInputTypeAttribute, useState } from 'react'; +import styles from './InputBox.module.scss'; + +interface InputBoxProps { + content: string; + id: keyof OwnerShop + register: UseFormRegister; + inputType: HTMLInputTypeAttribute; +} + +function formatPhoneNumber(inputNumber: string) { + const phoneNumber = inputNumber.replace(/\D/g, ''); + + const formattedPhoneNumber = phoneNumber.replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3'); + + return formattedPhoneNumber; +} + +export default function InputBox({ + content, id, register, inputType, +}: InputBoxProps) { + const [formattedPhoneNumber, setFormattedPhoneNumber] = useState(''); + + const handlePhoneChange = (e: React.ChangeEvent) => { + const inputNumber = e.target.value; + const formattedNumber = formatPhoneNumber(inputNumber); + setFormattedPhoneNumber(formattedNumber); + }; + return ( + + ); +} diff --git a/src/page/StoreRegistration/component/Modal/Category/Category.module.scss b/src/page/ShopRegistration/component/Modal/Category/Category.module.scss similarity index 100% rename from src/page/StoreRegistration/component/Modal/Category/Category.module.scss rename to src/page/ShopRegistration/component/Modal/Category/Category.module.scss diff --git a/src/page/StoreRegistration/component/Modal/Category/index.tsx b/src/page/ShopRegistration/component/Modal/Category/index.tsx similarity index 62% rename from src/page/StoreRegistration/component/Modal/Category/index.tsx rename to src/page/ShopRegistration/component/Modal/Category/index.tsx index c954534b..6dbc1cd2 100644 --- a/src/page/StoreRegistration/component/Modal/Category/index.tsx +++ b/src/page/ShopRegistration/component/Modal/Category/index.tsx @@ -1,11 +1,20 @@ -import useStoreCategory from 'query/storeCategory'; +import useShopCategory from 'query/shopCategory'; import { useState } from 'react'; import cn from 'utils/ts/className'; +import useModalStore from 'store/modalStore'; +import { Category as CategoryProps } from 'model/category/storeCategory'; import styles from './Category.module.scss'; export default function Category() { const [selectedCategory, setSelectedCategory] = useState(''); - const { categoryList } = useStoreCategory(); + const { categoryList } = useShopCategory(); + const { setCategoryState } = useModalStore(); + + const handleCategoryClick = (category: CategoryProps) => { + setSelectedCategory(category.name); + setCategoryState([category.name, category.id]); + }; + return (
{categoryList?.shop_categories.filter((_, index) => index > 0).map((category) => ( @@ -15,7 +24,7 @@ export default function Category() { [styles['category__menu--selected']]: category.name === selectedCategory, })} type="button" - onClick={() => setSelectedCategory(category.name)} + onClick={() => { handleCategoryClick(category); }} key={category.id} > diff --git a/src/page/StoreRegistration/component/Modal/ConfirmStore/ConfirmStore.module.scss b/src/page/ShopRegistration/component/Modal/ConfirmShop/ConfirmShop.module.scss similarity index 78% rename from src/page/StoreRegistration/component/Modal/ConfirmStore/ConfirmStore.module.scss rename to src/page/ShopRegistration/component/Modal/ConfirmShop/ConfirmShop.module.scss index d4144d4a..2ad85174 100644 --- a/src/page/StoreRegistration/component/Modal/ConfirmStore/ConfirmStore.module.scss +++ b/src/page/ShopRegistration/component/Modal/ConfirmShop/ConfirmShop.module.scss @@ -1,14 +1,18 @@ .container { - position: absolute; + position: sticky; bottom: 0; - left: 0; display: flex; - justify-content: space-around; + justify-content: space-between; align-items: center; - height: 25%; + height: 23%; width: 100%; border-radius: 20px 20px 0 0; box-shadow: 0 -5px 10px #00000026; + background-color: #ffffff; + + &__info { + margin-left: 32px; + } &__title { font-size: 20px; @@ -34,6 +38,7 @@ width: 144px; height: 48px; background-color: #175c8e; + margin-right: 31px; font-size: 20px; font-weight: 600; color: #ffffff; diff --git a/src/page/ShopRegistration/component/Modal/ConfirmShop/index.tsx b/src/page/ShopRegistration/component/Modal/ConfirmShop/index.tsx new file mode 100644 index 00000000..03074942 --- /dev/null +++ b/src/page/ShopRegistration/component/Modal/ConfirmShop/index.tsx @@ -0,0 +1,39 @@ +import useModalStore from 'store/modalStore'; +import styles from './ConfirmShop.module.scss'; + +interface ShopInfo { + name: string; + phone: string; +} + +interface ConfirmShopProps { + open: boolean; + selectedShop: ShopInfo; + onCancel: () => void; +} + +export default function ConfirmShop({ open, onCancel, selectedShop }: ConfirmShopProps) { + const { setSearchShopState: setSearchStoreState } = useModalStore(); + if (!open) return null; + return ( +
+
+ {selectedShop.name} +
+ 전화번호 + {selectedShop.phone} +
+
+ +
+ ); +} diff --git a/src/page/StoreRegistration/component/Modal/OperateTimeMobile/OperateTimeMobile.module.scss b/src/page/ShopRegistration/component/Modal/OperateTimeMobile/OperateTimeMobile.module.scss similarity index 100% rename from src/page/StoreRegistration/component/Modal/OperateTimeMobile/OperateTimeMobile.module.scss rename to src/page/ShopRegistration/component/Modal/OperateTimeMobile/OperateTimeMobile.module.scss diff --git a/src/page/StoreRegistration/component/Modal/OperateTimeMobile/index.tsx b/src/page/ShopRegistration/component/Modal/OperateTimeMobile/index.tsx similarity index 87% rename from src/page/StoreRegistration/component/Modal/OperateTimeMobile/index.tsx rename to src/page/ShopRegistration/component/Modal/OperateTimeMobile/index.tsx index aaafaba0..3198ef8e 100644 --- a/src/page/StoreRegistration/component/Modal/OperateTimeMobile/index.tsx +++ b/src/page/ShopRegistration/component/Modal/OperateTimeMobile/index.tsx @@ -1,8 +1,8 @@ import PreviousStep from 'component/common/Auth/PreviousStep'; import SubTitle from 'component/common/Auth/SubTitle'; import useStepStore from 'store/useStepStore'; -import TimePicker from 'page/StoreRegistration/component/TimePicker'; -import WEEK from 'utils/constant/week'; +import TimePicker from 'page/ShopRegistration/component/TimePicker'; +import { WEEK } from 'utils/constant/week'; import { createPortal } from 'react-dom'; import styles from './OperateTimeMobile.module.scss'; @@ -37,9 +37,9 @@ export default function OperateTimeMobile({ isOpen, closeModal }: OperateTimeMob {day} - + {' ~ '} - + diff --git a/src/page/StoreRegistration/component/Modal/OperateTimePC/OperateTimePC.module.scss b/src/page/ShopRegistration/component/Modal/OperateTimePC/OperateTimePC.module.scss similarity index 91% rename from src/page/StoreRegistration/component/Modal/OperateTimePC/OperateTimePC.module.scss rename to src/page/ShopRegistration/component/Modal/OperateTimePC/OperateTimePC.module.scss index 75650607..5bfbcbce 100644 --- a/src/page/StoreRegistration/component/Modal/OperateTimePC/OperateTimePC.module.scss +++ b/src/page/ShopRegistration/component/Modal/OperateTimePC/OperateTimePC.module.scss @@ -18,6 +18,10 @@ justify-content: center; align-items: center; height: 100%; + + &--selected { + color: #c5c5c5; + } } &__checkbox { diff --git a/src/page/ShopRegistration/component/Modal/OperateTimePC/index.tsx b/src/page/ShopRegistration/component/Modal/OperateTimePC/index.tsx new file mode 100644 index 00000000..84efaf5b --- /dev/null +++ b/src/page/ShopRegistration/component/Modal/OperateTimePC/index.tsx @@ -0,0 +1,54 @@ +import TimePicker from 'page/ShopRegistration/component/TimePicker'; +import { WEEK } from 'utils/constant/week'; +import useModalStore from 'store/modalStore'; +import cn from 'utils/ts/className'; +import styles from './OperateTimePC.module.scss'; + +export default function OperateTimePC() { + const { shopClosedState } = useModalStore(); + + const handleShopClosedChange = (day: string) => { + useModalStore.setState((prev) => ({ + ...prev, + shopClosedState: { + ...prev.shopClosedState, + [day]: !prev.shopClosedState[day], + }, + })); + }; + return ( + + + + + + + + + + {WEEK.map((day) => ( + + + + + + ))} + +
요일시간휴무
{day} + + {' ~ '} + + + handleShopClosedChange(day)} + checked={shopClosedState[day]} + /> +
+ ); +} diff --git a/src/page/StoreRegistration/component/Modal/SearchStore/SearchStore.module.scss b/src/page/ShopRegistration/component/Modal/SearchShop/SearchShop.module.scss similarity index 76% rename from src/page/StoreRegistration/component/Modal/SearchStore/SearchStore.module.scss rename to src/page/ShopRegistration/component/Modal/SearchShop/SearchShop.module.scss index d4c87bd8..5111c3f6 100644 --- a/src/page/StoreRegistration/component/Modal/SearchStore/SearchStore.module.scss +++ b/src/page/ShopRegistration/component/Modal/SearchShop/SearchShop.module.scss @@ -1,8 +1,7 @@ .info { position: relative; - width: calc(100% - 34px); + width: 100%; height: calc(100% - 32px); - padding: 32px 18px 0 16px; &__search { display: flex; @@ -12,7 +11,7 @@ padding: 9px 11px 9px 16px; background-color: #f6f8f9; border: 1px solid #d2dae2; - margin-bottom: 24px; + margin: 32px 18px 24px 16px; } &__input { @@ -35,7 +34,9 @@ .store-list { display: flex; flex-direction: column; - width: 100%; + width: calc(100% - 34px); + margin: 0 18px 0 16px; + min-height: 530px; } .store { @@ -65,8 +66,27 @@ gap: 12px; } - &__text { + &__delivery { + font-size: 12px; + + &--selected { + color: #f7941e; + } + } + + &__pay-card { + font-size: 12px; + + &--selected { + color: #f7941e; + } + } + + &__pay-bank { font-size: 12px; - color: #f7941e; + + &--selected { + color: #f7941e; + } } } diff --git a/src/page/ShopRegistration/component/Modal/SearchShop/index.tsx b/src/page/ShopRegistration/component/Modal/SearchShop/index.tsx new file mode 100644 index 00000000..06c1ad1e --- /dev/null +++ b/src/page/ShopRegistration/component/Modal/SearchShop/index.tsx @@ -0,0 +1,134 @@ +import { ReactComponent as Magnifier } from 'assets/svg/shopRegistration/magnifier.svg'; +import cn from 'utils/ts/className'; +import { ChangeEvent, useEffect, useState } from 'react'; +import useBooleanState from 'utils/hooks/useBooleanState'; +import ConfirmShop from 'page/ShopRegistration/component/Modal/ConfirmShop'; +import useShopList from 'query/shops'; +import styles from './SearchShop.module.scss'; + +interface SearchShopProps { + open: boolean; + onCancel: () => void; +} + +export default function SearchShop({ open, onCancel }: SearchShopProps) { + const [selectedStore, setSelectedStore] = useState({ + name: '', + phone: '', + }); + const { value: showConfirmStore, setValue: setConfirmStore } = useBooleanState(false); + const [searchText, setSearchText] = useState(''); + + const { shopList, isError } = useShopList(); + + function handleClickStore(e: React.MouseEvent) { + const { name, phone } = JSON.parse(e.currentTarget.value); + if (!showConfirmStore) { + setSelectedStore({ + name, + phone, + }); + } else { + setSelectedStore({ + name: '', + phone: '', + }); + } + } + + 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(); + } + } + + function toggleConfirmStore() { + setConfirmStore((prev) => !prev); + } + + useEffect(() => { + if (searchText === '') { + setFilteredShopList(shopList?.shops); + } + }, [searchText, shopList?.shops]); + + if (!open) return null; + return ( +
+
+ + handleSearch()} + /> +
+
+ {isError &&
에러가 발생했습니다.
} + {filteredShopList?.map((shop) => ( + + ))} +
+ +
+ ); +} diff --git a/src/page/StoreRegistration/component/TimePicker/TimePicker.module.scss b/src/page/ShopRegistration/component/Modal/TimeSelection/TimeSelection.module.scss similarity index 70% rename from src/page/StoreRegistration/component/TimePicker/TimePicker.module.scss rename to src/page/ShopRegistration/component/Modal/TimeSelection/TimeSelection.module.scss index 6c512712..943c9a1f 100644 --- a/src/page/StoreRegistration/component/TimePicker/TimePicker.module.scss +++ b/src/page/ShopRegistration/component/Modal/TimeSelection/TimeSelection.module.scss @@ -1,33 +1,3 @@ -.container { - position: relative; - display: flex; - justify-content: center; - align-items: center; - width: 92px; - - &__hour { - text-align: center; - font-size: 15px; - background-color: #ffffff; - cursor: pointer; - - &:focus { - outline: none; - } - } - - &__minute { - text-align: center; - font-size: 15px; - background-color: #ffffff; - cursor: pointer; - - &:focus { - outline: none; - } - } -} - .content { position: absolute; display: flex; diff --git a/src/page/ShopRegistration/component/Modal/TimeSelection/index.tsx b/src/page/ShopRegistration/component/Modal/TimeSelection/index.tsx new file mode 100644 index 00000000..3c56fdb8 --- /dev/null +++ b/src/page/ShopRegistration/component/Modal/TimeSelection/index.tsx @@ -0,0 +1,44 @@ +import { MouseEvent } from 'react'; +import styles from './TimeSelection.module.scss'; + +const hours: number[] = Array.from({ length: 24 }, (_, i) => i); +const minutes: number[] = Array.from({ length: 12 }, (_, i) => i * 5); + +interface TimeSelectionProps { + handleClickTimeChangeButton: (e: MouseEvent) => void; +} + +export default function TimeSelection({ handleClickTimeChangeButton }: TimeSelectionProps) { + return ( +
+
+ {hours.map((hour) => ( + + ))} +
+
+ {minutes.map((minute) => ( + + ))} +
+
+ ); +} diff --git a/src/page/ShopRegistration/component/TimePicker/TimePicker.module.scss b/src/page/ShopRegistration/component/TimePicker/TimePicker.module.scss new file mode 100644 index 00000000..83655b17 --- /dev/null +++ b/src/page/ShopRegistration/component/TimePicker/TimePicker.module.scss @@ -0,0 +1,37 @@ +.container { + position: relative; + display: flex; + justify-content: center; + align-items: center; + width: 92px; + + &__hour { + text-align: center; + font-size: 15px; + background-color: #ffffff; + cursor: pointer; + + &:focus { + outline: none; + } + + &--selected { + color: #c5c5c5; + } + } + + &__minute { + text-align: center; + font-size: 15px; + background-color: #ffffff; + cursor: pointer; + + &:focus { + outline: none; + } + + &--selected { + color: #c5c5c5; + } + } +} diff --git a/src/page/ShopRegistration/component/TimePicker/index.tsx b/src/page/ShopRegistration/component/TimePicker/index.tsx new file mode 100644 index 00000000..cf346b43 --- /dev/null +++ b/src/page/ShopRegistration/component/TimePicker/index.tsx @@ -0,0 +1,116 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +import { + useState, useRef, useEffect, MouseEvent, useCallback, +} from 'react'; +import useModalStore from 'store/modalStore'; +import { WEEK } from 'utils/constant/week'; +import cn from 'utils/ts/className'; +import TimeSelection from 'page/ShopRegistration/component/Modal/TimeSelection'; +import styles from './TimePicker.module.scss'; + +type Weekday = typeof WEEK[number]; + +interface TimerPickerProps { + operatingDay: Weekday; + isOpenTimePicker: boolean; +} + +export default function TimePicker({ operatingDay, isOpenTimePicker } : TimerPickerProps) { + const dropMenuRef = useRef(null); + const [isOpen, setIsOpen] = useState(false); + const [time, setTime] = useState({ + hour: '00', + minute: '00', + }); + const { + openTimeState, closeTimeState, shopClosedState, setOpenTimeState, setCloseTimeState, + } = useModalStore(); + + useEffect(() => { + const handleClickOutside = (e: Event) => { + if (isOpen && (!dropMenuRef.current?.contains(e.target as Node))) setIsOpen(false); + }; + document.addEventListener('mousedown', handleClickOutside); + + return () => document.removeEventListener('mousedown', handleClickOutside); + }, [isOpen]); + + function toggleModal() { + setIsOpen(!isOpen); + } + + function handleChangeOperateTime() { + if (isOpenTimePicker) { + setOpenTimeState({ + ...openTimeState, + [operatingDay]: `${time.hour}:${time.minute}`, + }); + } else { + setCloseTimeState({ + ...closeTimeState, + [operatingDay]: `${time.hour}:${time.minute}`, + }); + } + } + + const handleClickTimeChangeButton = useCallback( + (e: MouseEvent) => { + const selectedTime = Number(e.currentTarget.value); + const selectedId = e.currentTarget.id; + + if (selectedId === 'hour') { + setTime((prevTime) => ({ + ...prevTime, + hour: selectedTime.toString().padStart(2, '0'), + })); + } else if (selectedId === 'minute') { + setTime((prevTime) => ({ + ...prevTime, + minute: selectedTime.toString().padStart(2, '0'), + })); + } + }, + [], + ); + + useEffect(() => { + handleChangeOperateTime(); + }, [time, shopClosedState]); + + useEffect(() => { + if (isOpenTimePicker) { + time.hour = openTimeState[operatingDay]?.slice(0, 2) || '00:00'; + time.minute = openTimeState[operatingDay]?.slice(3, 5) || '00:00'; + } else { + time.hour = closeTimeState[operatingDay]?.slice(0, 2) || '00:00'; + time.minute = closeTimeState[operatingDay]?.slice(3, 5) || '00:00'; + } + }, []); + return ( +
+ + + {isOpen && !shopClosedState[operatingDay] && ( + + )} +
+ ); +} diff --git a/src/page/ShopRegistration/hooks/CheckSameTime.ts b/src/page/ShopRegistration/hooks/CheckSameTime.ts new file mode 100644 index 00000000..8f5adef9 --- /dev/null +++ b/src/page/ShopRegistration/hooks/CheckSameTime.ts @@ -0,0 +1,44 @@ +import { useMemo } from 'react'; +import useModalStore from 'store/modalStore'; + +export default function CheckSameTime() { + const { openTimeState, closeTimeState, shopClosedState } = useModalStore(); + const openTimeArray = Object.values(openTimeState); + const closeTimeArray = Object.values(closeTimeState); + const storeClosedArray = Object.values(shopClosedState); + + const isAllSameTime = openTimeArray.every((time) => openTimeArray[0] === time) + && closeTimeArray.every((time) => closeTimeArray[0] === time); + const hasClosedDay = storeClosedArray.some((closed) => closed); + const isAllSameOpenTimeExceptClosedDays = useMemo(() => { + const nonClosedOpenTime = openTimeArray.find((time, index) => { + if (!storeClosedArray[index]) return time; + return false; + })!; + return openTimeArray.every((time, index) => { + if (storeClosedArray[index]) return true; + return time === nonClosedOpenTime; + }); + }, [openTimeArray, storeClosedArray]); + const isAllSameCloseTimeExceptClosedDays = useMemo(() => { + const nonClosedCloseTime = closeTimeArray.find((time, index) => { + if (!storeClosedArray[index]) return time; + return false; + })!; + return closeTimeArray.every((time, index) => { + if (storeClosedArray[index]) return true; + return time === nonClosedCloseTime; + }); + }, [closeTimeArray, storeClosedArray]); + const isSpecificDayClosedAndAllSameTime = hasClosedDay + && isAllSameOpenTimeExceptClosedDays + && isAllSameCloseTimeExceptClosedDays; + + return { + isAllSameTime, + hasClosedDay, + isAllSameOpenTimeExceptClosedDays, + isAllSameCloseTimeExceptClosedDays, + isSpecificDayClosedAndAllSameTime, + }; +} diff --git a/src/page/ShopRegistration/hooks/useOperateTimeState.ts b/src/page/ShopRegistration/hooks/useOperateTimeState.ts new file mode 100644 index 00000000..888ac8d0 --- /dev/null +++ b/src/page/ShopRegistration/hooks/useOperateTimeState.ts @@ -0,0 +1,31 @@ +import { useEffect, useState } from 'react'; +import useModalStore from 'store/modalStore'; +import { WEEK } from 'utils/constant/week'; + +type OperateTimeProps = Record; + +export default function useOperateTimeState() { + const { + openTimeState, + closeTimeState, + shopClosedState, + } = useModalStore(); + + const [operateTimeState, setOperateTimeState] = useState({}); + + const openDay = WEEK.filter((day) => shopClosedState[day] === false)[0]; + + useEffect(() => { + setOperateTimeState((prevOperateTimeState) => ({ + ...prevOperateTimeState, + holiday: `매주 ${WEEK.filter((day) => shopClosedState[day]).join('요일 ')}요일 정기 휴무`, + time: `${openTimeState[openDay]} ~ ${closeTimeState[openDay]}`, + })); + }, [openTimeState, closeTimeState, shopClosedState, openDay]); + + WEEK.forEach((day) => { + operateTimeState[day] = shopClosedState[day] ? `매주 ${day} 정기 휴무` : `${openTimeState[day]} ~ ${closeTimeState[day]}`; + }); + + return operateTimeState; +} diff --git a/src/page/ShopRegistration/index.tsx b/src/page/ShopRegistration/index.tsx new file mode 100644 index 00000000..40e55ecb --- /dev/null +++ b/src/page/ShopRegistration/index.tsx @@ -0,0 +1,13 @@ +import useMediaQuery from 'utils/hooks/useMediaQuery'; +import ShopRegistrationMobile from './view/Mobile'; +import ShopRegistrationPC from './view/PC'; + +export default function ShopRegistration() { + const { isMobile } = useMediaQuery(); + + return ( +
+ {isMobile ? : } +
+ ); +} diff --git a/src/page/StoreRegistration/view/Mobile/Main/Main.module.scss b/src/page/ShopRegistration/view/Mobile/Main/Main.module.scss similarity index 100% rename from src/page/StoreRegistration/view/Mobile/Main/Main.module.scss rename to src/page/ShopRegistration/view/Mobile/Main/Main.module.scss diff --git a/src/page/StoreRegistration/view/Mobile/Main/index.tsx b/src/page/ShopRegistration/view/Mobile/Main/index.tsx similarity index 89% rename from src/page/StoreRegistration/view/Mobile/Main/index.tsx rename to src/page/ShopRegistration/view/Mobile/Main/index.tsx index 358aa1ef..5d978dd6 100644 --- a/src/page/StoreRegistration/view/Mobile/Main/index.tsx +++ b/src/page/ShopRegistration/view/Mobile/Main/index.tsx @@ -1,4 +1,4 @@ -import { ReactComponent as EmptyImgIcon } from 'assets/svg/StoreRegistration/mobile-empty-img.svg'; +import { ReactComponent as EmptyImgIcon } from 'assets/svg/shopRegistration/mobile-empty-img.svg'; import useStepStore from 'store/useStepStore'; import styles from './Main.module.scss'; diff --git a/src/page/StoreRegistration/view/Mobile/StoreCategory/StoreCategory.module.scss b/src/page/ShopRegistration/view/Mobile/ShopCategory/ShopCategory.module.scss similarity index 100% rename from src/page/StoreRegistration/view/Mobile/StoreCategory/StoreCategory.module.scss rename to src/page/ShopRegistration/view/Mobile/ShopCategory/ShopCategory.module.scss diff --git a/src/page/StoreRegistration/view/Mobile/StoreCategory/index.tsx b/src/page/ShopRegistration/view/Mobile/ShopCategory/index.tsx similarity index 86% rename from src/page/StoreRegistration/view/Mobile/StoreCategory/index.tsx rename to src/page/ShopRegistration/view/Mobile/ShopCategory/index.tsx index 253232ec..62bbf143 100644 --- a/src/page/StoreRegistration/view/Mobile/StoreCategory/index.tsx +++ b/src/page/ShopRegistration/view/Mobile/ShopCategory/index.tsx @@ -1,13 +1,13 @@ import { useState } from 'react'; import useStepStore from 'store/useStepStore'; -import useStoreCategory from 'query/storeCategory'; +import useShopCategory from 'query/shopCategory'; import cn from 'utils/ts/className'; -import styles from './StoreCategory.module.scss'; +import styles from './ShopCategory.module.scss'; type Category = string; -export default function StoreCategory() { - const { categoryList } = useStoreCategory(); +export default function ShopCategory() { + const { categoryList } = useShopCategory(); const { increaseStep } = useStepStore(); const [selectedCategory, setSelectedCategory] = useState(null); diff --git a/src/page/StoreRegistration/view/Mobile/StoreConfirmation/StoreConfirmation.module.scss b/src/page/ShopRegistration/view/Mobile/ShopConfirmation/ShopConfirmation.module.scss similarity index 100% rename from src/page/StoreRegistration/view/Mobile/StoreConfirmation/StoreConfirmation.module.scss rename to src/page/ShopRegistration/view/Mobile/ShopConfirmation/ShopConfirmation.module.scss diff --git a/src/page/StoreRegistration/view/Mobile/StoreConfirmation/index.tsx b/src/page/ShopRegistration/view/Mobile/ShopConfirmation/index.tsx similarity index 96% rename from src/page/StoreRegistration/view/Mobile/StoreConfirmation/index.tsx rename to src/page/ShopRegistration/view/Mobile/ShopConfirmation/index.tsx index d5fe9273..7cc19d57 100644 --- a/src/page/StoreRegistration/view/Mobile/StoreConfirmation/index.tsx +++ b/src/page/ShopRegistration/view/Mobile/ShopConfirmation/index.tsx @@ -1,7 +1,7 @@ import useStepStore from 'store/useStepStore'; -import styles from './StoreConfirmation.module.scss'; +import styles from './ShopConfirmation.module.scss'; -export default function StoreConfirmation() { +export default function ShopConfirmation() { const { increaseStep } = useStepStore(); return ( diff --git a/src/page/StoreRegistration/view/Mobile/StoreEntry/StoreEntry.module.scss b/src/page/ShopRegistration/view/Mobile/ShopEntry/ShopEntry.module.scss similarity index 100% rename from src/page/StoreRegistration/view/Mobile/StoreEntry/StoreEntry.module.scss rename to src/page/ShopRegistration/view/Mobile/ShopEntry/ShopEntry.module.scss diff --git a/src/page/StoreRegistration/view/Mobile/StoreEntry/index.tsx b/src/page/ShopRegistration/view/Mobile/ShopEntry/index.tsx similarity index 79% rename from src/page/StoreRegistration/view/Mobile/StoreEntry/index.tsx rename to src/page/ShopRegistration/view/Mobile/ShopEntry/index.tsx index 043286a7..2dab6b92 100644 --- a/src/page/StoreRegistration/view/Mobile/StoreEntry/index.tsx +++ b/src/page/ShopRegistration/view/Mobile/ShopEntry/index.tsx @@ -1,8 +1,8 @@ -import { ReactComponent as Memo } from 'assets/svg/StoreRegistration/memo.svg'; +import { ReactComponent as Memo } from 'assets/svg/shopRegistration/memo.svg'; import useStepStore from 'store/useStepStore'; -import styles from './StoreEntry.module.scss'; +import styles from './ShopEntry.module.scss'; -export default function StoreEntry() { +export default function ShopEntry() { const { increaseStep } = useStepStore(); return (
diff --git a/src/page/StoreRegistration/view/Mobile/StoreRegistrationMobile.module.scss b/src/page/ShopRegistration/view/Mobile/ShopRegistrationMobile.module.scss similarity index 100% rename from src/page/StoreRegistration/view/Mobile/StoreRegistrationMobile.module.scss rename to src/page/ShopRegistration/view/Mobile/ShopRegistrationMobile.module.scss diff --git a/src/page/StoreRegistration/view/Mobile/Sub/Sub.module.scss b/src/page/ShopRegistration/view/Mobile/Sub/Sub.module.scss similarity index 100% rename from src/page/StoreRegistration/view/Mobile/Sub/Sub.module.scss rename to src/page/ShopRegistration/view/Mobile/Sub/Sub.module.scss diff --git a/src/page/StoreRegistration/view/Mobile/Sub/index.tsx b/src/page/ShopRegistration/view/Mobile/Sub/index.tsx similarity index 96% rename from src/page/StoreRegistration/view/Mobile/Sub/index.tsx rename to src/page/ShopRegistration/view/Mobile/Sub/index.tsx index 0bb7a178..90cffd00 100644 --- a/src/page/StoreRegistration/view/Mobile/Sub/index.tsx +++ b/src/page/ShopRegistration/view/Mobile/Sub/index.tsx @@ -1,5 +1,5 @@ /* eslint-disable jsx-a11y/label-has-associated-control */ -import OperateTimeMobile from 'page/StoreRegistration/component/Modal/OperateTimeMobile'; +import OperateTimeMobile from 'page/ShopRegistration/component/Modal/OperateTimeMobile'; import useBooleanState from 'utils/hooks/useBooleanState'; import useStepStore from 'store/useStepStore'; import styles from './Sub.module.scss'; diff --git a/src/page/StoreRegistration/view/Mobile/index.tsx b/src/page/ShopRegistration/view/Mobile/index.tsx similarity index 79% rename from src/page/StoreRegistration/view/Mobile/index.tsx rename to src/page/ShopRegistration/view/Mobile/index.tsx index 7136e729..20af89a9 100644 --- a/src/page/StoreRegistration/view/Mobile/index.tsx +++ b/src/page/ShopRegistration/view/Mobile/index.tsx @@ -5,26 +5,26 @@ import Complete from 'component/common/Auth/Complete'; import SubTitle from 'component/common/Auth/SubTitle'; import useStepStore from 'store/useStepStore'; import PROGRESS_TITLE from 'utils/constant/progress'; -import StoreEntry from 'page/StoreRegistration/view/Mobile/StoreEntry'; -import StoreCategory from 'page/StoreRegistration/view/Mobile/StoreCategory'; -import Main from 'page/StoreRegistration/view/Mobile/Main'; -import Sub from 'page/StoreRegistration/view/Mobile/Sub'; -import StoreConfirmation from 'page/StoreRegistration/view/Mobile/StoreConfirmation'; -import styles from './StoreRegistrationMobile.module.scss'; +import ShopEntry from 'page/ShopRegistration/view/Mobile/ShopEntry'; +import ShopCategory from 'page/ShopRegistration/view/Mobile/ShopCategory'; +import Main from 'page/ShopRegistration/view/Mobile/Main'; +import Sub from 'page/ShopRegistration/view/Mobile/Sub'; +import ShopConfirmation from 'page/ShopRegistration/view/Mobile/ShopConfirmation'; +import styles from './ShopRegistrationMobile.module.scss'; -export default function StoreRegistrationMobile() { +export default function ShopRegistrationMobile() { const { TOTAL_STEP, step, decreaseStep } = useStepStore(); return (
- {step === 0 && } + {step === 0 && } {step === 1 && ( <> - + )} {step === 2 && ( @@ -46,7 +46,7 @@ export default function StoreRegistrationMobile() {
- + )} {step === 5 && ( diff --git a/src/page/StoreRegistration/view/PC/StoreRegistrationPC.module.scss b/src/page/ShopRegistration/view/PC/ShopRegistrationPC.module.scss similarity index 90% rename from src/page/StoreRegistration/view/PC/StoreRegistrationPC.module.scss rename to src/page/ShopRegistration/view/PC/ShopRegistrationPC.module.scss index e3a4c839..12fa10ba 100644 --- a/src/page/StoreRegistration/view/PC/StoreRegistrationPC.module.scss +++ b/src/page/ShopRegistration/view/PC/ShopRegistrationPC.module.scss @@ -77,7 +77,7 @@ align-items: center; gap: 16px; - &__label { + &__title { display: block; font-size: 18px; margin-bottom: 8px; @@ -92,6 +92,16 @@ height: 93px; border: 1px solid #d2dae2; padding: 53px 80px 54px; + cursor: pointer; + } + + &__upload-file { + display: none; + } + + &__main-menu { + width: 370px; + height: 202px; } &__cutlery-cross { @@ -104,7 +114,7 @@ position: relative; } - &__title { + &__text { text-align: center; font-size: 14px; display: block; @@ -130,11 +140,22 @@ } } + &__input-large { + width: 336px; + border: 1px solid #d2dae2; + height: 22px; + padding: 13px 16px; + + &:focus { + border-bottom: 1px solid black; + } + } + &__operate-time { display: flex; align-items: center; width: 272px; - height: 48px; + height: auto; font-size: 16px; color: #858585; } diff --git a/src/page/ShopRegistration/view/PC/index.tsx b/src/page/ShopRegistration/view/PC/index.tsx new file mode 100644 index 00000000..f553efb2 --- /dev/null +++ b/src/page/ShopRegistration/view/PC/index.tsx @@ -0,0 +1,309 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +import { ReactComponent as Memo } from 'assets/svg/shopRegistration/memo.svg'; +import { ReactComponent as Logo } from 'assets/svg/auth/koin-logo.svg'; +import { ReactComponent as Cutlery } from 'assets/svg/shopRegistration/cutlery.svg'; +import { useEffect } from 'react'; +import useStepStore from 'store/useStepStore'; +import Copyright from 'component/common/Copyright'; +import CustomButton from 'page/Auth/Signup/component/CustomButton'; +import Complete from 'component/common/Auth/Complete'; +import InputBox from 'page/ShopRegistration/component/InputBox'; +import Category from 'page/ShopRegistration/component/Modal/Category'; +import SearchShop from 'page/ShopRegistration/component/Modal/SearchShop'; +import OperateTimePC from 'page/ShopRegistration/component/Modal/OperateTimePC'; +import ConfirmPopup from 'page/ShopRegistration/component/ConfirmPopup'; +import useMediaQuery from 'utils/hooks/useMediaQuery'; +import useBooleanState from 'utils/hooks/useBooleanState'; +import CustomModal from 'component/common/CustomModal'; +import useModalStore from 'store/modalStore'; +import { WEEK, DAY_OF_WEEK } from 'utils/constant/week'; +import { SubmitHandler, useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { OwnerShop } from 'model/shopInfo/ownerShop'; +import { useMutation } from '@tanstack/react-query'; +import { postShop } from 'api/shop'; +import useImageUpload from 'utils/hooks/useImageUpload'; +import CheckSameTime from 'page/ShopRegistration/hooks/CheckSameTime'; +import useOperateTimeState from 'page/ShopRegistration/hooks/useOperateTimeState'; +import styles from './ShopRegistrationPC.module.scss'; + +export default function ShopRegistrationPC() { + const { isMobile } = useMediaQuery(); + const { step, setStep } = useStepStore(); + const { + value: showCategory, + setTrue: openCategory, + setFalse: closeCategory, + changeValue: toggleCategory, + } = useBooleanState(false); + const { + value: showOperateTime, + setTrue: openOperateTime, + setFalse: closeOperateTime, + } = useBooleanState(false); + const { + value: showSearchShop, + setTrue: openSearchShop, + setFalse: closeSearchShop, + } = useBooleanState(false); + const { + value: showConfirmPopup, + setTrue: openConfirmPopup, + setFalse: closeConfirmPopup, + } = useBooleanState(false); + const { imgFile, imgRef, saveImgFile } = useImageUpload(); + + const { + categoryState, + searchShopState, + setSearchShopState, + openTimeState, + closeTimeState, + shopClosedState, + } = useModalStore(); + + const operateTimeState = useOperateTimeState(); + + const { + isAllSameTime, + hasClosedDay, + isSpecificDayClosedAndAllSameTime, + } = CheckSameTime(); + + const { + register, handleSubmit, setValue, + } = useForm({ + resolver: zodResolver(OwnerShop), + }); + + const mutation = useMutation({ + mutationFn: (form: OwnerShop) => postShop(form), + onSuccess: () => setStep(5), + }); + + const openTimeArray = Object.values(openTimeState); + const closeTimeArray = Object.values(closeTimeState); + const shopClosedArray = Object.values(shopClosedState); + + useEffect(() => { + const openValue = DAY_OF_WEEK.map((day, index) => ({ + close_time: closeTimeArray[index], + closed: shopClosedArray[index], + day_of_week: day, + open_time: openTimeArray[index], + })); + setValue('open', openValue); + setValue('image_urls', [imgFile]); + setValue('name', searchShopState); + setValue('category_ids', [categoryState[1]]); + }, [openTimeState, closeTimeState, imgFile]); + + const onSubmit: SubmitHandler = (data) => { + mutation.mutate(data); + }; + + // step 1일 때 그리고 모바일에서 PC로 변경 될 때 카테고리 모달을 자동으로 켜줌 + useEffect(() => { + if (!isMobile && step === 1) { + toggleCategory(); + } + }, []); + + return ( + <> + {step === 0 && ( +
+
+ + 가게 정보 기입 +
+ + 가게의 다양한 정보를 입력 및 수정하여 +
+ 학생들에게 최신 가게 정보를 알려주세요 +
+
+ +
+ +
+ )} + {step >= 1 && step <= 4 && ( +
+
+ +
+
+ 대표 이미지 + +
+
+ 카테고리 +
+ + +
+
+ + + +
+ 대표자명 +
+ +
+
+
+ 가게명 +
+ { + setSearchShopState(e.target.value); + }} + /> + +
+
+ + + + + + +
+ 운영시간 +
+
+
+ { + isAllSameTime && !hasClosedDay ? ( +
+ {operateTimeState.time} +
+ ) + : null + } + { + isSpecificDayClosedAndAllSameTime ? ( +
+
{operateTimeState.time}
+
{operateTimeState.holiday}
+
+ ) : null + } + { + !isAllSameTime && !isSpecificDayClosedAndAllSameTime ? ( + <> + {WEEK.map((day) => ( +
+ {shopClosedState[day] ? `${operateTimeState[day]}` : `${day} : ${operateTimeState[day]}`} +
+ ))} + + ) : null + } +
+
+ +
+
+ + + + +
+ + + +
+
+ +
+ + +
+ +
+ )} + {step === 5 && ( +
+ + +
+ )} + + ); +} diff --git a/src/page/StoreRegistration/component/InputBox/index.tsx b/src/page/StoreRegistration/component/InputBox/index.tsx deleted file mode 100644 index 17119b0c..00000000 --- a/src/page/StoreRegistration/component/InputBox/index.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import styles from './InputBox.module.scss'; - -interface InputProps { - content: string; - id: string; -} - -export default function InputBox({ content, id }: InputProps) { - return ( - - ); -} diff --git a/src/page/StoreRegistration/component/Modal/ConfirmStore/index.tsx b/src/page/StoreRegistration/component/Modal/ConfirmStore/index.tsx deleted file mode 100644 index e5058ee8..00000000 --- a/src/page/StoreRegistration/component/Modal/ConfirmStore/index.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import styles from './ConfirmStore.module.scss'; - -interface ConfirmStoreProps { - open: boolean; - onCancel: () => void; -} - -export default function ConfirmStore({ open, onCancel }: ConfirmStoreProps) { - if (!open) return null; - return ( -
-
- 가장 맛있는 족발 -
- 전화번호 - 041-523-5849 -
-
- -
- ); -} diff --git a/src/page/StoreRegistration/component/Modal/OperateTimePC/index.tsx b/src/page/StoreRegistration/component/Modal/OperateTimePC/index.tsx deleted file mode 100644 index 836142aa..00000000 --- a/src/page/StoreRegistration/component/Modal/OperateTimePC/index.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import TimePicker from 'page/StoreRegistration/component/TimePicker'; -import WEEK from 'utils/constant/week'; -import styles from './OperateTimePC.module.scss'; - -export default function OperateTimePC() { - return ( - - - - - - - - - - {WEEK.map((day) => ( - - - - - - ))} - -
요일시간휴무
{day} - - {' ~ '} - -
- ); -} diff --git a/src/page/StoreRegistration/component/Modal/SearchStore/index.tsx b/src/page/StoreRegistration/component/Modal/SearchStore/index.tsx deleted file mode 100644 index 2d36953d..00000000 --- a/src/page/StoreRegistration/component/Modal/SearchStore/index.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { ReactComponent as Magnifier } from 'assets/svg/StoreRegistration/magnifier.svg'; -import cn from 'utils/ts/className'; -import { useState } from 'react'; -import useBooleanState from 'utils/hooks/useBooleanState'; -import ConfirmStore from 'page/StoreRegistration/component/Modal/ConfirmStore'; -import styles from './SearchStore.module.scss'; - -interface SearchStoreProps { - open: boolean; - onCancel: () => void; -} - -export default function SearchStore({ open, onCancel }: SearchStoreProps) { - const [selectedStore, setSelectedStore] = useState(''); - const { value: showConfirmStore, setValue: setShowConfirmStore } = useBooleanState(false); - - function toggleStore() { - setSelectedStore(selectedStore === '가장 맛있는 족발' ? ' ' : '가장 맛있는 족발'); - } - - function toggleModal() { - setShowConfirmStore((prev) => !prev); - } - - if (!open) return null; - return ( -
-
- - -
-
- -
- -
- ); -} diff --git a/src/page/StoreRegistration/component/TimePicker/index.tsx b/src/page/StoreRegistration/component/TimePicker/index.tsx deleted file mode 100644 index 1ef17201..00000000 --- a/src/page/StoreRegistration/component/TimePicker/index.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { - useState, useRef, useEffect, MouseEvent, -} from 'react'; -import styles from './TimePicker.module.scss'; - -const hours: number[] = Array.from({ length: 24 }, (_, i) => i); -const minutes: number[] = Array.from({ length: 12 }, (_, i) => i * 5); - -export default function TimePicker() { - const dropMenuRef = useRef(null); - const [isOpen, setIsOpen] = useState(false); - const [time, setTime] = useState({ - hour: '00', - minute: '00', - }); - - useEffect(() => { - const handleClickOutside = (e: Event) => { - if (isOpen && (!dropMenuRef.current?.contains(e.target as Node))) setIsOpen(false); - }; - document.addEventListener('mousedown', handleClickOutside); - - return () => document.removeEventListener('mousedown', handleClickOutside); - }, [isOpen]); - - function toggleModal() { - setIsOpen(!isOpen); - } - - function handleClickTimeChangeButton(e: MouseEvent) { - const selectedTime = parseInt(e.currentTarget.value, 10); - const selectedId = e.currentTarget.id; - - // 선택한 시간 또는 분을 텍스트로 업데이트 - setTime({ - ...time, - [selectedId]: selectedTime.toString().padStart(2, '0'), - }); - } - - return ( -
- - : - - {isOpen && ( -
-
- {hours.map((hour) => ( - - ))} -
-
- {minutes.map((minute) => ( - - ))} -
-
- )} -
- ); -} diff --git a/src/page/StoreRegistration/index.tsx b/src/page/StoreRegistration/index.tsx deleted file mode 100644 index 0c192830..00000000 --- a/src/page/StoreRegistration/index.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import useMediaQuery from 'utils/hooks/useMediaQuery'; -import StoreRegistrationMobile from './view/Mobile'; -import StoreRegistrationPC from './view/PC'; - -export default function StoreRegistration() { - const { isMobile } = useMediaQuery(); - - return ( -
- {isMobile ? : } -
- ); -} diff --git a/src/page/StoreRegistration/view/PC/index.tsx b/src/page/StoreRegistration/view/PC/index.tsx deleted file mode 100644 index 98aa2c2e..00000000 --- a/src/page/StoreRegistration/view/PC/index.tsx +++ /dev/null @@ -1,181 +0,0 @@ -/* eslint-disable react-hooks/exhaustive-deps */ -import { ReactComponent as Memo } from 'assets/svg/StoreRegistration/memo.svg'; -import { ReactComponent as Logo } from 'assets/svg/auth/koin-logo.svg'; -import { ReactComponent as Cutlery } from 'assets/svg/StoreRegistration/cutlery.svg'; -import { useEffect } from 'react'; -import useStepStore from 'store/useStepStore'; -import Copyright from 'component/common/Copyright'; -import CustomButton from 'page/Auth/Signup/component/CustomButton'; -import Complete from 'component/common/Auth/Complete'; -import InputBox from 'page/StoreRegistration/component/InputBox'; -import Category from 'page/StoreRegistration/component/Modal/Category'; -import SearchStore from 'page/StoreRegistration/component/Modal/SearchStore'; -import OperateTimePC from 'page/StoreRegistration/component/Modal/OperateTimePC'; -import ConfirmPopup from 'page/StoreRegistration/component/ConfirmPopup'; -import useMediaQuery from 'utils/hooks/useMediaQuery'; -import useBooleanState from 'utils/hooks/useBooleanState'; -import CustomModal from 'component/common/CustomModal'; -import styles from './StoreRegistrationPC.module.scss'; - -export default function StoreRegistrationPC() { - const { isMobile } = useMediaQuery(); - const { step, setStep } = useStepStore(); - const { - value: showCategory, - setTrue: openCategory, - setFalse: closeCategory, - changeValue: toggleCategory, - } = useBooleanState(false); - const { - value: showOperateTime, - setTrue: openOperateTime, - setFalse: closeOperateTime, - } = useBooleanState(false); - const { - value: showSearchStore, - setTrue: openSearchStore, - setFalse: closeSearchStore, - } = useBooleanState(false); - const { - value: showConfirmPopup, - setTrue: openConfirmPopup, - setFalse: closeConfirmPopup, - } = useBooleanState(false); - - // step 1일 때 그리고 모바일에서 PC로 변경 될 때 카테고리 모달을 자동으로 켜줌 - useEffect(() => { - if (!isMobile && step === 1) { - toggleCategory(); - } - }, []); - - return ( - <> - {step === 0 && ( -
-
- - 가게 정보 기입 -
- - 가게의 다양한 정보를 입력 및 수정하여 -
- 학생들에게 최신 가게 정보를 알려주세요 -
-
- -
- -
- )} - {step >= 1 && step <= 4 && ( -
-
- -
-
- 대표 이미지 -
- - 클릭하여 이미지를 등록해주세요. -
-
-
- 카테고리 -
- - -
-
- - - - -
- 가게명 -
- - -
-
- - - - - - -
- 운영시간 -
-
- 00:00 ~ 24:00 -
- -
-
- - - - -
- - - -
-
- -
- -
-
- -
- )} - {step === 5 && ( -
- - -
- )} - - ); -} diff --git a/src/query/shopCategory.ts b/src/query/shopCategory.ts new file mode 100644 index 00000000..f4eb260e --- /dev/null +++ b/src/query/shopCategory.ts @@ -0,0 +1,9 @@ +import { useQuery } from '@tanstack/react-query'; +import getShopCategory from 'api/category'; + +const useShopCategory = () => { + const { data: categoryList } = useQuery(['shopCategory'], getShopCategory); + return { categoryList }; +}; + +export default useShopCategory; diff --git a/src/query/shops.ts b/src/query/shops.ts new file mode 100644 index 00000000..05575515 --- /dev/null +++ b/src/query/shops.ts @@ -0,0 +1,9 @@ +import { useQuery } from '@tanstack/react-query'; +import { getShopList } from 'api/shop'; + +const useShopList = () => { + const { data: shopList, isError } = useQuery(['allshops'], getShopList); + return { shopList, isError }; +}; + +export default useShopList; diff --git a/src/query/storeCategory.ts b/src/query/storeCategory.ts deleted file mode 100644 index 2c8d6e56..00000000 --- a/src/query/storeCategory.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; -import getStoreCategory from 'api/category'; - -const useStoreCategory = () => { - const { data: categoryList } = useQuery(['storeCategory'], getStoreCategory); - return { categoryList }; -}; - -export default useStoreCategory; diff --git a/src/store/modalStore.ts b/src/store/modalStore.ts new file mode 100644 index 00000000..db155d39 --- /dev/null +++ b/src/store/modalStore.ts @@ -0,0 +1,52 @@ +import { create } from 'zustand'; +import { WEEK } from 'utils/constant/week'; + +type OperatingTime = { [key in typeof WEEK[number]]: string | null }; + +interface ModalStore { + categoryState: [string, number]; + searchShopState: string; + openTimeState: OperatingTime; + closeTimeState: OperatingTime; + shopClosedState: { [key: string]: boolean }; + setCategoryState: (state: [string, number]) => void; + setSearchShopState: (state: string) => void; + setOpenTimeState: (state: OperatingTime) => void; + setCloseTimeState: (state: OperatingTime) => void; + setShopClosedState: (state: { [key: string]: boolean }) => void; +} + +const initialOperatingTime: OperatingTime = { + 월: '00:00', + 화: '00:00', + 수: '00:00', + 목: '00:00', + 금: '00:00', + 토: '00:00', + 일: '00:00', +}; + +const initialShopClosed = { + 월: false, + 화: false, + 수: false, + 목: false, + 금: false, + 토: false, + 일: false, +}; + +const useModalStore = create((set) => ({ + categoryState: ['', 0], + searchShopState: '', + openTimeState: initialOperatingTime, + closeTimeState: initialOperatingTime, + shopClosedState: initialShopClosed, + setCategoryState: (state) => set({ categoryState: state }), + setSearchShopState: (state) => set({ searchShopState: state }), + setOpenTimeState: (state) => set(() => ({ openTimeState: state })), + setCloseTimeState: (state) => set(() => ({ closeTimeState: state })), + setShopClosedState: (state) => set({ shopClosedState: state }), +})); + +export default useModalStore; diff --git a/src/utils/constant/week.ts b/src/utils/constant/week.ts index 315d9590..77821c2b 100644 --- a/src/utils/constant/week.ts +++ b/src/utils/constant/week.ts @@ -1,3 +1,3 @@ -const WEEK = ['월', '화', '수', '목', '금', '토', '일'] as const; +export const WEEK = ['월', '화', '수', '목', '금', '토', '일'] as const; -export default WEEK; +export const DAY_OF_WEEK = ['MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY', 'SUNDAY']; diff --git a/src/utils/hooks/useImageUpload.ts b/src/utils/hooks/useImageUpload.ts new file mode 100644 index 00000000..7228662e --- /dev/null +++ b/src/utils/hooks/useImageUpload.ts @@ -0,0 +1,23 @@ +import { useState, useRef } from 'react'; + +export default function useImageUpload() { + const [imgFile, setImgFile] = useState(''); + const imgRef = useRef(null); + + const saveImgFile = () => { + const file = imgRef.current?.files?.[0]; + const reader = new FileReader(); + + reader.onloadend = () => { + const { result } = reader; + if (typeof result === 'string') { + setImgFile(result); + } + }; + if (file) { + reader.readAsDataURL(file); + } + }; + + return { imgFile, imgRef, saveImgFile }; +}