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

HOTFIX: 리스트생성 imageUrl 미삽입으로 인한 에러 해결 및 기타 수정 #18

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
2 changes: 1 addition & 1 deletion public/icons/dnd.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions src/app/create/_components/CreateItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<div>
<Header onBackClick={onBackClick} isSubmitActive={isValid} onSubmitClick={onSubmitClick} />
<Header onBackClick={onBackClick} isSubmitActive={isValid && !isSubmitting} onSubmitClick={onSubmitClick} />
<div className={styles.article}>
<h3 className={styles.label}>
아이템 추가 <span className={styles.required}>*</span>
Expand Down
8 changes: 7 additions & 1 deletion src/app/create/_components/item/ItemLayout.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,21 @@ import { style } from '@vanilla-extract/css';
export const itemHeader = style({
width: '100%',

flexGrow: 0,

display: 'flex',
alignItems: 'center',
gap: '12px',

overflow: 'hidden',
});

export const headerIcon = style({
flexShrink: '0',
});

export const rankAndTitle = style({
width: '100%',
flexGrow: 1,

display: 'flex',
gap: '8px',
Expand Down
4 changes: 2 additions & 2 deletions src/app/create/_components/item/ItemLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ export default function ItemLayout({
return (
<>
<div className={styles.itemHeader}>
<DndIcon alt="드래그앤드롭" />
<DndIcon width="18" height="18" alt="드래그앤드롭" className={styles.headerIcon} />
<div className={styles.rankAndTitle}>
<Label colorType={index === 0 ? 'blue' : 'skyblue'}>{`${index + 1}위`}</Label>
{titleInput}
</div>
{itemLength > 3 && (
<button onClick={handleDeleteItem}>
<ClearGrayIcon alt="아이템 삭제" />
<ClearGrayIcon alt="아이템 삭제" className={styles.headerIcon} />
</button>
)}
</div>
Expand Down
4 changes: 3 additions & 1 deletion src/app/create/_components/item/Items.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ export const draggingItem = style([
]);

export const title = style({
flexGrow: 1,

//body1
fontSize: '1.6rem',
fontWeight: '400',
Expand All @@ -57,7 +59,7 @@ export const comment = style({
width: '100%',
resize: 'none',

flexGrow: '1',
flexGrow: 1,

//body2
fontSize: '1.5rem',
Expand Down
15 changes: 8 additions & 7 deletions src/app/create/_components/item/Items.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,12 @@ export default function Items() {
{(provided) => (
<div className={styles.itemsContainer} ref={provided.innerRef} {...provided.droppableProps}>
{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 (
<Draggable key={item.id} draggableId={item.id} index={index}>
{(provided, snapshot) => (
Expand Down Expand Up @@ -138,6 +138,7 @@ export default function Items() {
<div className={styles.linkModalChildren}>
<input
className={styles.linkInput}
type="url"
placeholder={itemPlaceholder.link}
autoComplete="off"
{...register(`items.${index}.link`, itemLinkRules)}
Expand All @@ -154,7 +155,7 @@ export default function Items() {
type="file"
accept=".jpg, .jpeg, .png"
id={`${index}-image`}
{...register(`items.${index}.image`)}
{...register(`items.${index}.imageUrl`)}
/>
}
linkPreview={
Expand All @@ -170,12 +171,12 @@ export default function Items() {
)
}
imagePreview={
watchItems[index]?.image && (
watchItems[index]?.imageUrl !== '' && (
<Preview
type="image"
imageFile={watchItems[index]?.image?.[0]}
imageFile={watchItems[index]?.imageUrl?.[0]}
handleClearButtonClick={() => {
setValue(`items.${index}.image`, '');
setValue(`items.${index}.imageUrl`, '');
}}
/>
)
Expand All @@ -195,7 +196,7 @@ export default function Items() {
title: '',
comment: '',
link: '',
image: null,
imageUrl: '',
})
}
/>
Expand Down
64 changes: 48 additions & 16 deletions src/app/create/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<ListCreateType>;

export default function CreatePage() {
const [step, setStep] = useState<'list' | 'item'>('list');
const [newListId, setNewListId] = useState(0);
const router = useRouter();

const methods = useForm<ListCreateType>({
mode: 'onChange',
Expand All @@ -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: '',
},
],
},
Expand All @@ -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 (
Expand All @@ -121,6 +152,7 @@ export default function CreatePage() {
handleStepChange('list');
}}
onSubmitClick={handleSubmit}
isSubmitting={isUploadingImage || isCreatingList || isSuccess}
/>
)}
</FormProvider>
Expand Down
3 changes: 3 additions & 0 deletions src/app/user/[userId]/list/[listId]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function ListPage() {
return <div>리스트페이지</div>;
}
4 changes: 2 additions & 2 deletions src/lib/types/listType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export interface ItemCreateType {
title: string;
comment: string | null;
link: string | null;
image?: FileList | null;
imageUrl: FileList | '';
}

// 리스트 생성 타입
Expand All @@ -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;
Expand Down
7 changes: 6 additions & 1 deletion src/lib/utils/toasting.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
Loading