Skip to content

Commit

Permalink
update: 컨텐츠 등록 API제작 및 화면연결
Browse files Browse the repository at this point in the history
  • Loading branch information
JoStar33 committed Aug 23, 2024
1 parent f4f2d3d commit 830c6ae
Show file tree
Hide file tree
Showing 11 changed files with 146 additions and 24 deletions.
4 changes: 3 additions & 1 deletion src/api/contents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ const Contents = {
Get: {
list: () => requests.get<contents.IContentsListResponse>('/contents'),
},
Post: {},
Post: {
write: (body: contents.IContentsRegisterRequest) => requests.post('/contents', body),
},
Put: {},
Patch: {},
Delete: {},
Expand Down
15 changes: 12 additions & 3 deletions src/components/contents/write/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ export default function ContentsWrite({ onSubmit }: IProps) {
const { handleSubmit } = useFormContext<IContentsRegisterForm>();
return (
<S.ContentsWrite onSubmit={handleSubmit(onSubmit)}>
<Form.FileDrop<IContentsRegisterForm> name="image" />
<Form.InputA<IContentsRegisterForm> name="title" />
<Form.TextArea<IContentsRegisterForm> name="description" />
<h1 className="contents-write__header">컨텐츠 작성</h1>
<Form.ImageDrop<IContentsRegisterForm> name="image" />
<Form.InputA<IContentsRegisterForm> name="title" label="제목" />
<Form.TextArea<IContentsRegisterForm> name="description" label="내용" />
<Button type="submit" name="positive">
등록
</Button>
Expand All @@ -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;
}
}
`,
};
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -34,14 +35,14 @@ export default function ImageDrop<T extends FieldValues>({ name, boxSize = 150,
} = useFormContext<T>();
const [previewFiles, setPreviewFiles] = React.useState<TPreviewFiles[] | []>([]);
const inputFileRef = React.useRef<HTMLLabelElement>(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<HTMLLabelElement> = (event) => {
event.preventDefault();
Expand Down Expand Up @@ -74,6 +75,7 @@ export default function ImageDrop<T extends FieldValues>({ 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<T, Path<T>>);

const [acceptedWidth, acceptedHeight] = acceptedSize.split('x');
Expand Down Expand Up @@ -136,14 +138,16 @@ export default function ImageDrop<T extends FieldValues>({ 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<HTMLLabelElement>}
onDrop={handleDrop}
>
<input id={name} type="file" accept="image/*" {...register(name)} />
{isDragActive ? <motion.div animate={{ scale: 1.1 }} className="drop-in"></motion.div> : <div className="drop-in"></div>}
<motion.div whileHover={{ scale: 1.3 }} className="drop-in">
<FaPlus size={25} />
</motion.div>
</StyledImageArea>
<p>{guidText}</p>
{errors[name] && <ErrorText errors={errors} name={name} margin="0 0 5px 0" />}
Expand Down Expand Up @@ -189,10 +193,6 @@ const StyledImageArea = styled.label<{ boxSize: number }>`
:hover {
transform: scale(1.01);
}
.drop-in {
padding: 15px;
height: 100%;
}
input {
display: none;
}
Expand Down
4 changes: 2 additions & 2 deletions src/components/hookForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ 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 = {
InputA,
ErrorText,
RadioButton,
CheckBoxYN,
FileDrop,
ImageDrop,
TextArea,
};

Expand Down
47 changes: 44 additions & 3 deletions src/containers/contents/write.tsx
Original file line number Diff line number Diff line change
@@ -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<IContentsRegisterForm>({
defaultValues: {
image: [],
Expand All @@ -14,8 +19,44 @@ export default function ContentsWriteContainer() {
resolver: yupResolver(schema.contentsRegisterSchema),
});

const onSubmit: SubmitHandler<IContentsRegisterForm> = (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<IContentsRegisterForm> = async (submitData) => {
const requestData = {
...submitData,
image: submitData.image[0],
};
setModalState((prev) => ({
...prev,
type: 'CONFIRM',
titleText: '등록하시겠습니까?',
cancelButtonText: '취소',
confirmButtonText: '등록',
onClickConfirm: () => {
fetchContentsWrite(requestData);
},
onClickCancel: () => {
resetModalState();
},
}));
};

return (
Expand Down
31 changes: 31 additions & 0 deletions src/mocks/contents/contentsPostHandler.ts
Original file line number Diff line number Diff line change
@@ -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;
3 changes: 2 additions & 1 deletion src/mocks/contents/index.ts
Original file line number Diff line number Diff line change
@@ -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;
16 changes: 15 additions & 1 deletion src/mocks/fakeDatabase/resources/contents/index.ts
Original file line number Diff line number Diff line change
@@ -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: {
Expand All @@ -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 = () => {
Expand Down
17 changes: 17 additions & 0 deletions src/mocks/utils/getUserInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { DefaultBodyType, StrictRequest } from 'msw';
import { crypto } from '@/utils/crypto';

export default function getUserInfo(request: StrictRequest<DefaultBodyType>) {
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],
};
}
6 changes: 4 additions & 2 deletions src/stores/loading.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ILoadingStore>((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) }));
},
}));
7 changes: 6 additions & 1 deletion src/types/contents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export interface IContentsListElement {
image: string;
title: string;
createdAt: string;
description: string;
description?: string;
author: string;
}

Expand All @@ -18,6 +18,11 @@ export interface IContentsRegisterForm {
}

/***************************** Request *****************************/
export interface IContentsRegisterRequest {
image: string;
title: string;
description?: string;
}

/***************************** Response *****************************/
export interface IContentsListResponse extends DefaultResponse {
Expand Down

0 comments on commit 830c6ae

Please sign in to comment.