From 830c6ae81ed35bcb4313c373a4f16edea95531ae Mon Sep 17 00:00:00 2001 From: JoStar33 Date: Fri, 23 Aug 2024 16:46:12 +0900 Subject: [PATCH] =?UTF-8?q?update:=20=EC=BB=A8=ED=85=90=EC=B8=A0=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20API=EC=A0=9C=EC=9E=91=20=EB=B0=8F=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/contents.ts | 4 +- src/components/contents/write/index.tsx | 15 ++++-- .../hookForm/{FileDrop.tsx => ImageDrop.tsx} | 20 ++++---- src/components/hookForm/index.tsx | 4 +- src/containers/contents/write.tsx | 47 +++++++++++++++++-- src/mocks/contents/contentsPostHandler.ts | 31 ++++++++++++ src/mocks/contents/index.ts | 3 +- .../fakeDatabase/resources/contents/index.ts | 16 ++++++- src/mocks/utils/getUserInfo.ts | 17 +++++++ src/stores/loading.ts | 6 ++- src/types/contents.ts | 7 ++- 11 files changed, 146 insertions(+), 24 deletions(-) rename src/components/hookForm/{FileDrop.tsx => ImageDrop.tsx} (93%) create mode 100644 src/mocks/contents/contentsPostHandler.ts create mode 100644 src/mocks/utils/getUserInfo.ts diff --git a/src/api/contents.ts b/src/api/contents.ts index f88b6d8..5340888 100644 --- a/src/api/contents.ts +++ b/src/api/contents.ts @@ -5,7 +5,9 @@ const Contents = { Get: { list: () => requests.get('/contents'), }, - Post: {}, + Post: { + write: (body: contents.IContentsRegisterRequest) => requests.post('/contents', body), + }, Put: {}, Patch: {}, Delete: {}, diff --git a/src/components/contents/write/index.tsx b/src/components/contents/write/index.tsx index c72d244..e1f6c24 100644 --- a/src/components/contents/write/index.tsx +++ b/src/components/contents/write/index.tsx @@ -12,9 +12,10 @@ export default function ContentsWrite({ onSubmit }: IProps) { const { handleSubmit } = useFormContext(); return ( - name="image" /> - name="title" /> - name="description" /> +

컨텐츠 작성

+ name="image" /> + name="title" label="제목" /> + name="description" label="내용" /> @@ -27,5 +28,13 @@ const S = { display: flex; flex-direction: column; gap: 10px; + .contents-write { + &__header { + text-align: center; + font-size: 19px; + font-weight: 500; + margin: 10px 0; + } + } `, }; diff --git a/src/components/hookForm/FileDrop.tsx b/src/components/hookForm/ImageDrop.tsx similarity index 93% rename from src/components/hookForm/FileDrop.tsx rename to src/components/hookForm/ImageDrop.tsx index a325d31..fe1099c 100644 --- a/src/components/hookForm/FileDrop.tsx +++ b/src/components/hookForm/ImageDrop.tsx @@ -8,6 +8,7 @@ import { flexCenter } from '@/styles/Common'; import ErrorText from './ErrorText'; import Lightbox from 'yet-another-react-lightbox'; import Image from '@/components/common/Image'; +import { FaPlus } from 'react-icons/fa'; import 'yet-another-react-lightbox/styles.css'; @@ -34,14 +35,14 @@ export default function ImageDrop({ name, boxSize = 150, } = useFormContext(); const [previewFiles, setPreviewFiles] = React.useState([]); const inputFileRef = React.useRef(null); - const [isDragActive, setIsDragActive] = React.useState(false); + // const [isDragActive, setIsDragActive] = React.useState(false); const [open, setOpen] = React.useState(false); const files: File[] = watch(name); - const handleDragStart = () => setIsDragActive(true); + // const handleDragStart = () => setIsDragActive(true); - const handleDragEnd = () => setIsDragActive(false); + // const handleDragEnd = () => setIsDragActive(false); const handleDragOver: React.DragEventHandler = (event) => { event.preventDefault(); @@ -74,6 +75,7 @@ export default function ImageDrop({ name, boxSize = 150, const file = acceptedFiles[0]; if (file.size > 2000000) return alert('2MB이하의 파일만 업로드가능합니다.'); + console.log(file.stream); if (!acceptedSize) return setValue(name, [file] as PathValue>); const [acceptedWidth, acceptedHeight] = acceptedSize.split('x'); @@ -136,14 +138,16 @@ export default function ImageDrop({ name, boxSize = 150, htmlFor={name} boxSize={boxSize} ref={inputFileRef} - onDragEnter={handleDragStart} + // onDragEnter={handleDragStart} onDragOver={handleDragOver} // dragover 핸들러 추가 - onDragLeave={handleDragEnd} + // onDragLeave={handleDragEnd} onChange={handleUploadImages as unknown as React.FormEventHandler} onDrop={handleDrop} > - {isDragActive ? :
} + + +

{guidText}

{errors[name] && } @@ -189,10 +193,6 @@ const StyledImageArea = styled.label<{ boxSize: number }>` :hover { transform: scale(1.01); } - .drop-in { - padding: 15px; - height: 100%; - } input { display: none; } diff --git a/src/components/hookForm/index.tsx b/src/components/hookForm/index.tsx index 6e4c7b2..5a79b97 100644 --- a/src/components/hookForm/index.tsx +++ b/src/components/hookForm/index.tsx @@ -2,7 +2,7 @@ import ErrorText from '@/components/hookForm/ErrorText'; import InputA from '@/components/hookForm/InputA'; import RadioButton from '@/components/hookForm/RadioButton'; import CheckBoxYN from '@/components/hookForm/CheckBoxYN'; -import FileDrop from '@/components/hookForm/FileDrop'; +import ImageDrop from '@/components/hookForm/ImageDrop'; import TextArea from '@/components/hookForm/TextArea'; const Form = { @@ -10,7 +10,7 @@ const Form = { ErrorText, RadioButton, CheckBoxYN, - FileDrop, + ImageDrop, TextArea, }; diff --git a/src/containers/contents/write.tsx b/src/containers/contents/write.tsx index 6c3eb3b..b09d9c4 100644 --- a/src/containers/contents/write.tsx +++ b/src/containers/contents/write.tsx @@ -1,10 +1,15 @@ +import Contents from '@/api/contents'; import ContentsWrite from '@/components/contents/write'; -import { IContentsRegisterForm } from '@/types/contents'; +import { useErrorHandler } from '@/hooks/useErrorHandler'; +import { useModalStore } from '@/stores/modal'; +import { IContentsRegisterForm, IContentsRegisterRequest } from '@/types/contents'; import { schema } from '@/utils/validate/schema'; import { yupResolver } from '@hookform/resolvers/yup'; import { FormProvider, SubmitHandler, useForm } from 'react-hook-form'; export default function ContentsWriteContainer() { + const { handleError } = useErrorHandler(); + const { setModalState, resetModalState } = useModalStore(); const methods = useForm({ defaultValues: { image: [], @@ -14,8 +19,44 @@ export default function ContentsWriteContainer() { resolver: yupResolver(schema.contentsRegisterSchema), }); - const onSubmit: SubmitHandler = (submitData) => { - console.log(submitData); + const fetchContentsWrite = async (requestData: IContentsRegisterRequest) => { + try { + const response = await Contents.Post.write(requestData); + if (response.code !== 200) { + throw new Error(response.message); + } + setModalState((prev) => ({ + ...prev, + type: 'ALERT', + titleText: '등록되었습니다!', + confirmButtonText: '확인', + onClickConfirm: () => { + resetModalState(); + }, + })); + } catch (error: any) { + handleError(error); + } + }; + + const onSubmit: SubmitHandler = async (submitData) => { + const requestData = { + ...submitData, + image: submitData.image[0], + }; + setModalState((prev) => ({ + ...prev, + type: 'CONFIRM', + titleText: '등록하시겠습니까?', + cancelButtonText: '취소', + confirmButtonText: '등록', + onClickConfirm: () => { + fetchContentsWrite(requestData); + }, + onClickCancel: () => { + resetModalState(); + }, + })); }; return ( diff --git a/src/mocks/contents/contentsPostHandler.ts b/src/mocks/contents/contentsPostHandler.ts new file mode 100644 index 0000000..c1301f4 --- /dev/null +++ b/src/mocks/contents/contentsPostHandler.ts @@ -0,0 +1,31 @@ +import { http } from 'msw'; +import { commonUrl } from '@/mocks'; +import { contentsDatabase } from '@/mocks/fakeDatabase/resources/contents'; +import { IContentsRegisterRequest } from '@/types/contents'; +import getUserInfo from '@/mocks/utils/getUserInfo'; +import CustomResponse from '@/mocks/utils/customResponse'; + +const contentsUrl = (path?: string) => `${commonUrl(`/contents${path ?? ''}`)}`; + +const contentsPostHandler = [ + http.post(`${contentsUrl()}`, async ({ request }) => { + try { + const data = (await request.json()) as IContentsRegisterRequest; + const { email } = getUserInfo(request); + contentsDatabase.Create.write(data, email); + } catch (error: any) { + return CustomResponse({ + code: 500, + message: error, + value: undefined, + }); + } + return CustomResponse({ + code: 200, + message: '컨텐츠 등록 성공!', + value: undefined, + }); + }), +]; + +export default contentsPostHandler; diff --git a/src/mocks/contents/index.ts b/src/mocks/contents/index.ts index 9286d37..05d9438 100644 --- a/src/mocks/contents/index.ts +++ b/src/mocks/contents/index.ts @@ -1,5 +1,6 @@ import contentsGetHandler from '@/mocks/contents/contentsGetHandler'; +import contentsPostHandler from '@/mocks/contents/contentsPostHandler'; -const contentsHandler = [...contentsGetHandler]; +const contentsHandler = [...contentsGetHandler, ...contentsPostHandler]; export default contentsHandler; diff --git a/src/mocks/fakeDatabase/resources/contents/index.ts b/src/mocks/fakeDatabase/resources/contents/index.ts index fcd7a17..724d797 100644 --- a/src/mocks/fakeDatabase/resources/contents/index.ts +++ b/src/mocks/fakeDatabase/resources/contents/index.ts @@ -1,5 +1,6 @@ -import { IContentsListElement } from '@/types/contents'; +import { IContentsListElement, IContentsRegisterRequest } from '@/types/contents'; import databaseKey from '@/mocks/fakeDatabase/constants/databaseKey'; +import { dateFormat } from '@/utils/dateFormat'; const contentsDatabase = { Get: { @@ -13,6 +14,19 @@ const contentsDatabase = { }; }, }, + Create: { + write: (request: IContentsRegisterRequest, author: string) => { + const localStorageContentsList = localStorage.getItem(databaseKey.contentsList) ?? ''; + const parsedContentsList: IContentsListElement[] = localStorageContentsList ? JSON.parse(localStorageContentsList) : []; + const newContents: IContentsListElement = { + id: parsedContentsList.length + 1, + ...request, + author, + createdAt: dateFormat.date5(String(new Date())), + }; + localStorage.setItem(databaseKey.contentsList, JSON.stringify([...parsedContentsList, newContents])); + }, + }, }; const initializeContentsDatabase = () => { diff --git a/src/mocks/utils/getUserInfo.ts b/src/mocks/utils/getUserInfo.ts new file mode 100644 index 0000000..d27a567 --- /dev/null +++ b/src/mocks/utils/getUserInfo.ts @@ -0,0 +1,17 @@ +import { DefaultBodyType, StrictRequest } from 'msw'; +import { crypto } from '@/utils/crypto'; + +export default function getUserInfo(request: StrictRequest) { + const accessToken = request.headers.get('Authorization'); + if (!accessToken) + return { + email: 'error', + userId: 'error', + }; + const decryptionToken = crypto.decryptionDES(accessToken) ?? ''; + const splitData = decryptionToken.replaceAll('"', '').split('/'); + return { + email: splitData[0], + userId: splitData[1], + }; +} diff --git a/src/stores/loading.ts b/src/stores/loading.ts index a34ae2d..8569bc1 100644 --- a/src/stores/loading.ts +++ b/src/stores/loading.ts @@ -2,10 +2,12 @@ import { create } from 'zustand'; interface ILoadingStore { isLoading: boolean; - setIsLoading: (isShow: boolean) => void; + setIsLoading: (fn: ((prev: boolean) => boolean) | boolean) => void; } export const useLoadingStore = create((set) => ({ isLoading: false, - setIsLoading: (isShow: boolean) => set((state) => ({ ...state, isLoading: isShow })), + setIsLoading: (fn: ((prev: boolean) => boolean) | boolean) => { + set((state) => ({ isLoading: typeof fn === 'boolean' ? fn : fn(state.isLoading) })); + }, })); diff --git a/src/types/contents.ts b/src/types/contents.ts index a2d5f8f..b60d787 100644 --- a/src/types/contents.ts +++ b/src/types/contents.ts @@ -6,7 +6,7 @@ export interface IContentsListElement { image: string; title: string; createdAt: string; - description: string; + description?: string; author: string; } @@ -18,6 +18,11 @@ export interface IContentsRegisterForm { } /***************************** Request *****************************/ +export interface IContentsRegisterRequest { + image: string; + title: string; + description?: string; +} /***************************** Response *****************************/ export interface IContentsListResponse extends DefaultResponse {