Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/fe-078 : 생성 페이지 1.0v 업데이트 적용 #107

Merged
merged 23 commits into from
Jul 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
75f63ae
edit: map data type 변경
naro-Kim Jul 10, 2023
b9aaec5
edit: Date parsing type 변경
naro-Kim Jul 10, 2023
db47e65
edit: 나이 리스트 상수화
naro-Kim Jul 10, 2023
0971c4b
feat: mobile 대응 InputDropdown 컴포넌트 변경
naro-Kim Jul 13, 2023
1135c43
feat: WriteTitle 컴포넌트 분리
naro-Kim Jul 13, 2023
9aa154e
feat: goback IconButton 추가
naro-Kim Jul 13, 2023
2e7bef7
edit: BottomSheet Css 수정
naro-Kim Jul 13, 2023
9425483
feat: mobile 나이제한 설정 컴포넌트 추가
naro-Kim Jul 13, 2023
223bcfc
feat: schema logic 변경
naro-Kim Jul 13, 2023
2da8cbe
feat: mobile 날짜 시간 선택 컴포넌트 추가
naro-Kim Jul 13, 2023
38dd398
feat: Data parsing logic 업데이트
naro-Kim Jul 13, 2023
18b8531
style: Form style 업데이트
naro-Kim Jul 13, 2023
a0c1586
feat: post api mutation query 변경
naro-Kim Jul 14, 2023
e44d3ca
chore: step별 post board API 적용
naro-Kim Jul 14, 2023
fde610b
chore: AgeModal sizeProp 적용
naro-Kim Jul 14, 2023
a3632ba
chore: any를 PostResponse type으로 수정
naro-Kim Jul 14, 2023
87a10f3
Merge branch 'dev' of https://github.com/YAPP-Github/mukpat-client in…
naro-Kim Jul 18, 2023
60441c3
chore: useFunnel, usePostBoard 수정
naro-Kim Jul 19, 2023
d3f21ad
chore: flex start, end 수정
naro-Kim Jul 19, 2023
20a6ca1
feat: 최대나이 에러메세지 추가
naro-Kim Jul 21, 2023
7a1735e
chore: useLockScroll 수정
naro-Kim Jul 22, 2023
7c138dc
chore: Date helper 영역 추가
naro-Kim Jul 22, 2023
2cdd904
feat: 새로고침 및 비로그인 방지
naro-Kim Jul 22, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions src/api/write/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { request } from '@/utils/ky/request';
import { ParsedData, PostResponse } from '@/app/write/types';
class WriteAPI {
/**
* 유저가 입력한 데이터로 먹팟을 생성합니다.
* @param data - 유저가 입력한 board 데이터
*/
async postBoard({ ...data }: ParsedData): Promise<PostResponse> {
return request
.post('v2/boards', {
json: {
...data,
},
})
.json();
}
/**
* boardId에 해당하는 먹팟을 수정합니다
* @param boardId - 수정할 board의 id
* @param data - 유저가 입력한 board 데이터
*/
async patchBoard(boardId: number, { ...data }: ParsedData): Promise<PostResponse> {
return request
.patch(`v2/boards/${boardId}`, {
json: {
...data,
},
})
.json();
}
}

// api fetchers instance
export const writeAPI = new WriteAPI();
16 changes: 16 additions & 0 deletions src/api/write/hooks/usePostBoard.ts
Original file line number Diff line number Diff line change
@@ -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;
64 changes: 64 additions & 0 deletions src/app/write/components/AgeModal/AgeBottomSheet.tsx
Original file line number Diff line number Diff line change
@@ -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<T extends FieldValues> = {
/** Input을 제어하는 컨트롤입니다. */
control: Control<T>;
/** 유효성검사에서 해당 Input이 invalid할 경우 error message의 표시 여부를 설정합니다. @default true*/
showError?: boolean;
} & HTMLAttributes<HTMLDivElement>;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const AgeBottomSheet = ({ ...props }: TControl<any>) => {
const [save, setSave] = useBooleanState(false);
const [openBottomSheet, closeBottomSheet] = useOverlay();
const { control } = props;

const handleSave = useCallback(() => {
setSave();
closeBottomSheet();
}, [setSave, closeBottomSheet]);

const renderBottomSheet = () => {
return (
<BottomSheet onClose={closeBottomSheet} title={'참여 가능 나이를 선택해주세요.'}>
<div>
<BirthYear control={control} />
<div className={modalContent}>
<Typography variant="label3" color="sub">
최소 나이 선택
</Typography>
<AgeController placeholder={'최소 나이 제한'} name={MIN_AGE} control={control} />
<Typography variant="label3" color="sub">
최대 나이 선택
</Typography>
<AgeController placeholder={'최대 나이 제한'} name={MAX_AGE} control={control} />
</div>
<BottomButton onClick={handleSave} type="button">
저장하기
</BottomButton>
</div>
</BottomSheet>
);
};

return (
<div className={buttonWrapper}>
<button className={openButton} type="button" onClick={() => openBottomSheet(renderBottomSheet())}>
나이 제한 걸기
</button>
{save && control.getFieldState(MAX_AGE).isDirty && <AgeClearButton />}
</div>
);
};

export default AgeBottomSheet;
15 changes: 15 additions & 0 deletions src/app/write/components/AgeModal/AgeClearButton.tsx
Original file line number Diff line number Diff line change
@@ -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 <IconButton type="button" width={36} height={36} iconType="close" onClick={resetValues} />;
};

export default AgeClearButton;
11 changes: 7 additions & 4 deletions src/app/write/components/AgeModal/AgeController.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
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<T extends FieldValues> = {
control: Control<T>;
name: string;
placeholder: string;
disabled?: boolean;
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const AgeController = ({ control, name, placeholder }: ControllerProps<any>) => {
const AgeController = ({ disabled = false, control, name, placeholder }: ControllerProps<any>) => {
return (
<Controller
defaultValue={null}
control={control}
name={name}
render={({ field: { value, onChange } }) => (
<Dropdown style={{ width: '100%' }}>
<DropdownButton placeholder={placeholder}>{value && `${value}세`}</DropdownButton>
<DropdownButton disabled={disabled} placeholder={placeholder}>
{value && `${value}세`}
</DropdownButton>
<DropdownMenu selectable selectedItemKey={value} onSelectChange={onChange}>
{list.map((v) => (
{AGE_LIST.map((v) => (
<DropdownItem key={v} itemKey={v}>
{v}
</DropdownItem>
Expand Down
25 changes: 18 additions & 7 deletions src/app/write/components/AgeModal/AgeModal.css.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
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';
import { sizeProp } from '@/utils/sizeProp';

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({
display: 'flex',
justifyContent: 'end',
justifyContent: 'flex-end',
alignItems: 'center',
});

export const modalContentWrapper = style({
display: 'grid',
gridAutoFlow: 'row',
gap: '36px',
gap: sizeProp(36),
});

export const modalContent = style({
Expand All @@ -28,6 +31,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({
Expand Down
53 changes: 14 additions & 39 deletions src/app/write/components/AgeModal/AgeModal.tsx
Original file line number Diff line number Diff line change
@@ -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<T extends FieldValues> = {
control: Control<T>;
} & HTMLAttributes<HTMLDivElement>;

const BirthYear = ({ control }: ModalProps<FieldValues>) => {
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 (
<div className={clsx(modalContent, birthText)}>
{minBirthYear}년생 - {maxBirthYear}년생
</div>
);
};

const ClearButton = () => {
const { resetField } = useFormContext();
const resetValues = useCallback(() => {
resetField('minAge');
resetField('maxAge');
}, [resetField]);
return <IconButton width={36} height={36} iconType="close" onClick={resetValues} />;
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const AgeModal = ({ control }: ModalProps<any>) => {
const [openModal, closeModal] = useOverlay();
Expand All @@ -58,9 +34,9 @@ const AgeModal = ({ control }: ModalProps<any>) => {
<ModalContent className={modalContentWrapper} size="large">
<BirthYear control={control} />
<div className={modalContent}>
<AgeController placeholder={'최소 나이 제한'} name={'minAge'} control={control} />
<AgeController placeholder={'최소 나이 제한'} name={MIN_AGE} control={control} />
<SvgIcon width={10} id="bar" />
<AgeController placeholder={'최대 나이 제한'} name={'maxAge'} control={control} />
<AgeController placeholder={'최대 나이 제한'} name={MAX_AGE} control={control} />
</div>
</ModalContent>
<ModalFooter type="vertical">
Expand All @@ -74,15 +50,14 @@ const AgeModal = ({ control }: ModalProps<any>) => {
</Modal>
);
};

return (
<>
<div className={buttonWrapper}>
<button className={openButton} type="button" onClick={() => openModal(renderModal())}>
나이 제한 걸기
</button>
{save && formState.dirtyFields['minAge'] && formState.dirtyFields['maxAge'] && <ClearButton />}
</div>
</>
<div className={buttonWrapper}>
<button className={openButton} type="button" onClick={() => openModal(renderModal())}>
나이 제한 걸기
</button>
{save && formState.dirtyFields['minAge'] && formState.dirtyFields['maxAge'] && <AgeClearButton />}
</div>
);
};

Expand Down
26 changes: 26 additions & 0 deletions src/app/write/components/AgeModal/BirthYear.tsx
Original file line number Diff line number Diff line change
@@ -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<T extends FieldValues> = {
control: Control<T>;
};

const BirthYear = ({ control }: ModalProps<FieldValues>) => {
const [min, max] = useWatch({ control, name: ['minAge', 'maxAge'] });
if (!min || !max) {
return <div className={clsx(modalContent, birthText)}>-</div>;
} else {
const year = dayjs().year();
const minBirthYear = (year - min).toString().slice(2, 4);
const maxBirthYear = (year - max).toString().slice(2, 4);
return (
<div className={clsx(modalContent, birthText)}>
{minBirthYear}년생 - {maxBirthYear}년생
</div>
);
}
};

export default BirthYear;
10 changes: 0 additions & 10 deletions src/app/write/components/AgeModal/getAgeList.ts

This file was deleted.

14 changes: 0 additions & 14 deletions src/app/write/components/AgeModal/useBirthYear.ts

This file was deleted.

Loading