From 75f63ae28e013724687fa94ceaea9681f7250b57 Mon Sep 17 00:00:00 2001 From: naro_Kim Date: Mon, 10 Jul 2023 14:54:35 +0900 Subject: [PATCH 01/22] =?UTF-8?q?edit:=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 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/write/components/MapModal/MapModal.tsx b/src/app/write/components/MapModal/MapModal.tsx index b6f8b621..cb34bf62 100644 --- a/src/app/write/components/MapModal/MapModal.tsx +++ b/src/app/write/components/MapModal/MapModal.tsx @@ -12,10 +12,12 @@ const MapModal = () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const onClick = (data: any) => { if (data) { - method.setValue('locationName', `${data.road_address_name} ${data.place_name}`); + method.setValue('locationName', `${data.address_name} ${data.place_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)); } } closeModal(); From b9aaec5688b75be918270497a88d11bb19ca554a Mon Sep 17 00:00:00 2001 From: naro_Kim Date: Mon, 10 Jul 2023 19:59:15 +0900 Subject: [PATCH 02/22] =?UTF-8?q?edit:=20Date=20parsing=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/Form/util/parseData.ts | 17 +++++++++++++++++ src/app/write/hooks/usePostBoard.ts | 4 ++-- src/app/write/lib/schema.ts | 15 +++++++++++---- src/app/write/types/index.ts | 7 +++++-- 4 files changed, 35 insertions(+), 8 deletions(-) create mode 100644 src/app/write/components/Form/util/parseData.ts diff --git a/src/app/write/components/Form/util/parseData.ts b/src/app/write/components/Form/util/parseData.ts new file mode 100644 index 00000000..79f81a8d --- /dev/null +++ b/src/app/write/components/Form/util/parseData.ts @@ -0,0 +1,17 @@ +import dayjs from 'dayjs'; +import { ParsedData } from '@/app/write/types'; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const parseData = (data: ParsedData) => { + const date = dayjs(data.meetingDate).format('YYYY-MM-DD'); + const time = data.meetingTime.substr(-5); + data = { + ...data, + meetingDate: date, + meetingTime: time, + }; + console.log(data); + + return data; +}; + +export default parseData; diff --git a/src/app/write/hooks/usePostBoard.ts b/src/app/write/hooks/usePostBoard.ts index 7c5ebc0e..078cc70d 100644 --- a/src/app/write/hooks/usePostBoard.ts +++ b/src/app/write/hooks/usePostBoard.ts @@ -1,13 +1,13 @@ import { useState, useCallback } from 'react'; import { request } from '@/utils/ky/request'; import { KyResponse } from 'ky'; -import { BoardData } from '../types'; +import { ParsedData } from '../types'; import dayjs from 'dayjs'; const usePostBoard = (onSuccess: (response: KyResponse) => void) => { const [error, setError] = useState(null); const handlePostBoard = useCallback( - async (data: BoardData) => { + async (data: ParsedData) => { try { if (data.meetingDate == 'Invalid Date') { data.meetingDate = dayjs().format('YYYY-MM-DD'); diff --git a/src/app/write/lib/schema.ts b/src/app/write/lib/schema.ts index 94d87b8c..a1a0719c 100644 --- a/src/app/write/lib/schema.ts +++ b/src/app/write/lib/schema.ts @@ -1,10 +1,15 @@ import * as z from 'zod'; export const stepOneSchema = z.object({ - meetingDate: z.coerce.string({ - invalid_type_error: '필수 항목을 입력해주세요.', - required_error: '필수 항목을 입력해주세요.', - }), + meetingDate: z + .date({ + invalid_type_error: '필수 항목을 입력해주세요.', + 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'), @@ -16,6 +21,8 @@ export const stepOneSchema = z.object({ .min(5, { message: '필수 항목을 입력해주세요.' }), x: z.number(), y: z.number(), + 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/types/index.ts b/src/app/write/types/index.ts index dac53184..05175a31 100644 --- a/src/app/write/types/index.ts +++ b/src/app/write/types/index.ts @@ -1,5 +1,6 @@ export type StepOneData = { - meetingDate: string; + meetingDate: Date; + timezone: string; meetingTime: string; maxApply: number; minAge: number | null; @@ -7,6 +8,8 @@ export type StepOneData = { locationName: string; x: number; y: number; + region_1depth_name: string; + region_2depth_name: string; locationDetail: string | null; }; @@ -16,4 +19,4 @@ export type StepTwoData = { chatLink: string; }; -export type BoardData = StepOneData & StepTwoData; +export type ParsedData = Omit & StepTwoData & { meetingDate: string | Date }; From db47e65feee4bb8eb891e5fb547eab0ac4b18b62 Mon Sep 17 00:00:00 2001 From: naro_Kim Date: Mon, 10 Jul 2023 20:00:34 +0900 Subject: [PATCH 03/22] =?UTF-8?q?edit:=20=EB=82=98=EC=9D=B4=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=83=81=EC=88=98=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/write/components/AgeModal/AgeController.tsx | 4 ++-- src/app/write/constants/index.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/write/components/AgeModal/AgeController.tsx b/src/app/write/components/AgeModal/AgeController.tsx index d86b7fa4..7a003302 100644 --- a/src/app/write/components/AgeModal/AgeController.tsx +++ b/src/app/write/components/AgeModal/AgeController.tsx @@ -1,6 +1,6 @@ import { Dropdown, DropdownButton, DropdownItem, DropdownMenu } from '@/components'; import { Control, Controller, FieldValues } from 'react-hook-form'; -import { agelist as list } from '@/app/write/constants'; +import { AGE_LIST } from '@/app/write/constants'; type ControllerProps = { control: Control; @@ -19,7 +19,7 @@ const AgeController = ({ control, name, placeholder }: ControllerProps) => {value && `${value}세`} - {list.map((v) => ( + {AGE_LIST.map((v) => ( {v} diff --git a/src/app/write/constants/index.ts b/src/app/write/constants/index.ts index 46914165..dce6d655 100644 --- a/src/app/write/constants/index.ts +++ b/src/app/write/constants/index.ts @@ -1,4 +1,4 @@ -export const agelist = [ +export const AGE_LIST = [ '20', '21', '22', From 0971c4b3efa80bb27c2e69273e85f46c305aa451 Mon Sep 17 00:00:00 2001 From: naro_Kim Date: Thu, 13 Jul 2023 21:23:39 +0900 Subject: [PATCH 04/22] =?UTF-8?q?feat:=20mobile=20=EB=8C=80=EC=9D=91=20Inp?= =?UTF-8?q?utDropdown=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=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/components/Input/InputDropdown.tsx | 52 ++++++++++++++++---------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/src/components/Input/InputDropdown.tsx b/src/components/Input/InputDropdown.tsx index eaa63a40..56f9a853 100644 --- a/src/components/Input/InputDropdown.tsx +++ b/src/components/Input/InputDropdown.tsx @@ -1,28 +1,27 @@ 'use client'; - import { FieldValues, FieldPath, Controller, Control } from 'react-hook-form'; -import { Dropdown, DropdownButton, DropdownMenu, DropdownItem } from '../Dropdown'; +import { Dropdown } from '@/components'; import InputErrorMessage from './InputErrorMessage'; import { inputWrapper } from './Input.css'; import { HTMLAttributes } from 'react'; - -/** - * @property {string} name - Input을 구분짓는 고유한 이름입니다. 한 폼안에는 오직 하나의 이름만이 존재해야 합니다. name을 전달해야 form validation이 가능합니다. - * @property {string} placeholder: Input창에 미리 보여줄 텍스트를 설정합니다. - * @property {string[]} selections: Dropdown 아이템의 리스트입니다. - * @property {boolean} showError - 유효성검사에서 해당 Input이 invalid할 경우 error message의 표시 여부를 설정합니다. 기본값은 true입니다. - */ +import { useIsMobile } from '@/hooks'; type TControl = { control: Control; + /** Input을 구분짓는 고유한 이름입니다. 한 폼안에는 오직 하나의 이름만이 존재해야 합니다. name을 전달해야 form validation이 가능합니다. */ name: FieldPath; + /** 유효성검사에서 해당 Input이 invalid할 경우 error message의 표시 여부를 설정합니다. @default true*/ showError?: boolean; + /** Dropdown 아이템의 리스트입니다. */ selections?: string[]; + /** 모바일 BottomSheet DropDown에 해당하는 타이틀입니다. */ + title?: string; } & HTMLAttributes; // eslint-disable-next-line @typescript-eslint/no-explicit-any const InputDropdown = ({ ...props }: TControl) => { - const { name, control, placeholder, showError = true, selections = ['default'] } = props; + const { name, control, placeholder, showError = true, selections = ['default'], title } = props; + const mobile = useIsMobile(); return (
@@ -32,16 +31,31 @@ const InputDropdown = ({ ...props }: TControl) => { name={name} render={({ field: { value, onChange }, fieldState }) => ( - + {value} - - - {selections.map((v) => ( - - {v} - - ))} - + + {mobile ? ( + + {selections.map((v) => ( + + {v} + + ))} + + ) : ( + + {selections.map((v) => ( + + {v} + + ))} + + )} )} /> From 1135c4366b60b0afa7dab22cf232ec3939a20ca8 Mon Sep 17 00:00:00 2001 From: naro_Kim Date: Thu, 13 Jul 2023 21:25:24 +0900 Subject: [PATCH 05/22] =?UTF-8?q?feat:=20WriteTitle=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/WriteTitle/WriteTitle.css.ts | 16 ++++++++++++++ .../components/WriteTitle/WriteTitle.tsx | 21 +++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 src/app/write/components/WriteTitle/WriteTitle.css.ts create mode 100644 src/app/write/components/WriteTitle/WriteTitle.tsx diff --git a/src/app/write/components/WriteTitle/WriteTitle.css.ts b/src/app/write/components/WriteTitle/WriteTitle.css.ts new file mode 100644 index 00000000..8e15e3ae --- /dev/null +++ b/src/app/write/components/WriteTitle/WriteTitle.css.ts @@ -0,0 +1,16 @@ +import { screenMQ, themeTokens } from '@/styles/theme.css'; +import { style } from '@vanilla-extract/css'; + +const { space } = themeTokens; + +export const title = style({ + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + gap: space.md, + '@media': { + [screenMQ.m]: { + gap: space.sm, + }, + }, +}); diff --git a/src/app/write/components/WriteTitle/WriteTitle.tsx b/src/app/write/components/WriteTitle/WriteTitle.tsx new file mode 100644 index 00000000..36143d1c --- /dev/null +++ b/src/app/write/components/WriteTitle/WriteTitle.tsx @@ -0,0 +1,21 @@ +import { Typography, IconButton } from '@/components'; +import { useIsMobile } from '@/hooks'; +import { title } from './WriteTitle.css'; + +interface TitleProps { + prevStep: () => void; +} + +const WriteTitle = ({ prevStep }: TitleProps) => { + const mobile = useIsMobile(); + return ( +
+ + + 우리 회사 먹팟 만들기 + +
+ ); +}; + +export default WriteTitle; From 9aa154ea5cbe5fd64994fe880ed46a3d763655a2 Mon Sep 17 00:00:00 2001 From: naro_Kim Date: Thu, 13 Jul 2023 21:27:02 +0900 Subject: [PATCH 06/22] =?UTF-8?q?feat:=20goback=20IconButton=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 --- public/sprite.svg | 24 ++++++--------------- src/app/write/page.tsx | 17 +++++++++------ src/components/IconButton/types/IconType.ts | 3 ++- 3 files changed, 18 insertions(+), 26 deletions(-) diff --git a/public/sprite.svg b/public/sprite.svg index 3af5a39e..90a77318 100644 --- a/public/sprite.svg +++ b/public/sprite.svg @@ -27,17 +27,6 @@ - - - - - - - - - - - @@ -154,15 +143,14 @@ - - - - - - + + + + + + - diff --git a/src/app/write/page.tsx b/src/app/write/page.tsx index 5e1a707c..d542377e 100644 --- a/src/app/write/page.tsx +++ b/src/app/write/page.tsx @@ -1,14 +1,19 @@ 'use client'; import FirstStep from './components/Form/FirstStep'; import SecondStep from './components/Form/SecondStep'; -import { Typography } from '@/components'; import { wrapper } from './style.css'; import { useEffect } from 'react'; -import { useIsMobile, useFunnel } from '@/hooks'; +import { useFunnel } from '@/hooks'; +import { useProfile } from '@/api/hooks'; +import WriteTitle from './components/WriteTitle/WriteTitle'; export default function Write() { - const [step, { nextStep }] = useFunnel(['1', '2']); - const mobile = useIsMobile(); + const [step, { prevStep, nextStep }] = useFunnel(['1', '2']); + const { data: profile } = useProfile(); + const isLogin = Boolean(profile); + if (!isLogin) { + // window.history.back(); + } const preventClose = (e: BeforeUnloadEvent) => { e.preventDefault(); @@ -26,9 +31,7 @@ export default function Write() { return (
- - 우리 회사 먹팟 만들기 - + {step === '1' && } {step === '2' && }
diff --git a/src/components/IconButton/types/IconType.ts b/src/components/IconButton/types/IconType.ts index b43560a7..26e0eb56 100644 --- a/src/components/IconButton/types/IconType.ts +++ b/src/components/IconButton/types/IconType.ts @@ -19,4 +19,5 @@ export type IconType = | 'crown' | 'marker' | 'info' - | 'map'; + | 'map' + | 'goback'; From 2e7bef7494f9456c5711e1fa6896c1eeea0ce33f Mon Sep 17 00:00:00 2001 From: naro_Kim Date: Thu, 13 Jul 2023 21:27:50 +0900 Subject: [PATCH 07/22] =?UTF-8?q?edit:=20BottomSheet=20Css=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/BottomSheet/BottomSheet.css.ts | 3 ++- src/components/BottomSheet/README.md | 2 +- src/components/DayPicker/DayPicker.css.ts | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/BottomSheet/BottomSheet.css.ts b/src/components/BottomSheet/BottomSheet.css.ts index 34b0a558..6c8982c5 100644 --- a/src/components/BottomSheet/BottomSheet.css.ts +++ b/src/components/BottomSheet/BottomSheet.css.ts @@ -3,6 +3,7 @@ import { style, globalStyle } from '@vanilla-extract/css'; import { fontVariant } from '@/styles/variant.css'; import { recipe } from '@vanilla-extract/recipes'; import { sizeProp } from '@/utils/sizeProp'; +import { calc } from '@vanilla-extract/css-utils'; const { color, zIndices } = themeTokens; export const titleWrapper = style({ @@ -54,7 +55,7 @@ export const wrap = recipe({ borderRadius: '14px 14px 0px 0px', flexDirection: 'column', justifyContent: 'flex-start', - minHeight: sizeProp('632px'), + height: calc.subtract('100vh', sizeProp('180px')), }, variants: { open: { diff --git a/src/components/BottomSheet/README.md b/src/components/BottomSheet/README.md index abff07ca..fb7f0683 100644 --- a/src/components/BottomSheet/README.md +++ b/src/components/BottomSheet/README.md @@ -9,7 +9,7 @@ const Test = () => { const renderBottomSheet = () => { return ( - +
원하는 내용 적기
); diff --git a/src/components/DayPicker/DayPicker.css.ts b/src/components/DayPicker/DayPicker.css.ts index 65f5eb36..597efb7f 100644 --- a/src/components/DayPicker/DayPicker.css.ts +++ b/src/components/DayPicker/DayPicker.css.ts @@ -17,6 +17,8 @@ export const captionButtons = style({ }); export const calendar = style({ + display: 'flex', + justifyContent: 'center', vars: { '--rdp-background-color': 'rgba(28, 26, 26, 0.04)', }, From 9425483a1769f0afee9a9c02f9ba430a794844d5 Mon Sep 17 00:00:00 2001 From: naro_Kim Date: Thu, 13 Jul 2023 21:38:41 +0900 Subject: [PATCH 08/22] =?UTF-8?q?feat:=20mobile=20=EB=82=98=EC=9D=B4?= =?UTF-8?q?=EC=A0=9C=ED=95=9C=20=EC=84=A4=EC=A0=95=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/AgeModal/AgeBottomSheet.tsx | 64 +++++++++++++++++++ .../components/AgeModal/AgeClearButton.tsx | 15 +++++ .../components/AgeModal/AgeController.tsx | 7 +- .../write/components/AgeModal/AgeModal.css.ts | 20 ++++-- .../write/components/AgeModal/AgeModal.tsx | 53 ++++----------- .../write/components/AgeModal/BirthYear.tsx | 26 ++++++++ .../write/components/AgeModal/getAgeList.ts | 10 --- .../write/components/AgeModal/useBirthYear.ts | 14 ---- 8 files changed, 139 insertions(+), 70 deletions(-) create mode 100644 src/app/write/components/AgeModal/AgeBottomSheet.tsx create mode 100644 src/app/write/components/AgeModal/AgeClearButton.tsx create mode 100644 src/app/write/components/AgeModal/BirthYear.tsx delete mode 100644 src/app/write/components/AgeModal/getAgeList.ts delete mode 100644 src/app/write/components/AgeModal/useBirthYear.ts diff --git a/src/app/write/components/AgeModal/AgeBottomSheet.tsx b/src/app/write/components/AgeModal/AgeBottomSheet.tsx new file mode 100644 index 00000000..c39c8731 --- /dev/null +++ b/src/app/write/components/AgeModal/AgeBottomSheet.tsx @@ -0,0 +1,64 @@ +'use client'; + +import { HTMLAttributes, useCallback } from 'react'; +import { FieldValues, Control } from 'react-hook-form'; +import { BottomSheet, BottomButton, Typography } from '@/components'; +import { useOverlay, useBooleanState } from '@/hooks'; +import BirthYear from './BirthYear'; +import AgeController from './AgeController'; +import AgeClearButton from './AgeClearButton'; +import { buttonWrapper, modalContent, openButton } from './AgeModal.css'; +import { MAX_AGE, MIN_AGE } from '../../constants'; + +type TControl = { + /** Input을 제어하는 컨트롤입니다. */ + control: Control; + /** 유효성검사에서 해당 Input이 invalid할 경우 error message의 표시 여부를 설정합니다. @default true*/ + showError?: boolean; +} & HTMLAttributes; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const AgeBottomSheet = ({ ...props }: TControl) => { + const [save, setSave] = useBooleanState(false); + const [openBottomSheet, closeBottomSheet] = useOverlay(); + const { control } = props; + + const handleSave = useCallback(() => { + setSave(); + closeBottomSheet(); + }, [setSave, closeBottomSheet]); + + const renderBottomSheet = () => { + return ( + +
+ +
+ + 최소 나이 선택 + + + + 최대 나이 선택 + + +
+ + 저장하기 + +
+
+ ); + }; + + return ( +
+ + {save && control.getFieldState(MAX_AGE).isDirty && } +
+ ); +}; + +export default AgeBottomSheet; diff --git a/src/app/write/components/AgeModal/AgeClearButton.tsx b/src/app/write/components/AgeModal/AgeClearButton.tsx new file mode 100644 index 00000000..058a2606 --- /dev/null +++ b/src/app/write/components/AgeModal/AgeClearButton.tsx @@ -0,0 +1,15 @@ +import { IconButton } from '@/components'; +import { useCallback } from 'react'; +import { useFormContext } from 'react-hook-form'; +import { MAX_AGE, MIN_AGE } from '../../constants'; + +const AgeClearButton = () => { + const { resetField } = useFormContext(); + const resetValues = useCallback(() => { + resetField(MIN_AGE); + resetField(MAX_AGE); + }, [resetField]); + return ; +}; + +export default AgeClearButton; diff --git a/src/app/write/components/AgeModal/AgeController.tsx b/src/app/write/components/AgeModal/AgeController.tsx index 7a003302..5aaa78df 100644 --- a/src/app/write/components/AgeModal/AgeController.tsx +++ b/src/app/write/components/AgeModal/AgeController.tsx @@ -6,10 +6,11 @@ type ControllerProps = { control: Control; name: string; placeholder: string; + disabled?: boolean; }; // eslint-disable-next-line @typescript-eslint/no-explicit-any -const AgeController = ({ control, name, placeholder }: ControllerProps) => { +const AgeController = ({ disabled = false, control, name, placeholder }: ControllerProps) => { return ( ) => name={name} render={({ field: { value, onChange } }) => ( - {value && `${value}세`} + + {value && `${value}세`} + {AGE_LIST.map((v) => ( diff --git a/src/app/write/components/AgeModal/AgeModal.css.ts b/src/app/write/components/AgeModal/AgeModal.css.ts index ffde1eb4..9dbb96ba 100644 --- a/src/app/write/components/AgeModal/AgeModal.css.ts +++ b/src/app/write/components/AgeModal/AgeModal.css.ts @@ -1,13 +1,15 @@ import { style } from '@vanilla-extract/css'; import { fontVariant } from '@/styles/variant.css'; -import { themeTokens } from '@/styles/theme.css'; +import { screenMQ, themeTokens } from '@/styles/theme.css'; + +const { space, color } = themeTokens; export const openButton = style({ ...fontVariant.label3, - padding: `${themeTokens.space.md} ${themeTokens.space.xl}`, - gap: themeTokens.space.sm, - background: themeTokens.color.grey100, - borderRadius: themeTokens.space.md, + padding: `${space.md} ${space.xl}`, + gap: space.sm, + background: color.grey100, + borderRadius: space.md, }); export const buttonWrapper = style({ @@ -28,6 +30,14 @@ export const modalContent = style({ justifyContent: 'space-evenly', alignItems: 'center', gap: themeTokens.space.sm, + '@media': { + [screenMQ.m]: { + flexDirection: 'column', + justifyContent: 'flex-start', + alignItems: 'flex-start', + margin: space.xl, + }, + }, }); export const birthText = style({ diff --git a/src/app/write/components/AgeModal/AgeModal.tsx b/src/app/write/components/AgeModal/AgeModal.tsx index d921561a..bfd0a1bc 100644 --- a/src/app/write/components/AgeModal/AgeModal.tsx +++ b/src/app/write/components/AgeModal/AgeModal.tsx @@ -1,46 +1,22 @@ -import { Control, FieldValues, useFormContext, useWatch } from 'react-hook-form'; +import { Control, FieldValues, useFormContext } from 'react-hook-form'; import { HTMLAttributes, useCallback } from 'react'; -import dayjs from 'dayjs'; -import clsx from 'clsx'; -import { Button, IconButton, Modal, ModalContent, ModalFooter, ModalHeader, SvgIcon } from '@/components'; +import { Button, Modal, ModalContent, ModalFooter, ModalHeader, SvgIcon } from '@/components'; import { useBooleanState, useOverlay } from '@/hooks'; import { openButton, modalContent, modalContentWrapper, - birthText, buttonWrapper, } from '@/app/write/components/AgeModal/AgeModal.css'; import AgeController from './AgeController'; +import BirthYear from './BirthYear'; +import AgeClearButton from './AgeClearButton'; +import { MAX_AGE, MIN_AGE } from '../../constants'; type ModalProps = { control: Control; } & HTMLAttributes; -const BirthYear = ({ control }: ModalProps) => { - const results = useWatch({ control, name: ['minAge', 'maxAge'] }); - const year = dayjs().year(); - const min = parseInt(results[0]) || 20; - const max = parseInt(results[1]) || 30; - const minBirthYear = (year - min).toString().slice(2, 4); - const maxBirthYear = (year - max).toString().slice(2, 4); - - return ( -
- {minBirthYear}년생 - {maxBirthYear}년생 -
- ); -}; - -const ClearButton = () => { - const { resetField } = useFormContext(); - const resetValues = useCallback(() => { - resetField('minAge'); - resetField('maxAge'); - }, [resetField]); - return ; -}; - // eslint-disable-next-line @typescript-eslint/no-explicit-any const AgeModal = ({ control }: ModalProps) => { const [openModal, closeModal] = useOverlay(); @@ -58,9 +34,9 @@ const AgeModal = ({ control }: ModalProps) => {
- + - +
@@ -74,15 +50,14 @@ const AgeModal = ({ control }: ModalProps) => { ); }; + return ( - <> -
- - {save && formState.dirtyFields['minAge'] && formState.dirtyFields['maxAge'] && } -
- +
+ + {save && formState.dirtyFields['minAge'] && formState.dirtyFields['maxAge'] && } +
); }; diff --git a/src/app/write/components/AgeModal/BirthYear.tsx b/src/app/write/components/AgeModal/BirthYear.tsx new file mode 100644 index 00000000..df57bf8e --- /dev/null +++ b/src/app/write/components/AgeModal/BirthYear.tsx @@ -0,0 +1,26 @@ +import dayjs from 'dayjs'; +import { Control, FieldValues, useWatch } from 'react-hook-form'; +import { birthText, modalContent } from './AgeModal.css'; +import clsx from 'clsx'; + +type ModalProps = { + control: Control; +}; + +const BirthYear = ({ control }: ModalProps) => { + const [min, max] = useWatch({ control, name: ['minAge', 'maxAge'] }); + if (!min || !max) { + 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}년생 +
+ ); + } +}; + +export default BirthYear; diff --git a/src/app/write/components/AgeModal/getAgeList.ts b/src/app/write/components/AgeModal/getAgeList.ts deleted file mode 100644 index e619a1f1..00000000 --- a/src/app/write/components/AgeModal/getAgeList.ts +++ /dev/null @@ -1,10 +0,0 @@ -const getAgeList = (min: number, max: number) => { - const list: string[] = []; - for (let i = min; i < max; i++) { - list.push(i.toString()); - } - - return list; -}; - -export default getAgeList; diff --git a/src/app/write/components/AgeModal/useBirthYear.ts b/src/app/write/components/AgeModal/useBirthYear.ts deleted file mode 100644 index cbd4b200..00000000 --- a/src/app/write/components/AgeModal/useBirthYear.ts +++ /dev/null @@ -1,14 +0,0 @@ -import dayjs from 'dayjs'; -import { useFormContext } from 'react-hook-form'; - -const useBirthYear = () => { - const { watch } = useFormContext(); - const year = dayjs().year(); - const min = parseInt(watch('minAge')) || 20; - const max = parseInt(watch('maxAge')) || 30; - const minBirthYear = (year - min).toString().slice(2, 4); - const maxBirthYear = (year - max).toString().slice(2, 4); - return { minBirthYear, maxBirthYear }; -}; - -export default useBirthYear; From 223bcfcc6ac52bc3750e84508102bb89dfb84bb1 Mon Sep 17 00:00:00 2001 From: naro_Kim Date: Thu, 13 Jul 2023 21:40:08 +0900 Subject: [PATCH 09/22] =?UTF-8?q?feat:=20schema=20logic=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/lib/schema.ts | 62 +++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 26 deletions(-) diff --git a/src/app/write/lib/schema.ts b/src/app/write/lib/schema.ts index a1a0719c..d9d9810e 100644 --- a/src/app/write/lib/schema.ts +++ b/src/app/write/lib/schema.ts @@ -1,30 +1,40 @@ import * as z from 'zod'; -export const stepOneSchema = z.object({ - meetingDate: z - .date({ - invalid_type_error: '필수 항목을 입력해주세요.', - 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'), - 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: '필수 항목을 입력해주세요.' }), - x: z.number(), - y: z.number(), - region_1depth_name: z.coerce.string(), - region_2depth_name: z.coerce.string(), - locationDetail: z.string().optional().nullable().default(null), -}); +export const stepOneSchema = z + .object({ + meetingDate: z + .date({ + invalid_type_error: '필수 항목을 입력해주세요.', + 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'), + 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: '필수 항목을 입력해주세요.' }), + x: z.number(), + y: z.number(), + region_1depth_name: z.coerce.string(), + region_2depth_name: z.coerce.string(), + locationDetail: z.string().optional().nullable().default(null), + }) + .refine( + (arg) => { + if (arg.minAge && arg.maxAge) { + return arg.minAge <= arg.maxAge; + } + return true; + }, + { message: '최대 나이는 최소 나이 이상이어야 합니다.', path: ['maxAge'] }, + ); export const stepTwoSchema = z.object({ title: z @@ -40,7 +50,7 @@ export const stepTwoSchema = z.object({ .regex(new RegExp('https://open.kakao.com/o/[A-Za-z0-9]+'), { message: '올바른 형태의 url을 입력해 주세요.' }), }); -export const boardSchema = stepOneSchema.merge(stepTwoSchema); +export const boardSchema = stepOneSchema.and(stepTwoSchema); export type BoardSchema = z.infer; export type StepOneSchema = z.infer; From 2da8cbe6ea8c6c88a720eee2430e6e925dd6839c Mon Sep 17 00:00:00 2001 From: naro_Kim Date: Thu, 13 Jul 2023 21:41:32 +0900 Subject: [PATCH 10/22] =?UTF-8?q?feat:=20mobile=20=EB=82=A0=EC=A7=9C=20?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=20=EC=84=A0=ED=83=9D=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/InputDate/InputDate.css.ts | 10 ++ .../write/components/InputDate/InputDate.tsx | 55 ++++++--- .../TimeDropDown/TimeDropDown.css.ts | 11 ++ .../components/TimeDropDown/TimeDropDown.tsx | 33 ++++++ src/app/write/constants/index.ts | 104 ++++++++++++++++++ 5 files changed, 196 insertions(+), 17 deletions(-) create mode 100644 src/app/write/components/InputDate/InputDate.css.ts create mode 100644 src/app/write/components/TimeDropDown/TimeDropDown.css.ts create mode 100644 src/app/write/components/TimeDropDown/TimeDropDown.tsx diff --git a/src/app/write/components/InputDate/InputDate.css.ts b/src/app/write/components/InputDate/InputDate.css.ts new file mode 100644 index 00000000..54a29438 --- /dev/null +++ b/src/app/write/components/InputDate/InputDate.css.ts @@ -0,0 +1,10 @@ +import { screenMQ } from '@/styles/theme.css'; +import { style } from '@vanilla-extract/css'; + +export const preventClick = style({ + '@media': { + [screenMQ.m]: { + pointerEvents: 'none', + }, + }, +}); diff --git a/src/app/write/components/InputDate/InputDate.tsx b/src/app/write/components/InputDate/InputDate.tsx index 7f1d362d..408f3c84 100644 --- a/src/app/write/components/InputDate/InputDate.tsx +++ b/src/app/write/components/InputDate/InputDate.tsx @@ -3,36 +3,57 @@ import dayjs from 'dayjs'; import { HTMLAttributes } from 'react'; import { FieldValues, FieldPath, Controller, Control } from 'react-hook-form'; -import { DateInput } from '@/components'; +import { DateInput, DayPicker, BottomSheet } from '@/components'; import { inputWrapper } from '@/components/Input/Input.css'; - -/** - * @property {string} name - Input을 구분짓는 고유한 이름입니다. 한 폼안에는 오직 하나의 이름만이 존재해야 합니다. name을 전달해야 form validation이 가능합니다. - * @property {string} placeholder: Input창에 미리 보여줄 텍스트를 설정합니다. - * @property {string[]} selections: Dropdown 아이템의 리스트입니다. - * @property {boolean} showError - 유효성검사에서 해당 Input이 invalid할 경우 error message의 표시 여부를 설정합니다. 기본값은 true입니다. - */ +import { useOverlay, useIsMobile } from '@/hooks'; +import { preventClick } from './InputDate.css'; type TControl = { + /** Input을 제어하는 컨트롤입니다. */ control: Control; + /** Input을 구분짓는 고유한 이름입니다. 한 폼 안에는 오직 하나의 이름만이 존재해야 합니다. */ name: FieldPath; + /** 유효성검사에서 해당 Input이 invalid할 경우 error message의 표시 여부를 설정합니다. @default true*/ 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/TimeDropDown/TimeDropDown.css.ts b/src/app/write/components/TimeDropDown/TimeDropDown.css.ts new file mode 100644 index 00000000..cf3341f5 --- /dev/null +++ b/src/app/write/components/TimeDropDown/TimeDropDown.css.ts @@ -0,0 +1,11 @@ +import { style } from '@vanilla-extract/css'; +import { themeTokens } from '@/styles/theme.css'; + +const { space } = themeTokens; + +export const wrapper = style({ + display: 'grid', + gridAutoFlow: 'column', + gridTemplateColumns: '1fr 1fr', + gap: space.md, +}); diff --git a/src/app/write/components/TimeDropDown/TimeDropDown.tsx b/src/app/write/components/TimeDropDown/TimeDropDown.tsx new file mode 100644 index 00000000..a23153e2 --- /dev/null +++ b/src/app/write/components/TimeDropDown/TimeDropDown.tsx @@ -0,0 +1,33 @@ +import { InputDropdown, InputSection } from '@/components'; +import { TIMELIST_AM, TIMELIST_PM, TIME_SELECT } from '../../constants'; +import { useFormContext } from 'react-hook-form'; +import { wrapper } from './TimeDropDown.css'; + +const TimeDropDown = () => { + const { control, getValues } = useFormContext(); + + return ( +
+ +
+ + +
+
+
+ ); +}; + +export default TimeDropDown; diff --git a/src/app/write/constants/index.ts b/src/app/write/constants/index.ts index dce6d655..b164015b 100644 --- a/src/app/write/constants/index.ts +++ b/src/app/write/constants/index.ts @@ -94,3 +94,107 @@ export const LOCATION_DETAIL = 'locationDetail'; export const TITLE = 'title'; export const CONTENT = 'content'; export const CHAT_LINK = 'chatLink'; + +export const TIME_SELECT = ['오전', '오후']; + +export const TIMELIST_PM = [ + '12:00', + '12:15', + '12:30', + '12:45', + '01:00', + '01:15', + '01:30', + '01:45', + '02:00', + '02:15', + '02:30', + '02:45', + '03:00', + '03:15', + '03:30', + '03:45', + '04:00', + '04:15', + '04:30', + '04:45', + '05:00', + '05:15', + '05:30', + '05:45', + '06:00', + '06:15', + '06:30', + '06:45', + '07:00', + '07:15', + '07:30', + '07:45', + '08:00', + '08:15', + '08:30', + '08:45', + '09:00', + '09:15', + '09:30', + '09:45', + '10:00', + '10:15', + '10:30', + '10:45', + '11:00', + '11:15', + '11:30', + '11:45', +]; + +export const TIMELIST_AM = [ + '00:00', + '00:15', + '00:30', + '00:45', + '01:00', + '01:15', + '01:30', + '01:45', + '02:00', + '02:15', + '02:30', + '02:45', + '03:00', + '03:15', + '03:30', + '03:45', + '04:00', + '04:15', + '04:30', + '04:45', + '05:00', + '05:15', + '05:30', + '05:45', + '06:00', + '06:15', + '06:30', + '06:45', + '07:00', + '07:15', + '07:30', + '07:45', + '08:00', + '08:15', + '08:30', + '08:45', + '09:00', + '09:15', + '09:30', + '09:45', + '10:00', + '10:15', + '10:30', + '10:45', + '11:00', + '11:15', + '11:30', + '11:45', +]; From 38dd3985d0f6c675f33a09b703e6e13e5b0bde65 Mon Sep 17 00:00:00 2001 From: naro_Kim Date: Thu, 13 Jul 2023 21:46:41 +0900 Subject: [PATCH 11/22] =?UTF-8?q?feat:=20Data=20parsing=20logic=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/write/components/Form/FirstStep.tsx | 63 +++++++------------ src/app/write/components/Form/SecondStep.tsx | 19 ++---- .../write/components/Form/util/parseData.ts | 14 +++-- .../write/components/InputDate/getTimes.ts | 25 -------- src/app/write/components/index.ts | 1 + 5 files changed, 38 insertions(+), 84 deletions(-) delete mode 100644 src/app/write/components/InputDate/getTimes.ts diff --git a/src/app/write/components/Form/FirstStep.tsx b/src/app/write/components/Form/FirstStep.tsx index b3a8cf63..fa49a096 100644 --- a/src/app/write/components/Form/FirstStep.tsx +++ b/src/app/write/components/Form/FirstStep.tsx @@ -1,16 +1,16 @@ 'use client'; -import dayjs from 'dayjs'; -import { useCallback, useEffect, useState } from 'react'; +import { useCallback } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; -import { Button, Input, InputDropdown, InputSection, Typography } from '@/components'; +import { Button, Input, InputSection, Typography } from '@/components'; import { InputDate, Counter, AgeModal, MapModal } from '@/app/write/components'; import { zodResolver } from '@hookform/resolvers/zod'; -import getTimeList from '@/app/write/components/InputDate/getTimes'; import useFormStore from '@/app/write/store/useFormStore'; import { stepOneSchema, StepOneSchema } from '@/app/write/lib/schema'; import { StepOneData } from '@/app/write/types'; -import { formWrapper, sectionGap, inputGap, submitButton } from './Form.css'; -import { useMediaQuery } from '@/hooks'; +import { formWrapper, sectionGap, inputGap, submitButton, flexBetween } from './Form.css'; +import { useIsMobile } from '@/hooks'; +import TimeDropDown from '../TimeDropDown/TimeDropDown'; +import AgeBottomSheet from '../AgeModal/AgeBottomSheet'; type stepProps = { nextStep: () => void; @@ -18,31 +18,19 @@ type stepProps = { const FirstStep = ({ nextStep }: stepProps) => { const { stepOne, setData } = useFormStore(); - const [isMobile, setMobile] = useState(false); - const mobile = useMediaQuery({ bp: 'm' }); - + const mobile = useIsMobile(); const method = useForm({ - mode: 'onSubmit', resolver: zodResolver(stepOneSchema), + mode: 'onChange', defaultValues: stepOne || {}, }); - - useEffect(() => { - setMobile(mobile); - }, [mobile]); + console.log(method.formState.errors); const onSubmit = useCallback( (data: StepOneData) => { if (!data) { return; } - const date = dayjs(data.meetingDate).format('YYYY-MM-DD'); - const time = data.meetingTime.substr(-5); - data = { - ...data, - meetingDate: date, - meetingTime: time, - }; setData({ step: 1, data }); nextStep(); }, @@ -54,37 +42,28 @@ const FirstStep = ({ nextStep }: stepProps) => {
- + 언제 만날까요?
- - - +
- + 몇 명 모을까요?
- {isMobile ? ( - <> - -
- - -
-
- + {mobile ? ( + +
+ + +
+
) : ( <> @@ -98,7 +77,7 @@ const FirstStep = ({ nextStep }: stepProps) => {
- + 어디서 만날까요?
@@ -115,7 +94,7 @@ const FirstStep = ({ nextStep }: stepProps) => {
-
diff --git a/src/app/write/components/Form/SecondStep.tsx b/src/app/write/components/Form/SecondStep.tsx index 6f498670..82d30c50 100644 --- a/src/app/write/components/Form/SecondStep.tsx +++ b/src/app/write/components/Form/SecondStep.tsx @@ -1,34 +1,27 @@ 'use client'; -import { useRouter } from 'next/navigation'; import { FormProvider, useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import useFormStore from '@/app/write/store/useFormStore'; import { boardSchema, BoardSchema } from '@/app/write/lib/schema'; -import { BoardData } from '@/app/write/types'; +import { ParsedData } from '@/app/write/types'; import { Button, Input, InputSection, TextArea, Typography } from '@/components'; -import usePostBoard from '@/app/write/hooks/usePostBoard'; import { formWrapper, inputGap } from './Form.css'; +import parseData from './util/parseData'; const SecondStep = () => { - const { stepOne, reset } = useFormStore(); - const router = useRouter(); - const { error, handlePostBoard } = usePostBoard(() => { - console.log(error); - }); + const { stepOne, stepTwo, reset } = useFormStore(); const method = useForm({ resolver: zodResolver(boardSchema), - defaultValues: { ...stepOne }, + defaultValues: { ...stepOne, ...stepTwo }, }); - const onSubmit = (data: BoardData) => { + const onSubmit = (data: ParsedData) => { if (!data) { return; } - console.log(data); - handlePostBoard(data); + parseData(data); reset(); - router.push('/'); }; return ( diff --git a/src/app/write/components/Form/util/parseData.ts b/src/app/write/components/Form/util/parseData.ts index 79f81a8d..1d84966b 100644 --- a/src/app/write/components/Form/util/parseData.ts +++ b/src/app/write/components/Form/util/parseData.ts @@ -1,15 +1,21 @@ import dayjs from 'dayjs'; import { ParsedData } from '@/app/write/types'; -// eslint-disable-next-line @typescript-eslint/no-explicit-any + const parseData = (data: ParsedData) => { + if (!data) return; + const date = dayjs(data.meetingDate).format('YYYY-MM-DD'); - const time = data.meetingTime.substr(-5); + + if (data.timezone === '오후' && !data.meetingTime.startsWith('12')) { + const [hours, minutes] = data.meetingTime.split(':'); + data.meetingTime = `${String(parseInt(hours) + 12)}:${minutes}` as string; + } + data = { ...data, meetingDate: date, - meetingTime: time, + meetingTime: data.meetingTime, }; - console.log(data); return data; }; diff --git a/src/app/write/components/InputDate/getTimes.ts b/src/app/write/components/InputDate/getTimes.ts deleted file mode 100644 index a6641f16..00000000 --- a/src/app/write/components/InputDate/getTimes.ts +++ /dev/null @@ -1,25 +0,0 @@ -function getTimeList() { - const timeList: string[] = []; - - // for (let hour = 0; hour <= 12; hour++) { - // for (let minute = 0; minute <= 45; minute += 15) { - // const time = '오전 ' + padTime(hour) + ':' + padTime(minute); - // timeList.push(time); - // } - // } - - for (let hour = 12; hour <= 23; hour++) { - for (let minute = 0; minute <= 45; minute += 15) { - const time = '오후 ' + padTime(hour) + ':' + padTime(minute); - timeList.push(time); - } - } - - return timeList; -} - -function padTime(value: number) { - return value.toString().padStart(2, '0'); -} - -export default getTimeList; diff --git a/src/app/write/components/index.ts b/src/app/write/components/index.ts index 40a67cd5..e3f17574 100644 --- a/src/app/write/components/index.ts +++ b/src/app/write/components/index.ts @@ -4,3 +4,4 @@ export { default as InputDate } from './InputDate/InputDate'; export { default as FirstStep } from './Form/FirstStep'; export { default as SecondStep } from './Form/SecondStep'; export { default as MapModal } from './MapModal/MapModal'; +export { default as WriteTitle } from './WriteTitle/WriteTitle'; From 18b8531292435a49ca054946635172cca181d82a Mon Sep 17 00:00:00 2001 From: naro_Kim Date: Thu, 13 Jul 2023 21:47:21 +0900 Subject: [PATCH 12/22] =?UTF-8?q?style:=20Form=20style=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/write/components/Form/Form.css.ts | 6 ++++++ src/app/write/layout.tsx | 2 +- src/app/write/style.css.ts | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/app/write/components/Form/Form.css.ts b/src/app/write/components/Form/Form.css.ts index 3628545a..a74ddfee 100644 --- a/src/app/write/components/Form/Form.css.ts +++ b/src/app/write/components/Form/Form.css.ts @@ -30,3 +30,9 @@ export const submitButton = style({ }, }, }); + +export const flexBetween = style({ + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', +}); diff --git a/src/app/write/layout.tsx b/src/app/write/layout.tsx index e55a6815..af776df3 100644 --- a/src/app/write/layout.tsx +++ b/src/app/write/layout.tsx @@ -6,7 +6,7 @@ export default function layout({ children }: { children: ReactNode }) { return ( <>
- {children} + {children} ); } diff --git a/src/app/write/style.css.ts b/src/app/write/style.css.ts index 59915a8a..1717fe8d 100644 --- a/src/app/write/style.css.ts +++ b/src/app/write/style.css.ts @@ -1,5 +1,6 @@ import { style } from '@vanilla-extract/css'; import { screenMQ, themeTokens } from '@/styles/theme.css'; +import { sizeProp } from '@/utils/sizeProp'; const { space } = themeTokens; export const wrapper = style({ @@ -13,6 +14,7 @@ export const wrapper = style({ '@media': { [screenMQ.m]: { width: '100%', + maxWidth: sizeProp(400), marginTop: '7.25rem', marginBottom: space.xl, padding: `${space.lg} ${space.xl} 0 ${space.xl}`, From a0c15866baa82831b009d85fc1e59e18eef58e79 Mon Sep 17 00:00:00 2001 From: naro-Kim Date: Fri, 14 Jul 2023 22:22:04 +0900 Subject: [PATCH 13/22] =?UTF-8?q?feat:=20post=20api=20mutation=20query=20?= =?UTF-8?q?=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 | 34 +++++++++++++++++++++++++++++ src/api/write/hooks/usePostBoard.ts | 16 ++++++++++++++ src/app/write/components/index.ts | 2 ++ src/app/write/hooks/usePostBoard.ts | 32 --------------------------- 4 files changed, 52 insertions(+), 32 deletions(-) create mode 100644 src/api/write/api.ts create mode 100644 src/api/write/hooks/usePostBoard.ts delete mode 100644 src/app/write/hooks/usePostBoard.ts diff --git a/src/api/write/api.ts b/src/api/write/api.ts new file mode 100644 index 00000000..71337ebc --- /dev/null +++ b/src/api/write/api.ts @@ -0,0 +1,34 @@ +import { request } from '@/utils/ky/request'; +import { ParsedData } from '@/app/write/types'; +class WriteAPI { + /** + * 유저가 입력한 데이터로 먹팟을 생성합니다. + * @param data - 유저가 입력한 board 데이터 + */ + async postBoard({ ...data }: ParsedData) { + return request + .post('v2/boards', { + json: { + ...data, + }, + }) + .json(); + } + /** + * boardId에 해당하는 먹팟을 수정합니다 + * @param boardId - 수정할 board의 id + * @param data - 유저가 입력한 board 데이터 + */ + async patchBoard(boardId: number, { ...data }: ParsedData) { + return request + .patch(`v2/boards/${boardId}`, { + json: { + ...data, + }, + }) + .json(); + } +} + +// api fetchers instance +export const writeAPI = new WriteAPI(); diff --git a/src/api/write/hooks/usePostBoard.ts b/src/api/write/hooks/usePostBoard.ts new file mode 100644 index 00000000..ad8f92d7 --- /dev/null +++ b/src/api/write/hooks/usePostBoard.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 usePostBoard = () => { + const queryClient = useQueryClient(); + + return useMutation(writeAPI.postBoard, { + onSuccess: () => { + void queryClient.invalidateQueries(boardKeys.list()); + }, + }); +}; + +export default usePostBoard; diff --git a/src/app/write/components/index.ts b/src/app/write/components/index.ts index e3f17574..39db6aff 100644 --- a/src/app/write/components/index.ts +++ b/src/app/write/components/index.ts @@ -1,7 +1,9 @@ export { default as AgeModal } from './AgeModal/AgeModal'; +export { default as AgeBottomSheet } from './AgeModal/AgeBottomSheet'; export { default as Counter } from './Counter/Counter'; export { default as InputDate } from './InputDate/InputDate'; export { default as FirstStep } from './Form/FirstStep'; 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'; diff --git a/src/app/write/hooks/usePostBoard.ts b/src/app/write/hooks/usePostBoard.ts deleted file mode 100644 index 078cc70d..00000000 --- a/src/app/write/hooks/usePostBoard.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { useState, useCallback } from 'react'; -import { request } from '@/utils/ky/request'; -import { KyResponse } from 'ky'; -import { ParsedData } from '../types'; -import dayjs from 'dayjs'; - -const usePostBoard = (onSuccess: (response: KyResponse) => void) => { - const [error, setError] = useState(null); - const handlePostBoard = useCallback( - async (data: ParsedData) => { - try { - if (data.meetingDate == 'Invalid Date') { - data.meetingDate = dayjs().format('YYYY-MM-DD'); - } - const response = await request.post('v1/boards', { - json: { ...data }, - }); - onSuccess(response); - } catch (error) { - setError(error instanceof Error ? error : new Error('알 수 없는 에러입니다.')); - } - }, - [onSuccess], - ); - - return { - error, - handlePostBoard, - }; -}; - -export default usePostBoard; From e44d3caf8076822edcdb5ea31647583ab4564129 Mon Sep 17 00:00:00 2001 From: naro-Kim Date: Fri, 14 Jul 2023 22:30:26 +0900 Subject: [PATCH 14/22] =?UTF-8?q?chore:=20step=EB=B3=84=20post=20board=20A?= =?UTF-8?q?PI=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/write/components/Form/FirstStep.tsx | 41 ++++++------- src/app/write/components/Form/SecondStep.tsx | 60 ++++++++++++------- .../write/components/Form/util/parseData.ts | 6 +- src/app/write/hooks/useWriteForm.ts | 21 +++++++ src/app/write/types/index.ts | 7 ++- 5 files changed, 89 insertions(+), 46 deletions(-) create mode 100644 src/app/write/hooks/useWriteForm.ts diff --git a/src/app/write/components/Form/FirstStep.tsx b/src/app/write/components/Form/FirstStep.tsx index fa49a096..ae315506 100644 --- a/src/app/write/components/Form/FirstStep.tsx +++ b/src/app/write/components/Form/FirstStep.tsx @@ -1,30 +1,22 @@ 'use client'; import { useCallback } from 'react'; -import { FormProvider, useForm } from 'react-hook-form'; +import { FormProvider } from 'react-hook-form'; import { Button, Input, InputSection, Typography } from '@/components'; -import { InputDate, Counter, AgeModal, MapModal } from '@/app/write/components'; -import { zodResolver } from '@hookform/resolvers/zod'; +import { InputDate, Counter, AgeModal, AgeBottomSheet, MapModal, TimeDropDown } from '@/app/write/components'; import useFormStore from '@/app/write/store/useFormStore'; -import { stepOneSchema, StepOneSchema } from '@/app/write/lib/schema'; import { StepOneData } from '@/app/write/types'; +import { useWriteForm } from '@/app/write/hooks/useWriteForm'; import { formWrapper, sectionGap, inputGap, submitButton, flexBetween } from './Form.css'; import { useIsMobile } from '@/hooks'; -import TimeDropDown from '../TimeDropDown/TimeDropDown'; -import AgeBottomSheet from '../AgeModal/AgeBottomSheet'; type stepProps = { nextStep: () => void; }; const FirstStep = ({ nextStep }: stepProps) => { - const { stepOne, setData } = useFormStore(); + const { stepOneMethod } = useWriteForm(); + const { setData } = useFormStore(); const mobile = useIsMobile(); - const method = useForm({ - resolver: zodResolver(stepOneSchema), - mode: 'onChange', - defaultValues: stepOne || {}, - }); - console.log(method.formState.errors); const onSubmit = useCallback( (data: StepOneData) => { @@ -39,15 +31,15 @@ const FirstStep = ({ nextStep }: stepProps) => { return ( <> - -
+ +
언제 만날까요?
- +
@@ -60,17 +52,17 @@ const FirstStep = ({ nextStep }: stepProps) => { {mobile ? (
- - + +
) : ( <> - + - + )} @@ -86,7 +78,7 @@ const FirstStep = ({ nextStep }: stepProps) => { {
- diff --git a/src/app/write/components/Form/SecondStep.tsx b/src/app/write/components/Form/SecondStep.tsx index 82d30c50..910689d1 100644 --- a/src/app/write/components/Form/SecondStep.tsx +++ b/src/app/write/components/Form/SecondStep.tsx @@ -1,40 +1,60 @@ 'use client'; -import { FormProvider, useForm } from 'react-hook-form'; -import { zodResolver } from '@hookform/resolvers/zod'; +import { FormProvider, SubmitHandler } from 'react-hook-form'; import useFormStore from '@/app/write/store/useFormStore'; -import { boardSchema, BoardSchema } from '@/app/write/lib/schema'; -import { ParsedData } from '@/app/write/types'; -import { Button, Input, InputSection, TextArea, Typography } from '@/components'; +import { BoardData } 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 { useOverlay } from '@/hooks'; const SecondStep = () => { - const { stepOne, stepTwo, reset } = useFormStore(); + const { mutate: board } = useWriteBoard(); + const { stepTwoMethod } = useWriteForm(); + const [openToast, closeToast] = useOverlay(); + const { reset } = useFormStore(); + const router = useRouter(); - const method = useForm({ - resolver: zodResolver(boardSchema), - defaultValues: { ...stepOne, ...stepTwo }, - }); - - const onSubmit = (data: ParsedData) => { + const onSubmit: SubmitHandler = async (data: BoardData) => { if (!data) { return; } - parseData(data); - reset(); + board( + { ...parseData(data) }, + { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + onSuccess: (response: any) => { + openToast(); + router.push(`/board/${response.boardId}`); + reset(); + }, + onError: (error) => { + openToast(); + if (error.response.status === 403) { + router.push('/login'); + } + }, + }, + ); }; return ( <> - -
+ +
- - + +
{ 베타서비스에서는 채팅 기능이 제공되지 않습니다.
효율적인 소통을 위해 오픈 채팅방을 만들어주세요.
- diff --git a/src/app/write/components/Form/util/parseData.ts b/src/app/write/components/Form/util/parseData.ts index 1d84966b..dec054d8 100644 --- a/src/app/write/components/Form/util/parseData.ts +++ b/src/app/write/components/Form/util/parseData.ts @@ -1,8 +1,8 @@ import dayjs from 'dayjs'; -import { ParsedData } from '@/app/write/types'; +import { BoardData } from '@/app/write/types'; -const parseData = (data: ParsedData) => { - if (!data) return; +const parseData = (data: BoardData) => { + if (!data) return {} as BoardData; const date = dayjs(data.meetingDate).format('YYYY-MM-DD'); diff --git a/src/app/write/hooks/useWriteForm.ts b/src/app/write/hooks/useWriteForm.ts new file mode 100644 index 00000000..e462f816 --- /dev/null +++ b/src/app/write/hooks/useWriteForm.ts @@ -0,0 +1,21 @@ +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import useFormStore from '@/app/write/store/useFormStore'; +import { boardSchema, BoardSchema, StepOneSchema, stepOneSchema } from '@/app/write/lib/schema'; + +export const useWriteForm = () => { + const { stepOne, stepTwo } = useFormStore(); + + const stepOneMethod = useForm({ + resolver: zodResolver(stepOneSchema), + mode: 'onChange', + defaultValues: stepOne || {}, + }); + + const stepTwoMethod = useForm({ + resolver: zodResolver(boardSchema), + defaultValues: { ...stepOne, ...stepTwo }, + }); + + return { stepOneMethod, stepTwoMethod }; +}; diff --git a/src/app/write/types/index.ts b/src/app/write/types/index.ts index 05175a31..d1789d6d 100644 --- a/src/app/write/types/index.ts +++ b/src/app/write/types/index.ts @@ -19,4 +19,9 @@ export type StepTwoData = { chatLink: string; }; -export type ParsedData = Omit & StepTwoData & { meetingDate: string | Date }; +export type PostResponse = { + boardId: number; +}; + +export type BoardData = Omit & StepTwoData & { meetingDate: string | Date }; +export type ParsedData = Omit; From fde610b1d3c10a5c28a141b20341cfd1de4ebdcb Mon Sep 17 00:00:00 2001 From: naro-Kim Date: Fri, 14 Jul 2023 22:30:57 +0900 Subject: [PATCH 15/22] =?UTF-8?q?chore:=20AgeModal=20sizeProp=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/write/components/AgeModal/AgeModal.css.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/write/components/AgeModal/AgeModal.css.ts b/src/app/write/components/AgeModal/AgeModal.css.ts index 9dbb96ba..9439df12 100644 --- a/src/app/write/components/AgeModal/AgeModal.css.ts +++ b/src/app/write/components/AgeModal/AgeModal.css.ts @@ -1,6 +1,7 @@ import { style } from '@vanilla-extract/css'; import { fontVariant } from '@/styles/variant.css'; import { screenMQ, themeTokens } from '@/styles/theme.css'; +import { sizeProp } from '@/utils/sizeProp'; const { space, color } = themeTokens; @@ -21,7 +22,7 @@ export const buttonWrapper = style({ export const modalContentWrapper = style({ display: 'grid', gridAutoFlow: 'row', - gap: '36px', + gap: sizeProp(36), }); export const modalContent = style({ From a3632bad0f67b7881d49fbb355ac9f1a5437d2b7 Mon Sep 17 00:00:00 2001 From: naro-Kim Date: Fri, 14 Jul 2023 22:46:14 +0900 Subject: [PATCH 16/22] =?UTF-8?q?chore:=20any=EB=A5=BC=20PostResponse=20ty?= =?UTF-8?q?pe=EC=9C=BC=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/write/api.ts | 6 +++--- src/api/write/hooks/usePostBoard.ts | 4 +++- src/app/write/components/Form/SecondStep.tsx | 5 ++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/api/write/api.ts b/src/api/write/api.ts index 71337ebc..458802d2 100644 --- a/src/api/write/api.ts +++ b/src/api/write/api.ts @@ -1,11 +1,11 @@ import { request } from '@/utils/ky/request'; -import { ParsedData } from '@/app/write/types'; +import { ParsedData, PostResponse } from '@/app/write/types'; class WriteAPI { /** * 유저가 입력한 데이터로 먹팟을 생성합니다. * @param data - 유저가 입력한 board 데이터 */ - async postBoard({ ...data }: ParsedData) { + async postBoard({ ...data }: ParsedData): Promise { return request .post('v2/boards', { json: { @@ -19,7 +19,7 @@ class WriteAPI { * @param boardId - 수정할 board의 id * @param data - 유저가 입력한 board 데이터 */ - async patchBoard(boardId: number, { ...data }: ParsedData) { + async patchBoard(boardId: number, { ...data }: ParsedData): Promise { return request .patch(`v2/boards/${boardId}`, { json: { diff --git a/src/api/write/hooks/usePostBoard.ts b/src/api/write/hooks/usePostBoard.ts index ad8f92d7..c7f35246 100644 --- a/src/api/write/hooks/usePostBoard.ts +++ b/src/api/write/hooks/usePostBoard.ts @@ -2,11 +2,13 @@ import { useQueryClient } from '@tanstack/react-query'; import { useMutation } from '@/hooks'; import { boardKeys } from '@/api/board'; import { writeAPI } from '@/api/write/api'; +import { ParsedData, PostResponse } from '@/app/write/types'; +import { HTTPError } from 'ky'; const usePostBoard = () => { const queryClient = useQueryClient(); - return useMutation(writeAPI.postBoard, { + return useMutation(writeAPI.postBoard, { onSuccess: () => { void queryClient.invalidateQueries(boardKeys.list()); }, diff --git a/src/app/write/components/Form/SecondStep.tsx b/src/app/write/components/Form/SecondStep.tsx index 910689d1..e4ff82ab 100644 --- a/src/app/write/components/Form/SecondStep.tsx +++ b/src/app/write/components/Form/SecondStep.tsx @@ -1,7 +1,7 @@ 'use client'; import { FormProvider, SubmitHandler } from 'react-hook-form'; import useFormStore from '@/app/write/store/useFormStore'; -import { BoardData } from '@/app/write/types'; +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'; @@ -24,8 +24,7 @@ const SecondStep = () => { board( { ...parseData(data) }, { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - onSuccess: (response: any) => { + onSuccess: (response: PostResponse) => { openToast(); router.push(`/board/${response.boardId}`); reset(); From 60441c352281a9883b348ff10415c229531f7832 Mon Sep 17 00:00:00 2001 From: naro_Kim Date: Thu, 20 Jul 2023 02:23:25 +0900 Subject: [PATCH 17/22] =?UTF-8?q?chore:=20useFunnel,=20usePostBoard=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/write/hooks/usePostBoard.ts | 4 +--- src/hooks/useFunnel/useFunnel.ts | 4 +++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/api/write/hooks/usePostBoard.ts b/src/api/write/hooks/usePostBoard.ts index c7f35246..ad8f92d7 100644 --- a/src/api/write/hooks/usePostBoard.ts +++ b/src/api/write/hooks/usePostBoard.ts @@ -2,13 +2,11 @@ import { useQueryClient } from '@tanstack/react-query'; import { useMutation } from '@/hooks'; import { boardKeys } from '@/api/board'; import { writeAPI } from '@/api/write/api'; -import { ParsedData, PostResponse } from '@/app/write/types'; -import { HTTPError } from 'ky'; const usePostBoard = () => { const queryClient = useQueryClient(); - return useMutation(writeAPI.postBoard, { + return useMutation(writeAPI.postBoard, { onSuccess: () => { void queryClient.invalidateQueries(boardKeys.list()); }, diff --git a/src/hooks/useFunnel/useFunnel.ts b/src/hooks/useFunnel/useFunnel.ts index d844fdcd..34191c95 100644 --- a/src/hooks/useFunnel/useFunnel.ts +++ b/src/hooks/useFunnel/useFunnel.ts @@ -1,3 +1,4 @@ +import { useRouter } from 'next/navigation'; import { useState } from 'react'; function assertString(value: unknown): asserts value is string { @@ -17,10 +18,11 @@ const useFunnel = ( ] => { assertString(steps[0]); const [step, setStep] = useState(steps[0]); - + const router = useRouter(); const prevStep = () => { const currentStepIndex = steps.indexOf(step); if (currentStepIndex === 0) { + router.back(); return; } const newStep = steps[currentStepIndex - 1]; From d3f21adcdf19180ae106b0b110674e5ec40b0728 Mon Sep 17 00:00:00 2001 From: naro_Kim Date: Thu, 20 Jul 2023 02:25:01 +0900 Subject: [PATCH 18/22] =?UTF-8?q?chore:=20flex=20start,=20end=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/app/write/components/AgeModal/AgeModal.css.ts | 2 +- src/components/Input/Input.css.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/write/components/AgeModal/AgeModal.css.ts b/src/app/write/components/AgeModal/AgeModal.css.ts index 9439df12..54aa3491 100644 --- a/src/app/write/components/AgeModal/AgeModal.css.ts +++ b/src/app/write/components/AgeModal/AgeModal.css.ts @@ -15,7 +15,7 @@ export const openButton = style({ export const buttonWrapper = style({ display: 'flex', - justifyContent: 'end', + justifyContent: 'flex-end', alignItems: 'center', }); diff --git a/src/components/Input/Input.css.ts b/src/components/Input/Input.css.ts index ae05abd6..843d9773 100644 --- a/src/components/Input/Input.css.ts +++ b/src/components/Input/Input.css.ts @@ -36,7 +36,7 @@ export const section = recipe({ gridAutoFlow: 'row', gridTemplateColumns: '1fr', gridGap: space.sm, - alignItems: 'start', + alignItems: 'flex-start', }, }, }, From 20a6ca1d2fe81dd6aedb7efc5df3153fd4bccef6 Mon Sep 17 00:00:00 2001 From: naro_Kim Date: Fri, 21 Jul 2023 22:23:49 +0900 Subject: [PATCH 19/22] =?UTF-8?q?feat:=20=EC=B5=9C=EB=8C=80=EB=82=98?= =?UTF-8?q?=EC=9D=B4=20=EC=97=90=EB=9F=AC=EB=A9=94=EC=84=B8=EC=A7=80=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/write/components/AgeModal/AgeClearButton.tsx | 2 +- src/app/write/components/Form/FirstStep.tsx | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/app/write/components/AgeModal/AgeClearButton.tsx b/src/app/write/components/AgeModal/AgeClearButton.tsx index 058a2606..e1d1e98c 100644 --- a/src/app/write/components/AgeModal/AgeClearButton.tsx +++ b/src/app/write/components/AgeModal/AgeClearButton.tsx @@ -9,7 +9,7 @@ const AgeClearButton = () => { resetField(MIN_AGE); resetField(MAX_AGE); }, [resetField]); - return ; + return ; }; export default AgeClearButton; diff --git a/src/app/write/components/Form/FirstStep.tsx b/src/app/write/components/Form/FirstStep.tsx index ae315506..7481b78b 100644 --- a/src/app/write/components/Form/FirstStep.tsx +++ b/src/app/write/components/Form/FirstStep.tsx @@ -66,6 +66,9 @@ const FirstStep = ({ nextStep }: stepProps) => { )} + + {stepOneMethod.formState.errors['maxAge']?.message} +
From 7a1735ead6847f0ab474e3ef2b4dedee231faf3c Mon Sep 17 00:00:00 2001 From: naro_Kim Date: Sat, 22 Jul 2023 10:51:14 +0900 Subject: [PATCH 20/22] =?UTF-8?q?chore:=20useLockScroll=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/BottomSheet/BottomSheetView.tsx | 2 +- src/hooks/useLockScroll/useLockScroll.ts | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/components/BottomSheet/BottomSheetView.tsx b/src/components/BottomSheet/BottomSheetView.tsx index 8578dcdc..5e39b7d8 100644 --- a/src/components/BottomSheet/BottomSheetView.tsx +++ b/src/components/BottomSheet/BottomSheetView.tsx @@ -17,7 +17,7 @@ interface Props extends HTMLAttributes { const BottomSheetView = forwardRef( ({ title, isOpen, children, onClose, className, ...rest }, ref) => { - useLockScroll(); + useLockScroll(isOpen); return ( <> diff --git a/src/hooks/useLockScroll/useLockScroll.ts b/src/hooks/useLockScroll/useLockScroll.ts index 0bb15ff2..6270d562 100644 --- a/src/hooks/useLockScroll/useLockScroll.ts +++ b/src/hooks/useLockScroll/useLockScroll.ts @@ -1,12 +1,16 @@ import { useLayoutEffect } from 'react'; -const useLockScroll = () => { +const useLockScroll = (isOpen = true) => { useLayoutEffect(() => { - document.body.style.overflow = 'hidden'; + if (isOpen) { + document.body.style.overflow = 'hidden'; + } else { + document.body.style.overflow = 'unset'; + } + return () => { document.body.style.overflow = 'unset'; }; - }, []); + }, [isOpen]); }; - export default useLockScroll; From 7c138dc7982ae08ec15655f185cd1fb5eb50dc9a Mon Sep 17 00:00:00 2001 From: naro_Kim Date: Sat, 22 Jul 2023 10:52:16 +0900 Subject: [PATCH 21/22] =?UTF-8?q?chore:=20Date=20helper=20=EC=98=81?= =?UTF-8?q?=EC=97=AD=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/write/components/InputDate/InputDate.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/write/components/InputDate/InputDate.tsx b/src/app/write/components/InputDate/InputDate.tsx index 408f3c84..8a3acfcd 100644 --- a/src/app/write/components/InputDate/InputDate.tsx +++ b/src/app/write/components/InputDate/InputDate.tsx @@ -7,6 +7,7 @@ import { DateInput, DayPicker, BottomSheet } from '@/components'; import { inputWrapper } from '@/components/Input/Input.css'; import { useOverlay, useIsMobile } from '@/hooks'; import { preventClick } from './InputDate.css'; +import InputErrorMessage from '@/components/Input/InputErrorMessage'; type TControl = { /** Input을 제어하는 컨트롤입니다. */ @@ -50,7 +51,8 @@ const InputDate = ({ ...props }: TControl) => { mobile && openBottomSheet(renderBottomSheet({ value, onChange })); }} > - + +
)} /> From 2cdd904b74918c2e39c248b5091487fc9a0dd47b Mon Sep 17 00:00:00 2001 From: naro_Kim Date: Sat, 22 Jul 2023 11:00:25 +0900 Subject: [PATCH 22/22] =?UTF-8?q?feat:=20=EC=83=88=EB=A1=9C=EA=B3=A0?= =?UTF-8?q?=EC=B9=A8=20=EB=B0=8F=20=EB=B9=84=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/write/page.tsx | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/app/write/page.tsx b/src/app/write/page.tsx index d542377e..7adc771f 100644 --- a/src/app/write/page.tsx +++ b/src/app/write/page.tsx @@ -2,17 +2,20 @@ import FirstStep from './components/Form/FirstStep'; import SecondStep from './components/Form/SecondStep'; import { wrapper } from './style.css'; -import { useEffect } from 'react'; import { useFunnel } from '@/hooks'; import { useProfile } from '@/api/hooks'; import WriteTitle from './components/WriteTitle/WriteTitle'; +import useFormStore from './store/useFormStore'; +import { useEffect } from 'react'; +import { useRouter } from 'next/navigation'; export default function Write() { const [step, { prevStep, nextStep }] = useFunnel(['1', '2']); - const { data: profile } = useProfile(); - const isLogin = Boolean(profile); - if (!isLogin) { - // window.history.back(); + const { reset, setData } = useFormStore(); + const { data } = useProfile(); + const router = useRouter(); + if (!data) { + router.push('/login'); } const preventClose = (e: BeforeUnloadEvent) => { @@ -21,19 +24,18 @@ export default function Write() { }; useEffect(() => { - (() => { - window.addEventListener('beforeunload', preventClose); - })(); + window.addEventListener('beforeunload', preventClose); return () => { window.removeEventListener('beforeunload', preventClose); + reset(); }; }, []); return (
- {step === '1' && } - {step === '2' && } + {step === '1' && } + {step === '2' && }
); }