From a034b80efb0a14025b112c58f6cad9bc7ccbda78 Mon Sep 17 00:00:00 2001 From: Seoyoung Date: Tue, 6 Feb 2024 23:13:56 +0900 Subject: [PATCH] =?UTF-8?q?Fix:=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EB=A6=AC=ED=80=98=EC=8A=A4=ED=8A=B8=EC=97=90=20ima?= =?UTF-8?q?geUrl=20=EB=B9=88=EB=AC=B8=EC=9E=90=EC=97=B4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=EB=A1=9C=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EB=B0=8F=20=EC=97=90=EB=9F=AC=20=ED=86=A0=EC=8A=A4=ED=8A=B8,?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=20=ED=9B=84=20=EB=A6=AC=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=83=81=EC=84=B8=20=EC=9D=B4=EB=8F=99=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/create/_components/CreateItem.tsx | 5 +- src/app/create/_components/item/Items.tsx | 15 ++--- src/app/create/page.tsx | 64 +++++++++++++++----- src/app/user/[userId]/list/[listId]/page.tsx | 3 + src/lib/types/listType.ts | 4 +- src/lib/utils/toasting.ts | 7 ++- 6 files changed, 70 insertions(+), 28 deletions(-) create mode 100644 src/app/user/[userId]/list/[listId]/page.tsx diff --git a/src/app/create/_components/CreateItem.tsx b/src/app/create/_components/CreateItem.tsx index 15c95438..9aa4c6a8 100644 --- a/src/app/create/_components/CreateItem.tsx +++ b/src/app/create/_components/CreateItem.tsx @@ -7,16 +7,17 @@ import * as styles from './CreateItem.css'; interface CreateItemProps { onBackClick: () => void; onSubmitClick: () => void; + isSubmitting: boolean; } -export default function CreateItem({ onBackClick, onSubmitClick }: CreateItemProps) { +export default function CreateItem({ onBackClick, onSubmitClick, isSubmitting }: CreateItemProps) { const { formState: { isValid }, } = useFormContext(); return (
-
+

아이템 추가 * diff --git a/src/app/create/_components/item/Items.tsx b/src/app/create/_components/item/Items.tsx index 7b751999..d629da78 100644 --- a/src/app/create/_components/item/Items.tsx +++ b/src/app/create/_components/item/Items.tsx @@ -80,12 +80,12 @@ export default function Items() { {(provided) => (
{items.map((item, index) => { - const errorMessage = (field: 'title' | 'comment' | 'link' | 'image') => + const errorMessage = (field: 'title' | 'comment' | 'link' | 'imageUrl') => (errors as FormErrors)?.items?.[index]?.[field]?.message; const titleError = errorMessage('title'); const commentError = errorMessage('comment'); const linkError = errorMessage('link'); - // const imageError = errorMessage('image'); + // const imageError = errorMessage('imageUrl'); return ( {(provided, snapshot) => ( @@ -138,6 +138,7 @@ export default function Items() {
} linkPreview={ @@ -170,12 +171,12 @@ export default function Items() { ) } imagePreview={ - watchItems[index]?.image && ( + watchItems[index]?.imageUrl !== '' && ( { - setValue(`items.${index}.image`, ''); + setValue(`items.${index}.imageUrl`, ''); }} /> ) @@ -195,7 +196,7 @@ export default function Items() { title: '', comment: '', link: '', - image: null, + imageUrl: '', }) } /> diff --git a/src/app/create/page.tsx b/src/app/create/page.tsx index 071b0e96..3fc70038 100644 --- a/src/app/create/page.tsx +++ b/src/app/create/page.tsx @@ -4,16 +4,20 @@ import { useState } from 'react'; import { FieldErrors, FormProvider, useForm } from 'react-hook-form'; import { useMutation } from '@tanstack/react-query'; -import { ItemImagesType, ListCreateType } from '@/lib/types/listType'; import CreateItem from '@/app/create/_components/CreateItem'; import CreateList from '@/app/create/_components/CreateList'; +import { ItemImagesType, ListCreateType } from '@/lib/types/listType'; +import toasting from '@/lib/utils/toasting'; import { createList } from '../_api/list/createList'; import { uploadItemImages } from '../_api/list/uploadItemImages'; +import { useRouter } from 'next/navigation'; export type FormErrors = FieldErrors; export default function CreatePage() { const [step, setStep] = useState<'list' | 'item'>('list'); + const [newListId, setNewListId] = useState(0); + const router = useRouter(); const methods = useForm({ mode: 'onChange', @@ -32,21 +36,21 @@ export default function CreatePage() { title: '', comment: '', link: '', - image: null, + imageUrl: '', }, { rank: 0, title: '', comment: '', link: '', - image: null, + imageUrl: '', }, { rank: 0, title: '', comment: '', link: '', - image: null, + imageUrl: '', }, ], }, @@ -66,44 +70,71 @@ export default function CreatePage() { }); //데이터 쪼개기 - const listData = { + const listData: ListCreateType = { ...originData, - items: originData.items.map(({ image, ...rest }) => rest), + items: originData.items.map(({ imageUrl, ...rest }) => { + return { + ...rest, + imageUrl: '', + }; + }), }; const imageData: ItemImagesType = { ownerId: originData.ownerId, listId: 0, //temp extensionRanks: originData.items - .map(({ rank, image }) => { - return { rank: rank, extension: image?.[0]?.type.split('/')[1] as 'jpg' | 'jpeg' | 'png' }; - }) - .filter(({ extension }) => extension !== null && extension !== undefined), + .filter(({ imageUrl }) => imageUrl !== '') + .map(({ rank, imageUrl }) => { + return { + rank: rank, + extension: imageUrl !== '' ? (imageUrl?.[0]?.type.split('/')[1] as 'jpg' | 'jpeg' | 'png') : '', + }; + }), }; const imageFileList: File[] = originData.items - .map(({ image }) => image?.[0] as File) - .filter((image) => image !== undefined); + .filter(({ imageUrl }) => imageUrl !== '') + .map(({ imageUrl }) => imageUrl?.[0] as File); return { listData, imageData, imageFileList }; }; - const saveImageMutation = useMutation({ mutationFn: uploadItemImages }); + const { + mutate: saveImageMutate, + isPending: isUploadingImage, + isSuccess, + data: listId, + } = useMutation({ + mutationFn: uploadItemImages, + retry: 3, + retryDelay: 1000, + onError: () => { + toasting({ type: 'error', txt: '이미지를 업로드 하는 중에 오류가 발생했어요. 다시 업로드해주세요.' }); + }, + onSettled: () => { + router.push(`/user/${formatData().listData.ownerId}/list/${newListId}`); + }, + }); - const createListMutation = useMutation({ + const { mutate: createListMutate, isPending: isCreatingList } = useMutation({ mutationFn: createList, onSuccess: (data) => { - saveImageMutation.mutate({ + setNewListId(data.listId); + saveImageMutate({ listId: data.listId, imageData: formatData().imageData, imageFileList: formatData().imageFileList, }); }, + onError: () => { + toasting({ type: 'error', txt: '리스트 생성에 실패했어요. 다시 시도해주세요.' }); + }, }); const handleSubmit = () => { const { listData } = formatData(); - createListMutation.mutate(listData); + createListMutate(listData); }; return ( @@ -121,6 +152,7 @@ export default function CreatePage() { handleStepChange('list'); }} onSubmitClick={handleSubmit} + isSubmitting={isUploadingImage || isCreatingList || isSuccess} /> )} diff --git a/src/app/user/[userId]/list/[listId]/page.tsx b/src/app/user/[userId]/list/[listId]/page.tsx new file mode 100644 index 00000000..0b2b5630 --- /dev/null +++ b/src/app/user/[userId]/list/[listId]/page.tsx @@ -0,0 +1,3 @@ +export default function ListPage() { + return
리스트페이지
; +} diff --git a/src/lib/types/listType.ts b/src/lib/types/listType.ts index 2c456f13..a2d683fa 100644 --- a/src/lib/types/listType.ts +++ b/src/lib/types/listType.ts @@ -4,7 +4,7 @@ export interface ItemCreateType { title: string; comment: string | null; link: string | null; - image?: FileList | null; + imageUrl: FileList | ''; } // 리스트 생성 타입 @@ -27,7 +27,7 @@ export interface ListIdType { // 이미지 생성 타입 export interface ItemImageType { rank: number; - extension: 'jpg' | 'jpeg' | 'png'; + extension: 'jpg' | 'jpeg' | 'png' | ''; } export interface ItemImagesType { ownerId: number; diff --git a/src/lib/utils/toasting.ts b/src/lib/utils/toasting.ts index 44cb4e01..ad7f0909 100644 --- a/src/lib/utils/toasting.ts +++ b/src/lib/utils/toasting.ts @@ -1,7 +1,12 @@ import { toast, ToastOptions } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; -function toasting({ type = 'default', txt = '' }) { +interface ToastingProps { + type: 'default' | 'success' | 'error' | 'warning'; + txt: string; +} + +function toasting({ type = 'default', txt = '' }: ToastingProps) { const toastOption: ToastOptions = { position: 'bottom-center', autoClose: 1000,