From 4a5f5c9e3b62df3fd63965e8a7533b864a50e75c Mon Sep 17 00:00:00 2001 From: naro_Kim Date: Sat, 22 Jul 2023 22:52:03 +0900 Subject: [PATCH 01/22] =?UTF-8?q?chore:=20eslint=20any=20issue=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Input/hook/useForwardRef.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/components/Input/hook/useForwardRef.ts b/src/components/Input/hook/useForwardRef.ts index eadeb410..9a687809 100644 --- a/src/components/Input/hook/useForwardRef.ts +++ b/src/components/Input/hook/useForwardRef.ts @@ -1,19 +1,20 @@ import { useRef, useEffect, ForwardedRef } from 'react'; +// eslint-disable-next-line @typescript-eslint/no-explicit-any const useForwardRef = (ref: ForwardedRef, initialValue: any = null) => { - const targetRef = useRef(initialValue); + const targetRef = useRef(initialValue); - useEffect(() => { - if (!ref) return; + useEffect(() => { + if (!ref) return; - if (typeof ref === 'function') { - ref(targetRef.current); - } else { - ref.current = targetRef.current; - } - }, [ref]); + if (typeof ref === 'function') { + ref(targetRef.current); + } else { + ref.current = targetRef.current; + } + }, [ref]); - return targetRef; + return targetRef; }; export default useForwardRef; From 80fbd3d015297914a1522be4a85cac7f396d3cf4 Mon Sep 17 00:00:00 2001 From: naro_Kim Date: Sat, 22 Jul 2023 22:58:25 +0900 Subject: [PATCH 02/22] =?UTF-8?q?feat:=20=EC=9D=B4=ED=83=88=EB=B0=A9?= =?UTF-8?q?=EC=A7=80=20=EB=AA=A8=EB=8B=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/PreventModal/PreventModal.tsx | 38 +++++++++++++++++++ src/app/write/hooks/usePreventClose.ts | 27 +++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 src/app/write/components/PreventModal/PreventModal.tsx create mode 100644 src/app/write/hooks/usePreventClose.ts diff --git a/src/app/write/components/PreventModal/PreventModal.tsx b/src/app/write/components/PreventModal/PreventModal.tsx new file mode 100644 index 00000000..5734bbe5 --- /dev/null +++ b/src/app/write/components/PreventModal/PreventModal.tsx @@ -0,0 +1,38 @@ +import { Button, Modal } from '@/components'; +import { useOverlay } from '@/hooks'; +import { useRouter } from 'next/navigation'; +import { useCallback } from 'react'; + +interface ModalProps { + onClose: () => void; +} + +const PreventModal = ({ onClose }: ModalProps) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [openModal, closeModal] = useOverlay(); + const router = useRouter(); + + const handleClickOut = useCallback(() => { + router.back(); + }, [router]); + + const handleClose = useCallback(() => { + onClose(); + }, [onClose]); + + return ( + + + 페이지를 나가면 작성 중인 먹팟이 삭제돼요. + + + + + + ); +}; +export default PreventModal; diff --git a/src/app/write/hooks/usePreventClose.ts b/src/app/write/hooks/usePreventClose.ts new file mode 100644 index 00000000..70c835f3 --- /dev/null +++ b/src/app/write/hooks/usePreventClose.ts @@ -0,0 +1,27 @@ +import { useBooleanState } from '@/hooks'; +import { useCallback, useEffect } from 'react'; + +const usePreventClose = () => { + const [bool, setTrue, setFalse] = useBooleanState(); + const preventClose = useCallback( + (e: BeforeUnloadEvent) => { + e.preventDefault(); + e.returnValue = ''; + setTrue(); + }, + [setTrue], + ); + + useEffect(() => { + (() => { + window.addEventListener('beforeunload', preventClose); + })(); + return () => { + window.removeEventListener('beforeunload', preventClose); + }; + }, [preventClose]); + + return [bool, setFalse] as const; +}; + +export default usePreventClose; From 90438bfe6149c52b9e8141b8979274a24278b4c0 Mon Sep 17 00:00:00 2001 From: naro_Kim Date: Sat, 22 Jul 2023 22:59:47 +0900 Subject: [PATCH 03/22] =?UTF-8?q?fix:=20react-hook-form,=20zustand=20store?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/write/hooks/useWriteForm.ts | 5 ++-- src/app/write/store/useFormStore.tsx | 38 ++++++++++++++++++++++++---- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/src/app/write/hooks/useWriteForm.ts b/src/app/write/hooks/useWriteForm.ts index e462f816..911a6e9d 100644 --- a/src/app/write/hooks/useWriteForm.ts +++ b/src/app/write/hooks/useWriteForm.ts @@ -8,13 +8,14 @@ export const useWriteForm = () => { const stepOneMethod = useForm({ resolver: zodResolver(stepOneSchema), - mode: 'onChange', - defaultValues: stepOne || {}, + defaultValues: { ...stepOne }, + values: { ...stepOne }, }); const stepTwoMethod = useForm({ resolver: zodResolver(boardSchema), defaultValues: { ...stepOne, ...stepTwo }, + values: { ...stepOne, ...stepTwo }, }); return { stepOneMethod, stepTwoMethod }; diff --git a/src/app/write/store/useFormStore.tsx b/src/app/write/store/useFormStore.tsx index 92bed2fc..61ac2ed8 100644 --- a/src/app/write/store/useFormStore.tsx +++ b/src/app/write/store/useFormStore.tsx @@ -9,24 +9,52 @@ const stepVariant = { }; type setDataType = { step: 1; data: StepOneData } | { step: 2; data: StepTwoData }; +type initDataType = { stepOneData: StepOneData; stepTwoData: StepTwoData }; + +const INIT_STEPONE = { + meetingDate: new Date(), + timezone: '오후', + meetingTime: '12:00', + maxApply: 2, + minAge: null, + maxAge: null, + locationName: '', + x: 0, + y: 0, + region_1depth_name: '', + region_2depth_name: '', + locationDetail: null, +}; + +const INIT_STEPTWO = { + title: '', + content: '', + chatLink: '', +}; interface FormState { - stepOne: StepOneData | null; - stepTwo: StepTwoData | null; + stepOne: StepOneData; + stepTwo: StepTwoData; + initData: ({ stepOneData, stepTwoData }: initDataType) => void; setData: ({ step, data }: setDataType) => void; reset: () => void; } const useFormStore = create()( devtools((set) => ({ - stepOne: null, - stepTwo: null, + stepOne: INIT_STEPONE, + stepTwo: INIT_STEPTWO, + initData: ({ stepOneData, stepTwoData }) => + set({ + stepOne: stepOneData, + stepTwo: stepTwoData, + }), setData: ({ step, data }) => set((state) => ({ ...state, [stepVariant[step]]: data, })), - reset: () => set((state) => ({ ...state, stepOne: null, stepTwo: null })), + reset: () => set((state) => ({ ...state, stepOne: INIT_STEPONE, stepTwo: INIT_STEPTWO })), })), ); From 17e1b173b150264406df2c8da31b37571fe6acb4 Mon Sep 17 00:00:00 2001 From: naro_Kim Date: Sat, 22 Jul 2023 23:03:33 +0900 Subject: [PATCH 04/22] =?UTF-8?q?feat:=20=EC=88=98=EC=A0=95=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20api=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/index.ts | 3 ++ src/api/write/api.ts | 10 +++++- src/api/write/hooks/index.ts | 3 ++ src/api/write/hooks/useGetBoard.ts | 21 +++++++++++ src/api/write/index.ts | 3 ++ src/api/write/queryKeys.ts | 4 +++ src/app/write/hooks/useSetFormData.ts | 52 +++++++++++++++++++++++++++ 7 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 src/api/write/hooks/index.ts create mode 100644 src/api/write/hooks/useGetBoard.ts create mode 100644 src/api/write/index.ts create mode 100644 src/api/write/queryKeys.ts create mode 100644 src/app/write/hooks/useSetFormData.ts diff --git a/src/api/index.ts b/src/api/index.ts index fe7673e8..320f3f93 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,12 +1,15 @@ import { userKeys, userAPI } from './user'; import { boardKeys, boardAPI } from './board'; +import { writeAPI, writeKeys } from './write'; export const queryKeys = { user: userKeys, board: boardKeys, + write: writeKeys, } as const; export const api = { user: userAPI, board: boardAPI, + write: writeAPI, } as const; diff --git a/src/api/write/api.ts b/src/api/write/api.ts index 458802d2..5ef5bc4b 100644 --- a/src/api/write/api.ts +++ b/src/api/write/api.ts @@ -1,5 +1,5 @@ import { request } from '@/utils/ky/request'; -import { ParsedData, PostResponse } from '@/app/write/types'; +import { BoardData, ParsedData, PostResponse } from '@/app/write/types'; class WriteAPI { /** * 유저가 입력한 데이터로 먹팟을 생성합니다. @@ -28,6 +28,14 @@ class WriteAPI { }) .json(); } + + /** + * boardId에 해당하는 먹팟을 수정하기 위해 정보를 가져옵니다. + * @param boardId - 가져올 board의 id + */ + async getBoardDetail(boardId: number) { + return request.get(`v1/boards/${boardId}/update`).json(); + } } // api fetchers instance diff --git a/src/api/write/hooks/index.ts b/src/api/write/hooks/index.ts new file mode 100644 index 00000000..9548bfc8 --- /dev/null +++ b/src/api/write/hooks/index.ts @@ -0,0 +1,3 @@ +export { default as useGetBoard } from './useGetBoard'; + +export { default as usePostBoard } from './usePostBoard'; diff --git a/src/api/write/hooks/useGetBoard.ts b/src/api/write/hooks/useGetBoard.ts new file mode 100644 index 00000000..e342dbad --- /dev/null +++ b/src/api/write/hooks/useGetBoard.ts @@ -0,0 +1,21 @@ +import { writeKeys } from '@/api/write/queryKeys'; +import { writeAPI } from '@/api/write/api'; +import { useSuspenseQuery } from '@suspensive/react-query'; +import { BoardData } from '@/app/write/types'; + +interface Props { + boardId: number; + onSuccess: (data: BoardData) => void; +} + +const useGetBoard = ({ boardId, onSuccess }: Props) => { + return useSuspenseQuery({ + queryKey: writeKeys.board(boardId), + queryFn: () => writeAPI.getBoardDetail(boardId), + onSuccess: (data) => { + onSuccess(data); + }, + }); +}; + +export default useGetBoard; diff --git a/src/api/write/index.ts b/src/api/write/index.ts new file mode 100644 index 00000000..48bdbdea --- /dev/null +++ b/src/api/write/index.ts @@ -0,0 +1,3 @@ +export * from './hooks'; +export * from './queryKeys'; +export * from './api'; diff --git a/src/api/write/queryKeys.ts b/src/api/write/queryKeys.ts new file mode 100644 index 00000000..383b097d --- /dev/null +++ b/src/api/write/queryKeys.ts @@ -0,0 +1,4 @@ +export const writeKeys = { + all: ['write'], + board: (boardId: number) => [...writeKeys.all, 'detail', boardId] as const, +}; diff --git a/src/app/write/hooks/useSetFormData.ts b/src/app/write/hooks/useSetFormData.ts new file mode 100644 index 00000000..5902c7ec --- /dev/null +++ b/src/app/write/hooks/useSetFormData.ts @@ -0,0 +1,52 @@ +import { ParsedData, StepOneData, StepTwoData } from '../types'; +import useFormStore from '../store/useFormStore'; +import { useGetBoard } from '@/api/write'; + +const getParsedData = (board: ParsedData) => { + const date = new Date(board.meetingDate); + const timezone = board.meetingTime.startsWith('오후') ? '오후' : '오전'; + const time = board.meetingTime.slice(3); + const stepOneData: StepOneData = { + meetingDate: date, + meetingTime: time, + maxApply: board.maxApply, + minAge: board.minAge, + maxAge: board.maxAge, + timezone: timezone, + region_1depth_name: board.region_1depth_name, + region_2depth_name: board.region_2depth_name, + locationName: board.locationName, + x: board.x, + y: board.y, + locationDetail: board.locationDetail, + }; + + const stepTwoData: StepTwoData = { + title: board.title, + content: board.content, + chatLink: board.chatLink, + }; + + return { + stepOneData, + stepTwoData, + }; +}; + +const useSetFormData = (boardId: string) => { + const { initData } = useFormStore(); + const id = Number(boardId); + + useGetBoard({ + boardId: id, + onSuccess: (boardData) => { + const { stepOneData, stepTwoData } = getParsedData(boardData); + initData({ + stepOneData, + stepTwoData, + }); + }, + }); +}; + +export default useSetFormData; From 2e219937544533bf13eaba1727e7002250b8578d Mon Sep 17 00:00:00 2001 From: naro_Kim Date: Sat, 22 Jul 2023 23:06:21 +0900 Subject: [PATCH 05/22] =?UTF-8?q?feat:=20=EC=88=98=EC=A0=95=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20=EB=B2=84=ED=8A=BC=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DetailMenuButton/DetailMenuButton.tsx | 6 ++- .../InputLoading/InputLoading.css.ts | 41 +++++++++++++++++++ .../components/InputLoading/InputLoading.tsx | 36 ++++++++++++++++ 3 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 src/app/write/components/InputLoading/InputLoading.css.ts create mode 100644 src/app/write/components/InputLoading/InputLoading.tsx diff --git a/src/app/board/components/DetailMenuButton/DetailMenuButton.tsx b/src/app/board/components/DetailMenuButton/DetailMenuButton.tsx index 5e89821a..71fe3d9e 100644 --- a/src/app/board/components/DetailMenuButton/DetailMenuButton.tsx +++ b/src/app/board/components/DetailMenuButton/DetailMenuButton.tsx @@ -44,6 +44,10 @@ const DetailMenuButton = () => { ); }; + const handleClickEditBoard = () => { + router.push(`/write/${boardId}`); + }; + const handleClickCancelJoinButton = () => { openModal( { {isWriter && ( <> - + 수정하기 diff --git a/src/app/write/components/InputLoading/InputLoading.css.ts b/src/app/write/components/InputLoading/InputLoading.css.ts new file mode 100644 index 00000000..3d281b23 --- /dev/null +++ b/src/app/write/components/InputLoading/InputLoading.css.ts @@ -0,0 +1,41 @@ +import { screenMQ } from '@/styles/theme.css'; +import { style } from '@vanilla-extract/css'; + +export const loadingForm = style({ + position: 'relative', + top: '116px', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', +}); + +export const wrapper = style({ + display: 'flex', + flexDirection: 'column', + alignItems: 'flex-start', + justifyContent: 'center', + width: '100%', +}); + +export const row = style({ + display: 'grid', + gridAutoFlow: 'column', + gridAutoColumns: '1fr 3fr', + alignItems: 'center', + justifyContent: 'space-between', + width: '100%', + marginBottom: '16px', + '@media': { + [screenMQ.m]: { + gridAutoFlow: 'row', + alignItems: 'flex-start', + gap: '1rem', + }, + }, +}); + +export const section = style({ + display: 'flex', + flexDirection: 'column', + gap: '56px', +}); diff --git a/src/app/write/components/InputLoading/InputLoading.tsx b/src/app/write/components/InputLoading/InputLoading.tsx new file mode 100644 index 00000000..047cca8d --- /dev/null +++ b/src/app/write/components/InputLoading/InputLoading.tsx @@ -0,0 +1,36 @@ +import { Skeleton } from '@/components'; +import { loadingForm, row, section, wrapper } from './InputLoading.css'; + +const SkeletonInput = () => { + return ( +
+ + {Array(2) + .fill(0) + .map((_, index) => ( +
+ + +
+ ))} +
+ ); +}; + +const InputLoading = () => { + return ( +
+
+ + + + +
+
+ +
+
+ ); +}; + +export default InputLoading; From adee0b3ffa2ee820682fa11cde2cf00e3b5d0034 Mon Sep 17 00:00:00 2001 From: naro_Kim Date: Sat, 22 Jul 2023 23:08:36 +0900 Subject: [PATCH 06/22] =?UTF-8?q?feat:=20=EC=88=98=EC=A0=95=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/write/[id]/page.tsx | 23 +++++++++ src/app/write/components/Form/BoardForm.tsx | 46 +++++++++++++++++ src/app/write/components/Form/FirstStep.tsx | 6 +-- src/app/write/components/Form/SecondStep.tsx | 53 +++++++++++++------- src/app/write/components/index.ts | 1 + src/app/write/page.tsx | 6 +-- 6 files changed, 110 insertions(+), 25 deletions(-) create mode 100644 src/app/write/[id]/page.tsx create mode 100644 src/app/write/components/Form/BoardForm.tsx diff --git a/src/app/write/[id]/page.tsx b/src/app/write/[id]/page.tsx new file mode 100644 index 00000000..57da83ba --- /dev/null +++ b/src/app/write/[id]/page.tsx @@ -0,0 +1,23 @@ +import { notFound } from 'next/navigation'; +import { InputLoading } from '../components'; +import { Suspense } from 'react'; +import BoardForm from '../components/Form/BoardForm'; + +const BoardEditPage = async ({ + params: { id }, +}: { + params: { + id: string; + }; +}) => { + const boardId = Number(id); + if (Number.isNaN(boardId)) notFound(); + + return ( + }> + + + ); +}; + +export default BoardEditPage; diff --git a/src/app/write/components/Form/BoardForm.tsx b/src/app/write/components/Form/BoardForm.tsx new file mode 100644 index 00000000..433d8508 --- /dev/null +++ b/src/app/write/components/Form/BoardForm.tsx @@ -0,0 +1,46 @@ +'use client'; + +import FirstStep from './FirstStep'; +import SecondStep from './SecondStep'; +import { wrapper } from '../../style.css'; +import { useFunnel } from '@/hooks'; +import { useProfile } from '@/api/hooks'; +import { useParams, useRouter } from 'next/navigation'; +import WriteTitle from '../WriteTitle/WriteTitle'; +import useSetFormData from '../../hooks/useSetFormData'; +import useFormStore from '../../store/useFormStore'; +import { useEffect } from 'react'; + +export default function BoardForm() { + const { data } = useProfile(); + const router = useRouter(); + + const { id: boardId } = useParams(); + useSetFormData(boardId); + const { reset, setData } = useFormStore(); + const [step, { prevStep, nextStep }] = useFunnel(['1', '2']); + const preventClose = (e: BeforeUnloadEvent) => { + e.preventDefault(); + e.returnValue = ''; + }; + + useEffect(() => { + window.addEventListener('beforeunload', preventClose); + return () => { + reset(); + window.removeEventListener('beforeunload', preventClose); + }; + }, []); + + if (!data) { + router.push('/login'); + } + + return ( +
+ + {step === '1' && } + {step === '2' && } +
+ ); +} diff --git a/src/app/write/components/Form/FirstStep.tsx b/src/app/write/components/Form/FirstStep.tsx index 7481b78b..a4ceb32b 100644 --- a/src/app/write/components/Form/FirstStep.tsx +++ b/src/app/write/components/Form/FirstStep.tsx @@ -1,9 +1,9 @@ 'use client'; + import { useCallback } from 'react'; import { FormProvider } from 'react-hook-form'; import { Button, Input, InputSection, Typography } from '@/components'; import { InputDate, Counter, AgeModal, AgeBottomSheet, MapModal, TimeDropDown } from '@/app/write/components'; -import useFormStore from '@/app/write/store/useFormStore'; import { StepOneData } from '@/app/write/types'; import { useWriteForm } from '@/app/write/hooks/useWriteForm'; import { formWrapper, sectionGap, inputGap, submitButton, flexBetween } from './Form.css'; @@ -11,11 +11,11 @@ import { useIsMobile } from '@/hooks'; type stepProps = { nextStep: () => void; + setData: ({ step, data }: { step: 1; data: StepOneData }) => void; }; -const FirstStep = ({ nextStep }: stepProps) => { +const FirstStep = ({ nextStep, setData }: stepProps) => { const { stepOneMethod } = useWriteForm(); - const { setData } = useFormStore(); const mobile = useIsMobile(); const onSubmit = useCallback( diff --git a/src/app/write/components/Form/SecondStep.tsx b/src/app/write/components/Form/SecondStep.tsx index e4ff82ab..6bb1b0bb 100644 --- a/src/app/write/components/Form/SecondStep.tsx +++ b/src/app/write/components/Form/SecondStep.tsx @@ -1,42 +1,57 @@ 'use client'; + import { FormProvider, SubmitHandler } from 'react-hook-form'; -import useFormStore from '@/app/write/store/useFormStore'; import { BoardData, PostResponse } from '@/app/write/types'; import { Button, Input, InputSection, TextArea, Toast, Typography } from '@/components'; import { formWrapper, inputGap } from './Form.css'; import parseData from './util/parseData'; import useWriteBoard from '@/api/write/hooks/usePostBoard'; import { useRouter } from 'next/navigation'; -import { useWriteForm } from '../../hooks/useWriteForm'; +import { useWriteForm } from '@/app/write/hooks/useWriteForm'; import { useOverlay } from '@/hooks'; +import { writeAPI } from '@/api/write'; + +type StepProps = { + reset: () => void; + boardId?: number; + isPatch?: boolean; +}; -const SecondStep = () => { +const SecondStep = ({ reset, boardId, isPatch = false }: StepProps) => { const { mutate: board } = useWriteBoard(); const { stepTwoMethod } = useWriteForm(); const [openToast, closeToast] = useOverlay(); - const { reset } = useFormStore(); const router = useRouter(); const onSubmit: SubmitHandler = async (data: BoardData) => { if (!data) { return; } - board( - { ...parseData(data) }, - { - onSuccess: (response: PostResponse) => { - openToast(); - router.push(`/board/${response.boardId}`); - reset(); + if (!isPatch) { + board( + { ...parseData(data) }, + { + onSuccess: (response: PostResponse) => { + openToast(); + router.push(`/board/${response.boardId}`); + reset(); + }, + onError: (error) => { + openToast(); + if (error.response.status === 403) { + router.push('/login'); + } + }, }, - onError: (error) => { - openToast(); - if (error.response.status === 403) { - router.push('/login'); - } - }, - }, - ); + ); + } + if (isPatch && boardId) { + writeAPI.patchBoard(boardId, { ...parseData(data) }).then(() => { + openToast(); + router.push(`/board/${boardId}`); + reset(); + }); + } }; return ( diff --git a/src/app/write/components/index.ts b/src/app/write/components/index.ts index 39db6aff..09f0aa70 100644 --- a/src/app/write/components/index.ts +++ b/src/app/write/components/index.ts @@ -7,3 +7,4 @@ export { default as SecondStep } from './Form/SecondStep'; export { default as MapModal } from './MapModal/MapModal'; export { default as WriteTitle } from './WriteTitle/WriteTitle'; export { default as TimeDropDown } from './TimeDropDown/TimeDropDown'; +export { default as InputLoading } from './InputLoading/InputLoading'; diff --git a/src/app/write/page.tsx b/src/app/write/page.tsx index 9e539aad..243497e2 100644 --- a/src/app/write/page.tsx +++ b/src/app/write/page.tsx @@ -11,7 +11,7 @@ import { useRouter } from 'next/navigation'; export default function Write() { const [step, { prevStep, nextStep }] = useFunnel(['1', '2']); - const { reset } = useFormStore(); + const { reset, setData } = useFormStore(); const { data } = useProfile(); const router = useRouter(); if (!data) { @@ -35,8 +35,8 @@ export default function Write() { return (
- {step === '1' && } - {step === '2' && } + {step === '1' && } + {step === '2' && }
); } From 4029c46ff29b2aecc90ad94543aab27897d7932d Mon Sep 17 00:00:00 2001 From: naro_Kim Date: Sat, 22 Jul 2023 23:09:29 +0900 Subject: [PATCH 07/22] =?UTF-8?q?fix:=20pc=20view=20use=20client=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/write/components/AgeModal/AgeModal.tsx | 2 ++ src/app/write/components/Counter/Counter.tsx | 1 + src/app/write/components/MapModal/MapModal.tsx | 2 ++ src/app/write/components/TimeDropDown/TimeDropDown.tsx | 2 ++ 4 files changed, 7 insertions(+) diff --git a/src/app/write/components/AgeModal/AgeModal.tsx b/src/app/write/components/AgeModal/AgeModal.tsx index bfd0a1bc..9a99ea4c 100644 --- a/src/app/write/components/AgeModal/AgeModal.tsx +++ b/src/app/write/components/AgeModal/AgeModal.tsx @@ -1,3 +1,5 @@ +'use client'; + import { Control, FieldValues, useFormContext } from 'react-hook-form'; import { HTMLAttributes, useCallback } from 'react'; import { Button, Modal, ModalContent, ModalFooter, ModalHeader, SvgIcon } from '@/components'; diff --git a/src/app/write/components/Counter/Counter.tsx b/src/app/write/components/Counter/Counter.tsx index 61be5d7e..e7bcf74c 100644 --- a/src/app/write/components/Counter/Counter.tsx +++ b/src/app/write/components/Counter/Counter.tsx @@ -1,3 +1,4 @@ +'use client'; import { useCallback, HTMLAttributes } from 'react'; import { FieldValues, FieldPath, Controller, Control, useFormContext } from 'react-hook-form'; import { IconButton } from '@/components'; diff --git a/src/app/write/components/MapModal/MapModal.tsx b/src/app/write/components/MapModal/MapModal.tsx index cb34bf62..0e7a10cb 100644 --- a/src/app/write/components/MapModal/MapModal.tsx +++ b/src/app/write/components/MapModal/MapModal.tsx @@ -1,3 +1,5 @@ +'use client'; + import { useFormContext } from 'react-hook-form'; import { Map, Input } from '@/components'; import { useOverlay } from '@/hooks'; diff --git a/src/app/write/components/TimeDropDown/TimeDropDown.tsx b/src/app/write/components/TimeDropDown/TimeDropDown.tsx index a23153e2..97d2bd6c 100644 --- a/src/app/write/components/TimeDropDown/TimeDropDown.tsx +++ b/src/app/write/components/TimeDropDown/TimeDropDown.tsx @@ -1,3 +1,5 @@ +'use client'; + import { InputDropdown, InputSection } from '@/components'; import { TIMELIST_AM, TIMELIST_PM, TIME_SELECT } from '../../constants'; import { useFormContext } from 'react-hook-form'; From 601c3d7d2d75915294ea88dde39a2a4244017a4c Mon Sep 17 00:00:00 2001 From: naro_Kim Date: Mon, 24 Jul 2023 14:16:57 +0900 Subject: [PATCH 08/22] =?UTF-8?q?fix:=20map=20Modal=20data=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/write/components/MapModal/MapModal.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/write/components/MapModal/MapModal.tsx b/src/app/write/components/MapModal/MapModal.tsx index 0e7a10cb..f7a45157 100644 --- a/src/app/write/components/MapModal/MapModal.tsx +++ b/src/app/write/components/MapModal/MapModal.tsx @@ -14,12 +14,12 @@ const MapModal = () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const onClick = (data: any) => { if (data) { - method.setValue('locationName', `${data.address_name} ${data.place_name}`); + method.setValue('locationName', `${data.address_name}`); if (data.x && data.y) { method.setValue('x', parseFloat(data.x)); method.setValue('y', parseFloat(data.y)); - method.setValue('region_1depth_name', parseFloat(data.region_1depth_name)); - method.setValue('region_2depth_name', parseFloat(data.region_2depth_name)); + method.setValue('region_1depth_name', data.region_1depth_name); + method.setValue('region_2depth_name', data.region_2depth_name); } } closeModal(); From c4ee2f79c63317e31d1a1e12af6d132eb310db8e Mon Sep 17 00:00:00 2001 From: Yangseungchan Date: Mon, 24 Jul 2023 22:03:11 +0900 Subject: [PATCH 09/22] =?UTF-8?q?chore:=20BottomSheet=EA=B0=80=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A1=A4=20=EA=B0=80=EB=8A=A5=ED=95=98=EA=B2=8C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/BottomSheet/BottomSheet.css.ts | 9 +++++++++ src/components/BottomSheet/BottomSheetView.tsx | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/components/BottomSheet/BottomSheet.css.ts b/src/components/BottomSheet/BottomSheet.css.ts index 6c8982c5..9954281a 100644 --- a/src/components/BottomSheet/BottomSheet.css.ts +++ b/src/components/BottomSheet/BottomSheet.css.ts @@ -73,3 +73,12 @@ export const wrap = recipe({ open: false, }, }); + +export const itemWrapper = style({ + overflow: 'scroll', + selectors: { + '&::-webkit-scrollbar': { + display: 'none', + }, + }, +}); diff --git a/src/components/BottomSheet/BottomSheetView.tsx b/src/components/BottomSheet/BottomSheetView.tsx index 5e39b7d8..af7916c1 100644 --- a/src/components/BottomSheet/BottomSheetView.tsx +++ b/src/components/BottomSheet/BottomSheetView.tsx @@ -2,7 +2,7 @@ import { HTMLAttributes, forwardRef } from 'react'; import cx from 'classnames'; import { IconButton } from '@/components'; import { useLockScroll } from '@/hooks'; -import { titleWrapper, background, wrap } from './BottomSheet.css'; +import { titleWrapper, background, wrap, itemWrapper } from './BottomSheet.css'; interface Props extends HTMLAttributes { /** BottomSheet 제목으로 표시할 텍스트 */ @@ -27,7 +27,7 @@ const BottomSheetView = forwardRef(
{title}
- {children} +
{children}
); From 7512c0672e29002bfdd105d63c284664501bf5d1 Mon Sep 17 00:00:00 2001 From: Yangseungchan Date: Mon, 24 Jul 2023 22:10:48 +0900 Subject: [PATCH 10/22] =?UTF-8?q?chore:=20=EC=8B=9C=EA=B0=84=20=EB=93=9C?= =?UTF-8?q?=EB=9E=8D=EB=8B=A4=EC=9A=B4=20=ED=8C=A8=EB=94=A9=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/BottomSheet/BottomSheetView.tsx | 6 +++--- src/components/Input/Input.css.ts | 4 ++++ src/components/Input/InputDropdown.tsx | 10 ++++++++-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/components/BottomSheet/BottomSheetView.tsx b/src/components/BottomSheet/BottomSheetView.tsx index af7916c1..5aa05867 100644 --- a/src/components/BottomSheet/BottomSheetView.tsx +++ b/src/components/BottomSheet/BottomSheetView.tsx @@ -21,13 +21,13 @@ const BottomSheetView = forwardRef( return ( <> -
-
+
+
{title}
-
{children}
+
{children}
); diff --git a/src/components/Input/Input.css.ts b/src/components/Input/Input.css.ts index 843d9773..bf9b0428 100644 --- a/src/components/Input/Input.css.ts +++ b/src/components/Input/Input.css.ts @@ -121,6 +121,10 @@ export const fixedError = recipe({ }, }); +export const bottomSheetContent = style({ + padding: `0 ${space.sm}`, +}); + globalStyle(`${clearButton} > img`, { margin: '0 auto', backgroundPosition: 'center', diff --git a/src/components/Input/InputDropdown.tsx b/src/components/Input/InputDropdown.tsx index 56f9a853..c15e368f 100644 --- a/src/components/Input/InputDropdown.tsx +++ b/src/components/Input/InputDropdown.tsx @@ -2,7 +2,7 @@ import { FieldValues, FieldPath, Controller, Control } from 'react-hook-form'; import { Dropdown } from '@/components'; import InputErrorMessage from './InputErrorMessage'; -import { inputWrapper } from './Input.css'; +import { inputWrapper, bottomSheetContent } from './Input.css'; import { HTMLAttributes } from 'react'; import { useIsMobile } from '@/hooks'; @@ -40,7 +40,13 @@ const InputDropdown = ({ ...props }: TControl) => { {value} {mobile ? ( - + {selections.map((v) => ( {v} From 7f28b10af3bababff25768ca62dc6bae16175974 Mon Sep 17 00:00:00 2001 From: Yangseungchan Date: Tue, 25 Jul 2023 13:07:44 +0900 Subject: [PATCH 11/22] =?UTF-8?q?chore:=20AgeController=20=EB=AA=A8?= =?UTF-8?q?=EB=B0=94=EC=9D=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/AgeModal/AgeController.tsx | 11 +++++-- src/components/Dropdown/Dropdown.tsx | 13 ++++---- src/components/Dropdown/DropdownWrapper.tsx | 30 +++++++++++-------- src/hooks/useClickOutside/useClickOutside.ts | 7 ++++- 4 files changed, 38 insertions(+), 23 deletions(-) diff --git a/src/app/write/components/AgeModal/AgeController.tsx b/src/app/write/components/AgeModal/AgeController.tsx index 5aaa78df..5e66aa12 100644 --- a/src/app/write/components/AgeModal/AgeController.tsx +++ b/src/app/write/components/AgeModal/AgeController.tsx @@ -1,6 +1,8 @@ import { Dropdown, DropdownButton, DropdownItem, DropdownMenu } from '@/components'; import { Control, Controller, FieldValues } from 'react-hook-form'; import { AGE_LIST } from '@/app/write/constants'; +import DropdownModal from '@/components/Dropdown/DropdownModal'; +import { useIsMobile } from '@/hooks'; type ControllerProps = { control: Control; @@ -11,23 +13,26 @@ type ControllerProps = { // eslint-disable-next-line @typescript-eslint/no-explicit-any const AgeController = ({ disabled = false, control, name, placeholder }: ControllerProps) => { + const isMobile = useIsMobile(); + const DropdownItemWrapper = isMobile ? DropdownModal : DropdownMenu; + return ( ( - + {value && `${value}세`} - + {AGE_LIST.map((v) => ( {v} ))} - + )} /> diff --git a/src/components/Dropdown/Dropdown.tsx b/src/components/Dropdown/Dropdown.tsx index 858bdd38..1d8b66e6 100644 --- a/src/components/Dropdown/Dropdown.tsx +++ b/src/components/Dropdown/Dropdown.tsx @@ -1,16 +1,17 @@ 'use client'; +import { ComponentProps } from 'react'; import { DropdownContextProvider } from './contexts/DropdownContext'; import DropdownWrapper from './DropdownWrapper'; -type Props = React.HTMLAttributes; +type Props = ComponentProps; const Dropdown = ({ children, ...rest }: Props) => { - return ( - - {children} - - ); + return ( + + {children} + + ); }; export default Dropdown; diff --git a/src/components/Dropdown/DropdownWrapper.tsx b/src/components/Dropdown/DropdownWrapper.tsx index ea9cb1b5..94df4b87 100644 --- a/src/components/Dropdown/DropdownWrapper.tsx +++ b/src/components/Dropdown/DropdownWrapper.tsx @@ -2,22 +2,26 @@ import clsx from 'classnames'; import { useClickOutside } from '@/hooks'; import { wrapper } from './Dropdown.css'; import { useDropdownContext } from './contexts/DropdownContext'; +import { HTMLAttributes } from 'react'; -type Props = React.HTMLAttributes; +interface Props extends HTMLAttributes { + disableClickOutside?: boolean; +} -const DropdownWrapper = ({ children, className, ...rest }: Props) => { - const { closeDropdown } = useDropdownContext(); - const ref = useClickOutside({ - onClickOutside: () => { - closeDropdown(); - }, - }); +const DropdownWrapper = ({ children, className, disableClickOutside = false, ...rest }: Props) => { + const { closeDropdown } = useDropdownContext(); + const ref = useClickOutside({ + onClickOutside: () => { + closeDropdown(); + }, + disabled: disableClickOutside, + }); - return ( -
- {children} -
- ); + return ( +
+ {children} +
+ ); }; export default DropdownWrapper; diff --git a/src/hooks/useClickOutside/useClickOutside.ts b/src/hooks/useClickOutside/useClickOutside.ts index 5604fc7b..06134eda 100644 --- a/src/hooks/useClickOutside/useClickOutside.ts +++ b/src/hooks/useClickOutside/useClickOutside.ts @@ -12,21 +12,26 @@ interface Props { onClickOutside: () => void; /** 외부 클릭 이벤트 종류 */ event?: keyof ClickOutsideEvents; + /** 비활성화 여부 */ + disabled?: boolean; } /** * @param {Props} props * @param {() => void} props.onClickOutside - 외부 클릭 이벤트 발생 시 실행할 함수 * @param {keyof ClickOutsideEvents} props.event - 외부 클릭 이벤트 종류 + * @param {boolean} props.disabled - 비활성화 여부 * @returns {React.RefObject} 외부 클릭 이벤트를 감지할 element ref */ const useClickOutside = ({ onClickOutside, event = 'mousedown', + disabled = false, }: Props): React.RefObject => { const ref = useRef(null); useEffect(() => { + if (disabled) return; const handleClickOutside = (e: ClickOutsideEvents[typeof event]) => { // ref가 overlay-container내에 없는데, overlay-container 안에서 발생한 이벤트 인 경우 if (!ref.current?.closest(OVERLAY_CONTAINER) && (e.target as HTMLElement).closest(OVERLAY_CONTAINER)) { @@ -41,7 +46,7 @@ const useClickOutside = ({ return () => { window.removeEventListener(event, handleClickOutside); }; - }, [onClickOutside, event]); + }, [onClickOutside, event, disabled]); return ref; }; From 59620d273ea447768a22ab403bb2db42e768b5f8 Mon Sep 17 00:00:00 2001 From: naro_Kim Date: Tue, 25 Jul 2023 14:02:43 +0900 Subject: [PATCH 12/22] =?UTF-8?q?fix:=20CTA=20=ED=99=9C=EC=84=B1=ED=99=94,?= =?UTF-8?q?=20=ED=97=AC=ED=8D=BC=ED=85=8D=EC=8A=A4=ED=8A=B8=20=EC=8A=A4?= =?UTF-8?q?=ED=82=A4=EB=A7=88=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/write/components/Form/FirstStep.tsx | 7 +------ src/app/write/components/Form/SecondStep.tsx | 2 +- src/app/write/lib/schema.ts | 10 +++------- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/src/app/write/components/Form/FirstStep.tsx b/src/app/write/components/Form/FirstStep.tsx index a4ceb32b..3b5b80e6 100644 --- a/src/app/write/components/Form/FirstStep.tsx +++ b/src/app/write/components/Form/FirstStep.tsx @@ -89,12 +89,7 @@ const FirstStep = ({ nextStep, setData }: stepProps) => {
- diff --git a/src/app/write/components/Form/SecondStep.tsx b/src/app/write/components/Form/SecondStep.tsx index 6bb1b0bb..82c260bc 100644 --- a/src/app/write/components/Form/SecondStep.tsx +++ b/src/app/write/components/Form/SecondStep.tsx @@ -78,7 +78,7 @@ const SecondStep = ({ reset, boardId, isPatch = false }: StepProps) => { 베타서비스에서는 채팅 기능이 제공되지 않습니다.
효율적인 소통을 위해 오픈 채팅방을 만들어주세요. - diff --git a/src/app/write/lib/schema.ts b/src/app/write/lib/schema.ts index d9d9810e..d99e3afb 100644 --- a/src/app/write/lib/schema.ts +++ b/src/app/write/lib/schema.ts @@ -8,12 +8,8 @@ export const stepOneSchema = z required_error: '필수 항목을 입력해주세요.', }) .default(new Date()), - timezone: z - .string({ invalid_type_error: '필수 항목을 입력해주세요.', required_error: '필수 항목을 입력해주세요.' }) - .default('오후'), - meetingTime: z - .string({ invalid_type_error: '필수 항목을 입력해주세요.', required_error: '필수 항목을 입력해주세요.' }) - .default('12:00'), + timezone: z.string().default('오후'), + meetingTime: z.string().default('12:00'), maxApply: z.coerce.number().min(2, { message: '최소 인원은 2명 이상 가능합니다.' }).default(2), minAge: z.coerce.number().min(20).max(100).optional().nullable().default(null), maxAge: z.coerce.number().min(20).max(100).optional().nullable().default(null), @@ -45,7 +41,7 @@ export const stepTwoSchema = z.object({ content: z.string().nullable().optional(), chatLink: z .string({ invalid_type_error: '필수 입력 항목을 입력해주세요.', required_error: '필수 입력 항목을 입력해주세요.' }) - .url({ message: '올바른 형태의 url을 입력해 주세요.' }) + .nonempty({ message: '필수 입력 항목을 입력해주세요.' }) .max(300, { message: '링크는 300(자)를 넘을 수 없습니다.' }) .regex(new RegExp('https://open.kakao.com/o/[A-Za-z0-9]+'), { message: '올바른 형태의 url을 입력해 주세요.' }), }); From 76d612fcc6c098365278b3379f712d0a5fe1b0bc Mon Sep 17 00:00:00 2001 From: naro_Kim Date: Tue, 25 Jul 2023 14:03:18 +0900 Subject: [PATCH 13/22] =?UTF-8?q?chore:=20=EB=AF=B8=EC=82=AC=EC=9A=A9=20?= =?UTF-8?q?=ED=9B=85=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Input/hook/useForwardRef.ts | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 src/components/Input/hook/useForwardRef.ts diff --git a/src/components/Input/hook/useForwardRef.ts b/src/components/Input/hook/useForwardRef.ts deleted file mode 100644 index 9a687809..00000000 --- a/src/components/Input/hook/useForwardRef.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { useRef, useEffect, ForwardedRef } from 'react'; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const useForwardRef = (ref: ForwardedRef, initialValue: any = null) => { - const targetRef = useRef(initialValue); - - useEffect(() => { - if (!ref) return; - - if (typeof ref === 'function') { - ref(targetRef.current); - } else { - ref.current = targetRef.current; - } - }, [ref]); - - return targetRef; -}; - -export default useForwardRef; From 425ff52c681e34c32455dd9dc7d04e8aea8e0220 Mon Sep 17 00:00:00 2001 From: naro_Kim Date: Tue, 25 Jul 2023 14:03:58 +0900 Subject: [PATCH 14/22] =?UTF-8?q?fix:=20validation=20stroke=20=ED=99=9C?= =?UTF-8?q?=EC=84=B1=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/write/hooks/useWriteForm.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/app/write/hooks/useWriteForm.ts b/src/app/write/hooks/useWriteForm.ts index 911a6e9d..165772f6 100644 --- a/src/app/write/hooks/useWriteForm.ts +++ b/src/app/write/hooks/useWriteForm.ts @@ -8,12 +8,14 @@ export const useWriteForm = () => { const stepOneMethod = useForm({ resolver: zodResolver(stepOneSchema), + mode: 'onSubmit', defaultValues: { ...stepOne }, values: { ...stepOne }, }); const stepTwoMethod = useForm({ resolver: zodResolver(boardSchema), + mode: 'onSubmit', defaultValues: { ...stepOne, ...stepTwo }, values: { ...stepOne, ...stepTwo }, }); From 90f513464a26d7f6cbeb64b5db5354ef689e887e Mon Sep 17 00:00:00 2001 From: naro_Kim Date: Tue, 25 Jul 2023 14:24:10 +0900 Subject: [PATCH 15/22] fix: dayPicker mobile update --- .../write/components/InputDate/InputDate.tsx | 33 +++---------------- .../components/PreventModal/PreventModal.tsx | 3 +- src/components/DateInput/DateInput.tsx | 21 ++++++++---- 3 files changed, 20 insertions(+), 37 deletions(-) diff --git a/src/app/write/components/InputDate/InputDate.tsx b/src/app/write/components/InputDate/InputDate.tsx index 8a3acfcd..388a4088 100644 --- a/src/app/write/components/InputDate/InputDate.tsx +++ b/src/app/write/components/InputDate/InputDate.tsx @@ -3,10 +3,7 @@ import dayjs from 'dayjs'; import { HTMLAttributes } from 'react'; import { FieldValues, FieldPath, Controller, Control } from 'react-hook-form'; -import { DateInput, DayPicker, BottomSheet } from '@/components'; -import { inputWrapper } from '@/components/Input/Input.css'; -import { useOverlay, useIsMobile } from '@/hooks'; -import { preventClick } from './InputDate.css'; +import { DateInput } from '@/components'; import InputErrorMessage from '@/components/Input/InputErrorMessage'; type TControl = { @@ -18,42 +15,20 @@ type TControl = { showError?: boolean; } & HTMLAttributes; -interface FieldProps { - value: Date; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - onChange: (event: any) => void; -} - // eslint-disable-next-line @typescript-eslint/no-explicit-any const InputDate = ({ ...props }: TControl) => { - const mobile = useIsMobile(); - const [openBottomSheet, closeBottomSheet] = useOverlay(); - const { name, control } = props; const now = dayjs().format('YYYY년 MM월 DD일 (오늘)'); - const renderBottomSheet = ({ value, onChange }: FieldProps) => { - return ( - - - - ); - }; - return ( ( -
{ - mobile && openBottomSheet(renderBottomSheet({ value, onChange })); - }} - > - + <> + -
+ )} /> ); diff --git a/src/app/write/components/PreventModal/PreventModal.tsx b/src/app/write/components/PreventModal/PreventModal.tsx index 5734bbe5..d56ae85a 100644 --- a/src/app/write/components/PreventModal/PreventModal.tsx +++ b/src/app/write/components/PreventModal/PreventModal.tsx @@ -8,8 +8,7 @@ interface ModalProps { } const PreventModal = ({ onClose }: ModalProps) => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [openModal, closeModal] = useOverlay(); + const [, closeModal] = useOverlay(); const router = useRouter(); const handleClickOut = useCallback(() => { diff --git a/src/components/DateInput/DateInput.tsx b/src/components/DateInput/DateInput.tsx index 05b47994..b27b8ebf 100644 --- a/src/components/DateInput/DateInput.tsx +++ b/src/components/DateInput/DateInput.tsx @@ -3,10 +3,11 @@ import { useRef } from 'react'; import { DayPickerSingleProps, SelectSingleEventHandler } from 'react-day-picker'; -import { Dropdown, DropdownButton, DropdownMenu, DayPicker } from '@/components'; +import { Dropdown, DayPicker } from '@/components'; import { DropdownMenuHandle } from '@/components/Dropdown/DropdownMenu'; import { selectedDateCaption } from './utils/caption'; import { menu } from './DateInput.css'; +import { useIsMobile } from '@/hooks'; interface Props extends Omit { /** 선택된 날짜 상태값 */ @@ -17,10 +18,12 @@ interface Props extends Omit { const menuRef = useRef(null); + const mobile = useIsMobile(); const handleSelectDate: SelectSingleEventHandler = (date) => { onSelect(date); @@ -29,12 +32,18 @@ const DateInput = ({ selected, onSelect, placeholder = '날짜 선택', isError, return ( - + {selected ? selectedDateCaption(selected) : null} - - - - + + {mobile ? ( + + + + ) : ( + + + + )} ); }; From ddd419ae7af0e9eb455954368d3597993360bff9 Mon Sep 17 00:00:00 2001 From: naro_Kim Date: Tue, 25 Jul 2023 15:42:47 +0900 Subject: [PATCH 16/22] =?UTF-8?q?chore:=20=EB=AF=B8=EC=82=AC=EC=9A=A9=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/PreventModal/PreventModal.tsx | 37 ------------------- src/app/write/hooks/usePreventClose.ts | 27 -------------- 2 files changed, 64 deletions(-) delete mode 100644 src/app/write/components/PreventModal/PreventModal.tsx delete mode 100644 src/app/write/hooks/usePreventClose.ts diff --git a/src/app/write/components/PreventModal/PreventModal.tsx b/src/app/write/components/PreventModal/PreventModal.tsx deleted file mode 100644 index d56ae85a..00000000 --- a/src/app/write/components/PreventModal/PreventModal.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { Button, Modal } from '@/components'; -import { useOverlay } from '@/hooks'; -import { useRouter } from 'next/navigation'; -import { useCallback } from 'react'; - -interface ModalProps { - onClose: () => void; -} - -const PreventModal = ({ onClose }: ModalProps) => { - const [, closeModal] = useOverlay(); - const router = useRouter(); - - const handleClickOut = useCallback(() => { - router.back(); - }, [router]); - - const handleClose = useCallback(() => { - onClose(); - }, [onClose]); - - return ( - - - 페이지를 나가면 작성 중인 먹팟이 삭제돼요. - - - - - - ); -}; -export default PreventModal; diff --git a/src/app/write/hooks/usePreventClose.ts b/src/app/write/hooks/usePreventClose.ts deleted file mode 100644 index 70c835f3..00000000 --- a/src/app/write/hooks/usePreventClose.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { useBooleanState } from '@/hooks'; -import { useCallback, useEffect } from 'react'; - -const usePreventClose = () => { - const [bool, setTrue, setFalse] = useBooleanState(); - const preventClose = useCallback( - (e: BeforeUnloadEvent) => { - e.preventDefault(); - e.returnValue = ''; - setTrue(); - }, - [setTrue], - ); - - useEffect(() => { - (() => { - window.addEventListener('beforeunload', preventClose); - })(); - return () => { - window.removeEventListener('beforeunload', preventClose); - }; - }, [preventClose]); - - return [bool, setFalse] as const; -}; - -export default usePreventClose; From 462e6bfad5d99560178f77140d61ce239486eb4f Mon Sep 17 00:00:00 2001 From: naro_Kim Date: Tue, 25 Jul 2023 15:43:20 +0900 Subject: [PATCH 17/22] =?UTF-8?q?fix:=20Mobile=20age=20BottomSheet=20style?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/AgeModal/AgeBottomSheet.tsx | 22 +++++++++++-------- .../write/components/AgeModal/AgeModal.css.ts | 7 +++--- .../write/components/AgeModal/BirthYear.tsx | 7 +++--- src/app/write/components/Form/FirstStep.tsx | 4 ++-- src/app/write/components/Form/Form.css.ts | 10 +++++++++ src/app/write/style.css.ts | 2 +- 6 files changed, 33 insertions(+), 19 deletions(-) diff --git a/src/app/write/components/AgeModal/AgeBottomSheet.tsx b/src/app/write/components/AgeModal/AgeBottomSheet.tsx index c39c8731..d7d02c93 100644 --- a/src/app/write/components/AgeModal/AgeBottomSheet.tsx +++ b/src/app/write/components/AgeModal/AgeBottomSheet.tsx @@ -33,15 +33,19 @@ const AgeBottomSheet = ({ ...props }: TControl) => {
-
- - 최소 나이 선택 - - - - 최대 나이 선택 - - +
+
+ + 최소 나이 선택 + + +
+
+ + 최대 나이 선택 + + +
저장하기 diff --git a/src/app/write/components/AgeModal/AgeModal.css.ts b/src/app/write/components/AgeModal/AgeModal.css.ts index 54aa3491..9401ea6d 100644 --- a/src/app/write/components/AgeModal/AgeModal.css.ts +++ b/src/app/write/components/AgeModal/AgeModal.css.ts @@ -30,18 +30,19 @@ export const modalContent = style({ flexDirection: 'row', justifyContent: 'space-evenly', alignItems: 'center', - gap: themeTokens.space.sm, + gap: space.sm, '@media': { [screenMQ.m]: { flexDirection: 'column', justifyContent: 'flex-start', alignItems: 'flex-start', - margin: space.xl, + margin: `${space['4xl']} ${space.xl}`, }, }, }); export const birthText = style({ ...fontVariant.body2, - color: themeTokens.color.sub, + color: color.sub, + margin: `${space.xl}`, }); diff --git a/src/app/write/components/AgeModal/BirthYear.tsx b/src/app/write/components/AgeModal/BirthYear.tsx index df57bf8e..7b58415e 100644 --- a/src/app/write/components/AgeModal/BirthYear.tsx +++ b/src/app/write/components/AgeModal/BirthYear.tsx @@ -1,7 +1,6 @@ import dayjs from 'dayjs'; import { Control, FieldValues, useWatch } from 'react-hook-form'; -import { birthText, modalContent } from './AgeModal.css'; -import clsx from 'clsx'; +import { birthText } from './AgeModal.css'; type ModalProps = { control: Control; @@ -10,13 +9,13 @@ type ModalProps = { const BirthYear = ({ control }: ModalProps) => { const [min, max] = useWatch({ control, name: ['minAge', 'maxAge'] }); if (!min || !max) { - return
-
; + return
-
; } else { const year = dayjs().year(); const minBirthYear = (year - min).toString().slice(2, 4); const maxBirthYear = (year - max).toString().slice(2, 4); return ( -
+
{minBirthYear}년생 - {maxBirthYear}년생
); diff --git a/src/app/write/components/Form/FirstStep.tsx b/src/app/write/components/Form/FirstStep.tsx index 3b5b80e6..bc07881c 100644 --- a/src/app/write/components/Form/FirstStep.tsx +++ b/src/app/write/components/Form/FirstStep.tsx @@ -6,7 +6,7 @@ import { Button, Input, InputSection, Typography } from '@/components'; import { InputDate, Counter, AgeModal, AgeBottomSheet, MapModal, TimeDropDown } from '@/app/write/components'; import { StepOneData } from '@/app/write/types'; import { useWriteForm } from '@/app/write/hooks/useWriteForm'; -import { formWrapper, sectionGap, inputGap, submitButton, flexBetween } from './Form.css'; +import { formWrapper, sectionGap, inputGap, submitButton, flexBetween, ageError } from './Form.css'; import { useIsMobile } from '@/hooks'; type stepProps = { @@ -66,7 +66,7 @@ const FirstStep = ({ nextStep, setData }: stepProps) => { )} - + {stepOneMethod.formState.errors['maxAge']?.message}
diff --git a/src/app/write/components/Form/Form.css.ts b/src/app/write/components/Form/Form.css.ts index a74ddfee..82fea485 100644 --- a/src/app/write/components/Form/Form.css.ts +++ b/src/app/write/components/Form/Form.css.ts @@ -36,3 +36,13 @@ export const flexBetween = style({ flexDirection: 'row', justifyContent: 'space-between', }); + +export const ageError = style({ + margin: '0', + textAlign: 'end', + '@media': { + [screenMQ.m]: { + textAlign: 'start', + }, + }, +}); diff --git a/src/app/write/style.css.ts b/src/app/write/style.css.ts index 1717fe8d..02907acf 100644 --- a/src/app/write/style.css.ts +++ b/src/app/write/style.css.ts @@ -17,7 +17,7 @@ export const wrapper = style({ maxWidth: sizeProp(400), marginTop: '7.25rem', marginBottom: space.xl, - padding: `${space.lg} ${space.xl} 0 ${space.xl}`, + padding: `0 ${space.xl}`, }, }, }); From 9b901e8ccec58462ffea98e1bfb2d1c942e741c4 Mon Sep 17 00:00:00 2001 From: naro_Kim Date: Wed, 26 Jul 2023 01:02:53 +0900 Subject: [PATCH 18/22] =?UTF-8?q?fix:=20DateCaption=20button=20type=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/DayPicker/DateCaption.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/DayPicker/DateCaption.tsx b/src/components/DayPicker/DateCaption.tsx index f10a40b5..8bb26039 100644 --- a/src/components/DayPicker/DateCaption.tsx +++ b/src/components/DayPicker/DateCaption.tsx @@ -15,6 +15,7 @@ const DateFormatCaption = ({ displayMonth }: CaptionProps) => {
{ /> Date: Wed, 26 Jul 2023 02:20:36 +0900 Subject: [PATCH 19/22] =?UTF-8?q?fix:=20patch=20api=20useMutation=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/write/api.ts | 7 ++++++- src/api/write/hooks/index.ts | 2 ++ src/api/write/hooks/usePatchBoard.ts | 16 ++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 src/api/write/hooks/usePatchBoard.ts diff --git a/src/api/write/api.ts b/src/api/write/api.ts index 5ef5bc4b..2a684111 100644 --- a/src/api/write/api.ts +++ b/src/api/write/api.ts @@ -1,5 +1,9 @@ import { request } from '@/utils/ky/request'; import { BoardData, ParsedData, PostResponse } from '@/app/write/types'; +interface Props { + boardId: number; + data: ParsedData; +} class WriteAPI { /** * 유저가 입력한 데이터로 먹팟을 생성합니다. @@ -19,7 +23,8 @@ class WriteAPI { * @param boardId - 수정할 board의 id * @param data - 유저가 입력한 board 데이터 */ - async patchBoard(boardId: number, { ...data }: ParsedData): Promise { + + async patchBoard({ boardId, data }: Props): Promise { return request .patch(`v2/boards/${boardId}`, { json: { diff --git a/src/api/write/hooks/index.ts b/src/api/write/hooks/index.ts index 9548bfc8..be25c216 100644 --- a/src/api/write/hooks/index.ts +++ b/src/api/write/hooks/index.ts @@ -1,3 +1,5 @@ export { default as useGetBoard } from './useGetBoard'; export { default as usePostBoard } from './usePostBoard'; + +export { default as usePatchBoard } from './usePatchBoard'; diff --git a/src/api/write/hooks/usePatchBoard.ts b/src/api/write/hooks/usePatchBoard.ts new file mode 100644 index 00000000..7f8fcc0f --- /dev/null +++ b/src/api/write/hooks/usePatchBoard.ts @@ -0,0 +1,16 @@ +import { useQueryClient } from '@tanstack/react-query'; +import { useMutation } from '@/hooks'; +import { boardKeys } from '@/api/board'; +import { writeAPI } from '@/api/write/api'; + +const usePatchBoard = (boardId: number) => { + const queryClient = useQueryClient(); + + return useMutation(writeAPI.patchBoard, { + onSuccess: () => { + void queryClient.invalidateQueries(boardKeys.detail(boardId)); + }, + }); +}; + +export default usePatchBoard; From 87b7f13ac2cd7473d44c8c5b08fe08cd137a22a5 Mon Sep 17 00:00:00 2001 From: naro_Kim Date: Wed, 26 Jul 2023 18:38:34 +0900 Subject: [PATCH 20/22] =?UTF-8?q?fix:=20map=20data=20type=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/write/components/MapModal/MapModal.tsx | 3 ++- src/app/write/hooks/useSetFormData.ts | 1 + src/app/write/lib/schema.ts | 8 +++++--- src/app/write/store/useFormStore.tsx | 1 + src/app/write/types/index.ts | 1 + 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/app/write/components/MapModal/MapModal.tsx b/src/app/write/components/MapModal/MapModal.tsx index f7a45157..11a95231 100644 --- a/src/app/write/components/MapModal/MapModal.tsx +++ b/src/app/write/components/MapModal/MapModal.tsx @@ -14,7 +14,8 @@ const MapModal = () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const onClick = (data: any) => { if (data) { - method.setValue('locationName', `${data.address_name}`); + method.setValue('locationName', `${data.place_name}` || `${data.address_name}`); + method.setValue('addressName ', `${data.address_name}`); if (data.x && data.y) { method.setValue('x', parseFloat(data.x)); method.setValue('y', parseFloat(data.y)); diff --git a/src/app/write/hooks/useSetFormData.ts b/src/app/write/hooks/useSetFormData.ts index 5902c7ec..0bc007f6 100644 --- a/src/app/write/hooks/useSetFormData.ts +++ b/src/app/write/hooks/useSetFormData.ts @@ -16,6 +16,7 @@ const getParsedData = (board: ParsedData) => { region_1depth_name: board.region_1depth_name, region_2depth_name: board.region_2depth_name, locationName: board.locationName, + addressName: board.addressName, x: board.x, y: board.y, locationDetail: board.locationDetail, diff --git a/src/app/write/lib/schema.ts b/src/app/write/lib/schema.ts index d99e3afb..7a47d127 100644 --- a/src/app/write/lib/schema.ts +++ b/src/app/write/lib/schema.ts @@ -13,11 +13,13 @@ export const stepOneSchema = z maxApply: z.coerce.number().min(2, { message: '최소 인원은 2명 이상 가능합니다.' }).default(2), minAge: z.coerce.number().min(20).max(100).optional().nullable().default(null), maxAge: z.coerce.number().min(20).max(100).optional().nullable().default(null), - locationName: z - .string({ invalid_type_error: '필수 항목을 입력해주세요.', required_error: '필수 항목을 입력해주세요.' }) - .min(5, { message: '필수 항목을 입력해주세요.' }), + locationName: z.string({ + invalid_type_error: '필수 항목을 입력해주세요.', + required_error: '필수 항목을 입력해주세요.', + }), x: z.number(), y: z.number(), + addressName: z.coerce.string(), region_1depth_name: z.coerce.string(), region_2depth_name: z.coerce.string(), locationDetail: z.string().optional().nullable().default(null), diff --git a/src/app/write/store/useFormStore.tsx b/src/app/write/store/useFormStore.tsx index 61ac2ed8..5e85c0ad 100644 --- a/src/app/write/store/useFormStore.tsx +++ b/src/app/write/store/useFormStore.tsx @@ -19,6 +19,7 @@ const INIT_STEPONE = { minAge: null, maxAge: null, locationName: '', + addressName: '', x: 0, y: 0, region_1depth_name: '', diff --git a/src/app/write/types/index.ts b/src/app/write/types/index.ts index d1789d6d..59a535bb 100644 --- a/src/app/write/types/index.ts +++ b/src/app/write/types/index.ts @@ -6,6 +6,7 @@ export type StepOneData = { minAge: number | null; maxAge: number | null; locationName: string; + addressName: string; x: number; y: number; region_1depth_name: string; From 7b7ddf268c5d399ac3f3dae7a7582bca878267de Mon Sep 17 00:00:00 2001 From: naro_Kim Date: Wed, 26 Jul 2023 18:50:08 +0900 Subject: [PATCH 21/22] fix: refactor useMutation hook for patch board --- src/app/write/components/Form/BoardForm.tsx | 2 +- src/app/write/components/Form/FirstStep.tsx | 3 +- src/app/write/components/Form/SecondStep.tsx | 29 ++++++++++++++------ src/app/write/page.tsx | 2 +- 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/app/write/components/Form/BoardForm.tsx b/src/app/write/components/Form/BoardForm.tsx index 433d8508..095d48bf 100644 --- a/src/app/write/components/Form/BoardForm.tsx +++ b/src/app/write/components/Form/BoardForm.tsx @@ -15,7 +15,7 @@ export default function BoardForm() { const { data } = useProfile(); const router = useRouter(); - const { id: boardId } = useParams(); + const { id: boardId } = useParams() as { id: string }; useSetFormData(boardId); const { reset, setData } = useFormStore(); const [step, { prevStep, nextStep }] = useFunnel(['1', '2']); diff --git a/src/app/write/components/Form/FirstStep.tsx b/src/app/write/components/Form/FirstStep.tsx index bc07881c..43d3cd22 100644 --- a/src/app/write/components/Form/FirstStep.tsx +++ b/src/app/write/components/Form/FirstStep.tsx @@ -17,7 +17,6 @@ type stepProps = { const FirstStep = ({ nextStep, setData }: stepProps) => { const { stepOneMethod } = useWriteForm(); const mobile = useIsMobile(); - const onSubmit = useCallback( (data: StepOneData) => { if (!data) { @@ -84,7 +83,7 @@ const FirstStep = ({ nextStep, setData }: stepProps) => { {...stepOneMethod.register('locationDetail', { required: false })} name={'locationDetail'} placeholder="ex) 1층 로비, 식당 입구" - maxLength={100} + maxLength={30} >
diff --git a/src/app/write/components/Form/SecondStep.tsx b/src/app/write/components/Form/SecondStep.tsx index 82c260bc..8ffff9dd 100644 --- a/src/app/write/components/Form/SecondStep.tsx +++ b/src/app/write/components/Form/SecondStep.tsx @@ -5,20 +5,20 @@ import { BoardData, PostResponse } from '@/app/write/types'; import { Button, Input, InputSection, TextArea, Toast, Typography } from '@/components'; import { formWrapper, inputGap } from './Form.css'; import parseData from './util/parseData'; -import useWriteBoard from '@/api/write/hooks/usePostBoard'; import { useRouter } from 'next/navigation'; import { useWriteForm } from '@/app/write/hooks/useWriteForm'; import { useOverlay } from '@/hooks'; -import { writeAPI } from '@/api/write'; +import { usePostBoard, usePatchBoard } from '@/api/write'; type StepProps = { reset: () => void; - boardId?: number; + boardId: number; isPatch?: boolean; }; const SecondStep = ({ reset, boardId, isPatch = false }: StepProps) => { - const { mutate: board } = useWriteBoard(); + const { mutate: board } = usePostBoard(); + const { mutate: patch } = usePatchBoard(boardId); const { stepTwoMethod } = useWriteForm(); const [openToast, closeToast] = useOverlay(); const router = useRouter(); @@ -46,11 +46,22 @@ const SecondStep = ({ reset, boardId, isPatch = false }: StepProps) => { ); } if (isPatch && boardId) { - writeAPI.patchBoard(boardId, { ...parseData(data) }).then(() => { - openToast(); - router.push(`/board/${boardId}`); - reset(); - }); + patch( + { boardId: boardId, data: { ...parseData(data) } }, + { + onSuccess: () => { + openToast(); + router.push(`/board/${boardId}`); + reset(); + }, + onError: (error) => { + openToast(); + if (error.response.status === 403) { + router.push('/login'); + } + }, + }, + ); } }; diff --git a/src/app/write/page.tsx b/src/app/write/page.tsx index 243497e2..8fe8b344 100644 --- a/src/app/write/page.tsx +++ b/src/app/write/page.tsx @@ -36,7 +36,7 @@ export default function Write() {
{step === '1' && } - {step === '2' && } + {step === '2' && }
); } From 4e8bce99f8b067d159592bd42291bcbb3ecf724e Mon Sep 17 00:00:00 2001 From: Yangseungchan Date: Wed, 26 Jul 2023 19:19:56 +0900 Subject: [PATCH 22/22] =?UTF-8?q?chore:=20test=20action=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 466863e4..74373f4c 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -18,7 +18,7 @@ jobs: - name: nodeJS uses: actions/setup-node@v3 with: - node-version: 18.x + node-version: 18.16 cache: yarn - name: dependency install