Skip to content

Commit

Permalink
Merge pull request #111 from YAPP-Github/feature/FE-081
Browse files Browse the repository at this point in the history
  • Loading branch information
naro-Kim authored Jul 26, 2023
2 parents c89490e + 4e8bce9 commit 6b118b3
Show file tree
Hide file tree
Showing 43 changed files with 504 additions and 161 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions src/api/index.ts
Original file line number Diff line number Diff line change
@@ -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;
17 changes: 15 additions & 2 deletions src/api/write/api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { request } from '@/utils/ky/request';
import { ParsedData, PostResponse } from '@/app/write/types';
import { BoardData, ParsedData, PostResponse } from '@/app/write/types';
interface Props {
boardId: number;
data: ParsedData;
}
class WriteAPI {
/**
* 유저가 입력한 데이터로 먹팟을 생성합니다.
Expand All @@ -19,7 +23,8 @@ class WriteAPI {
* @param boardId - 수정할 board의 id
* @param data - 유저가 입력한 board 데이터
*/
async patchBoard(boardId: number, { ...data }: ParsedData): Promise<PostResponse> {

async patchBoard({ boardId, data }: Props): Promise<PostResponse> {
return request
.patch(`v2/boards/${boardId}`, {
json: {
Expand All @@ -28,6 +33,14 @@ class WriteAPI {
})
.json();
}

/**
* boardId에 해당하는 먹팟을 수정하기 위해 정보를 가져옵니다.
* @param boardId - 가져올 board의 id
*/
async getBoardDetail(boardId: number) {
return request.get(`v1/boards/${boardId}/update`).json<BoardData>();
}
}

// api fetchers instance
Expand Down
5 changes: 5 additions & 0 deletions src/api/write/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export { default as useGetBoard } from './useGetBoard';

export { default as usePostBoard } from './usePostBoard';

export { default as usePatchBoard } from './usePatchBoard';
21 changes: 21 additions & 0 deletions src/api/write/hooks/useGetBoard.ts
Original file line number Diff line number Diff line change
@@ -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;
16 changes: 16 additions & 0 deletions src/api/write/hooks/usePatchBoard.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 usePatchBoard = (boardId: number) => {
const queryClient = useQueryClient();

return useMutation(writeAPI.patchBoard, {
onSuccess: () => {
void queryClient.invalidateQueries(boardKeys.detail(boardId));
},
});
};

export default usePatchBoard;
3 changes: 3 additions & 0 deletions src/api/write/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './hooks';
export * from './queryKeys';
export * from './api';
4 changes: 4 additions & 0 deletions src/api/write/queryKeys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const writeKeys = {
all: ['write'],
board: (boardId: number) => [...writeKeys.all, 'detail', boardId] as const,
};
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ const DetailMenuButton = ({ board }: Props) => {
);
};

const handleClickEditBoard = () => {
router.push(`/write/${boardId}`);
};

const handleClickCancelJoinButton = () => {
openModal(
<CancelJoinModal
Expand All @@ -74,7 +78,7 @@ const DetailMenuButton = ({ board }: Props) => {
</Dropdown.Item>
{isWriter && (
<>
<Dropdown.Item itemKey="change">
<Dropdown.Item itemKey="change" onClick={handleClickEditBoard}>
<Typography variant="label2">수정하기</Typography>
</Dropdown.Item>
<Dropdown.Item itemKey="delete" onClick={handleClickDelete}>
Expand Down
23 changes: 23 additions & 0 deletions src/app/write/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Suspense fallback={<InputLoading />}>
<BoardForm />
</Suspense>
);
};

export default BoardEditPage;
22 changes: 13 additions & 9 deletions src/app/write/components/AgeModal/AgeBottomSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,19 @@ const AgeBottomSheet = ({ ...props }: TControl<any>) => {
<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>
<div className={modalContent}>
<Typography variant="label3" color="sub">
최소 나이 선택
</Typography>
<AgeController placeholder={'최소 나이 제한'} name={MIN_AGE} control={control} />
</div>
<div className={modalContent}>
<Typography variant="label3" color="sub">
최대 나이 선택
</Typography>
<AgeController placeholder={'최대 나이 제한'} name={MAX_AGE} control={control} />
</div>
</div>
<BottomButton onClick={handleSave} type="button">
저장하기
Expand Down
11 changes: 8 additions & 3 deletions src/app/write/components/AgeModal/AgeController.tsx
Original file line number Diff line number Diff line change
@@ -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<T extends FieldValues> = {
control: Control<T>;
Expand All @@ -11,23 +13,26 @@ type ControllerProps<T extends FieldValues> = {

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const AgeController = ({ disabled = false, control, name, placeholder }: ControllerProps<any>) => {
const isMobile = useIsMobile();
const DropdownItemWrapper = isMobile ? DropdownModal : DropdownMenu;

return (
<Controller
defaultValue={null}
control={control}
name={name}
render={({ field: { value, onChange } }) => (
<Dropdown style={{ width: '100%' }}>
<Dropdown style={{ width: '100%' }} disableClickOutside={isMobile}>
<DropdownButton disabled={disabled} placeholder={placeholder}>
{value && `${value}세`}
</DropdownButton>
<DropdownMenu selectable selectedItemKey={value} onSelectChange={onChange}>
<DropdownItemWrapper selectable selectedItemKey={value} onSelectChange={onChange}>
{AGE_LIST.map((v) => (
<DropdownItem key={v} itemKey={v}>
{v}
</DropdownItem>
))}
</DropdownMenu>
</DropdownItemWrapper>
</Dropdown>
)}
/>
Expand Down
7 changes: 4 additions & 3 deletions src/app/write/components/AgeModal/AgeModal.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`,
});
2 changes: 2 additions & 0 deletions src/app/write/components/AgeModal/AgeModal.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
7 changes: 3 additions & 4 deletions src/app/write/components/AgeModal/BirthYear.tsx
Original file line number Diff line number Diff line change
@@ -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<T extends FieldValues> = {
control: Control<T>;
Expand All @@ -10,13 +9,13 @@ type ModalProps<T extends FieldValues> = {
const BirthYear = ({ control }: ModalProps<FieldValues>) => {
const [min, max] = useWatch({ control, name: ['minAge', 'maxAge'] });
if (!min || !max) {
return <div className={clsx(modalContent, birthText)}>-</div>;
return <div className={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)}>
<div className={birthText}>
{minBirthYear}년생 - {maxBirthYear}년생
</div>
);
Expand Down
1 change: 1 addition & 0 deletions src/app/write/components/Counter/Counter.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
46 changes: 46 additions & 0 deletions src/app/write/components/Form/BoardForm.tsx
Original file line number Diff line number Diff line change
@@ -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() as { id: string };
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 (
<div className={wrapper}>
<WriteTitle prevStep={prevStep} />
{step === '1' && <FirstStep setData={setData} nextStep={nextStep} />}
{step === '2' && <SecondStep boardId={Number(boardId)} reset={reset} isPatch />}
</div>
);
}
20 changes: 7 additions & 13 deletions src/app/write/components/Form/FirstStep.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
'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';
import { formWrapper, sectionGap, inputGap, submitButton, flexBetween, ageError } from './Form.css';
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(
(data: StepOneData) => {
if (!data) {
Expand Down Expand Up @@ -66,7 +65,7 @@ const FirstStep = ({ nextStep }: stepProps) => {
</InputSection>
</>
)}
<Typography style={{ margin: '0', textAlign: 'end' }} color="red500" variant="label5" as="p">
<Typography className={ageError} color="red500" variant="label5" as="p">
{stepOneMethod.formState.errors['maxAge']?.message}
</Typography>
</div>
Expand All @@ -84,17 +83,12 @@ const FirstStep = ({ nextStep }: stepProps) => {
{...stepOneMethod.register('locationDetail', { required: false })}
name={'locationDetail'}
placeholder="ex) 1층 로비, 식당 입구"
maxLength={100}
maxLength={30}
></Input>
</InputSection>
</div>
</div>
<Button
size="paddingMedium"
className={submitButton}
type="submit"
disabled={!stepOneMethod.formState.isDirty}
>
<Button size="paddingMedium" className={submitButton} type="submit">
다음
</Button>
</form>
Expand Down
Loading

0 comments on commit 6b118b3

Please sign in to comment.