Skip to content
16 changes: 12 additions & 4 deletions src/components/Modal/DeleteModal/DeleteModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,16 @@ const DeleteModal = ({ type, id, showDeleteModal, setShowDeleteModal }: DeleteMo
deleteWineMutation.mutate(id, {
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['wines'] }); //์‚ญ์ œํ›„ ๊ด€๋ จ๋ฐ์ดํ„ฐ ๋ฐ”๋กœ ๊ฐฑ์‹ 
toast.success('์™€์ธ์ด ์„ฑ๊ณต์ ์œผ๋กœ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.');
toast.success('', {
description: '์™€์ธ์ด ์„ฑ๊ณต์ ์œผ๋กœ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.',
});
console.log('์™€์ธ ์‚ญ์ œ ์„ฑ๊ณต');
setShowDeleteModal(false);
},
onError: (error) => {
toast.error('์™€์ธ ์‚ญ์ œ๊ฐ€ ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค.');
toast.error('', {
description: '์™€์ธ ์‚ญ์ œ๊ฐ€ ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค.',
});
console.error('์™€์ธ ์‚ญ์ œ ์‹คํŒจ', error);
},
});
Expand All @@ -47,12 +51,16 @@ const DeleteModal = ({ type, id, showDeleteModal, setShowDeleteModal }: DeleteMo
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['reviews'] });
queryClient.invalidateQueries({ queryKey: ['wineDetail'] });
toast.success('๋ฆฌ๋ทฐ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.');
toast.success('', {
description: '๋ฆฌ๋ทฐ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.',
});
console.log('๋ฆฌ๋ทฐ ์‚ญ์ œ ์„ฑ๊ณต');
setShowDeleteModal(false);
},
onError: (error) => {
toast.error('๋ฆฌ๋ทฐ ์‚ญ์ œ๊ฐ€ ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค.');
toast.error('', {
description: '๋ฆฌ๋ทฐ ์‚ญ์ œ๊ฐ€ ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค.',
});
console.error('๋ฆฌ๋ทฐ ์‚ญ์ œ ์‹คํŒจ', error);
},
});
Expand Down
22 changes: 19 additions & 3 deletions src/components/Modal/ReviewModal/AddReviewModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ const aromaMap: Record<string, string> = {
์˜คํฌ: 'OAK',
๋ฐ”๋‹๋ผ: 'VANILLA',
ํ›„์ถ”: 'PEPPER',
์ œ๋นต: 'BAKERY',
์ œ๋นต: 'BAKING',
ํ’€: 'GRASS',
์‚ฌ๊ณผ: 'APPLE',
๋ณต์ˆญ์•„: 'PEACH',
Expand Down Expand Up @@ -115,15 +115,19 @@ const AddReviewModal = ({ wineId, wineName }: { wineId: number; wineName: string
//mutation function
mutationFn: postReview, //์‹ค์ œ ์„œ๋ฒ„์— ๋ฆฌ๋ทฐ๋ฅผ ๋ณด๋‚ด๋Š” ์—ญํ• 
onSuccess: (data) => {
toast.success('๋ฆฌ๋ทฐ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ๋“ฑ๋ก๋˜์—ˆ์Šต๋‹ˆ๋‹ค.');
toast.success('', {
description: '๋ฆฌ๋ทฐ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ๋“ฑ๋ก๋˜์—ˆ์Šต๋‹ˆ๋‹ค.',
});
console.log('๋ฆฌ๋ทฐ ๋“ฑ๋ก ์„ฑ๊ณต', data);
queryClient.invalidateQueries({ queryKey: ['reviews'] });
queryClient.invalidateQueries({ queryKey: ['wineDetail'] });
reset();
setShowRegisterModal(false);
},
onError: (error) => {
toast.error('๋ฆฌ๋ทฐ ๋“ฑ๋ก์ด ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค.');
toast.error('', {
description: '๋ฆฌ๋ทฐ ๋“ฑ๋ก์ด ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค.',
});
console.log('๋ฆฌ๋ทฐ ๋“ฑ๋ก ์‹คํŒจ', error);
},
});
Expand Down Expand Up @@ -174,6 +178,12 @@ const AddReviewModal = ({ wineId, wineName }: { wineId: number; wineName: string
};
////

const content = watch('content');
const aromaList = watch('aroma');
const rating = watch('rating');

const isFormValid = rating > 0 && content.trim().length > 0 && aromaList.length > 0;

const renderButton = (
<Button
onClick={handleSubmit(onSubmit)}
Expand All @@ -182,6 +192,8 @@ const AddReviewModal = ({ wineId, wineName }: { wineId: number; wineName: string
size='xl'
width='full'
fontSize='lg'
disabled={!isFormValid}
className={!isFormValid ? 'cursor-not-allowed' : ''}
>
๋ฆฌ๋ทฐ ๋‚จ๊ธฐ๊ธฐ
</Button>
Expand Down Expand Up @@ -212,6 +224,10 @@ const AddReviewModal = ({ wineId, wineName }: { wineId: number; wineName: string
id='content'
{...register('content', {
required: '๋ฆฌ๋ทฐ ๋‚ด์šฉ์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”.',
maxLength: {
value: 500,
message: '์ตœ๋Œ€ 500์ž๊นŒ์ง€ ์ž…๋ ฅ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.',
},
onChange: () => clearErrors('content'),
})}
placeholder='ํ›„๊ธฐ๋ฅผ ์ž‘์„ฑํ•ด ์ฃผ์„ธ์š”'
Expand Down
22 changes: 19 additions & 3 deletions src/components/Modal/ReviewModal/EditReviewModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export const aromaMap: Record<string, string> = {
์˜คํฌ: 'OAK',
๋ฐ”๋‹๋ผ: 'VANILLA',
ํ›„์ถ”: 'PEPPER',
์ œ๋นต: 'BAKERY',
์ œ๋นต: 'BAKING',
ํ’€: 'GRASS',
์‚ฌ๊ณผ: 'APPLE',
๋ณต์ˆญ์•„: 'PEACH',
Expand Down Expand Up @@ -99,14 +99,18 @@ const EditReviewModal = ({
mutationFn: updateReview,
throwOnError: true,
onSuccess: () => {
toast.success('๋ฆฌ๋ทฐ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ˆ˜์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค.');
toast.success('', {
description: '๋ฆฌ๋ทฐ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ˆ˜์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค.',
});
console.log('๋ฆฌ๋ทฐ ์ˆ˜์ • ์™„๋ฃŒ');
queryClient.invalidateQueries({ queryKey: ['reviews'] });
queryClient.invalidateQueries({ queryKey: ['wineDetail'] });
setShowEditModal(false);
},
onError: (error) => {
toast.error('๋ฆฌ๋ทฐ ์ˆ˜์ •์ด ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค.');
toast.error('', {
description: '๋ฆฌ๋ทฐ ์ˆ˜์ •์ด ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค.',
});
console.log('๋ฆฌ๋ทฐ ์ˆ˜์ • ์‹คํŒจ', error);
},
});
Expand Down Expand Up @@ -176,6 +180,12 @@ const EditReviewModal = ({
}
};

const content = watch('content');
const aromaList = watch('aroma');
const rating = watch('rating');

const isFormValid = rating > 0 && content.trim().length > 0 && aromaList.length > 0;

const renderButton = (
<Button
onClick={handleSubmit(onSubmit)}
Expand All @@ -184,6 +194,8 @@ const EditReviewModal = ({
size='xl'
width='full'
fontSize='lg'
disabled={!isFormValid}
className={!isFormValid ? 'cursor-not-allowed' : ''}
>
์ˆ˜์ • ์™„๋ฃŒ
</Button>
Expand Down Expand Up @@ -215,6 +227,10 @@ const EditReviewModal = ({
id='content'
{...register('content', {
required: '๋ฆฌ๋ทฐ ๋‚ด์šฉ์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”.',
maxLength: {
value: 500,
message: '์ตœ๋Œ€ 500์ž๊นŒ์ง€ ์ž…๋ ฅ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.',
},
onChange: () => clearErrors('content'),
})}
placeholder='ํ›„๊ธฐ๋ฅผ ์ž‘์„ฑํ•ด ์ฃผ์„ธ์š”'
Expand Down
88 changes: 59 additions & 29 deletions src/components/Modal/WineModal/AddWineModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import CameraIcon from '@/assets/camera.svg';
import DropdownIcon from '@/assets/dropdowntriangle.svg';
import BasicBottomSheet from '@/components/common/BottomSheet/BasicBottomSheet';
import { useMediaQuery } from '@/hooks/useMediaQuery';
import { getTrimmedHandlers } from '@/lib/inputCheck';
import { getCommaNumberHandlers } from '@/lib/inputNumberCheck';
import { renameFileIfNeeded } from '@/lib/renameFile';

import SelectDropdown from '../../common/dropdown/SelectDropdown';
Expand All @@ -17,7 +19,7 @@ import BasicModal from '../../common/Modal/BasicModal';
import { Button } from '../../ui/button';
interface WineForm {
wineName: string;
winePrice: number;
winePrice: string;
wineOrigin: string;
wineImage: FileList;
wineType: string;
Expand Down Expand Up @@ -64,13 +66,14 @@ const AddWineModal = ({ showRegisterModal, setShowRegisterModal }: AddWineModalP
setValue,
setError,
reset,
watch,
} = useForm<WineForm>({
mode: 'onBlur',
});
const resetForm = () => {
reset({
wineName: '',
winePrice: NaN,
winePrice: '',
wineOrigin: '',
wineImage: {} as FileList,
wineType: '',
Expand All @@ -79,35 +82,32 @@ const AddWineModal = ({ showRegisterModal, setShowRegisterModal }: AddWineModalP
setPreviewImage(null);
if (fileInputRef.current) fileInputRef.current.value = '';
};
const handlePostWine = async (form: WineForm) => {
const file = form.wineImage[0];
const newFileName = file.name.replace(/\s/g, '-');
const newFile = new File([file], newFileName, { type: file.type });
const imageUrl = await uploadImage(newFile);
const requestData: PostWineRequest = {
name: form.wineName,
region: form.wineOrigin,
image: imageUrl,
price: Number(form.winePrice),
type: form.wineType.toUpperCase() as 'RED' | 'WHITE' | 'SPARKLING',
};
return postWine(requestData);

const handlePostWine = async (data: PostWineRequest) => {
return postWine(data);
};
const postWineMutation = useMutation({
mutationFn: handlePostWine,
onSuccess: () => {
toast.success('์™€์ธ์ด ์„ฑ๊ณต์ ์œผ๋กœ ๋“ฑ๋ก๋˜์—ˆ์Šต๋‹ˆ๋‹ค.');
toast.success('', {
description: '์™€์ธ์ด ์„ฑ๊ณต์ ์œผ๋กœ ๋“ฑ๋ก๋˜์—ˆ์Šต๋‹ˆ๋‹ค.',
});
console.log('์™€์ธ ๋“ฑ๋ก ์„ฑ๊ณต');
resetForm();
setShowRegisterModal(false);
queryClient.invalidateQueries({ queryKey: ['wines'] });
},
onError: (error) => {
toast.error('์™€์ธ ๋“ฑ๋ก์ด ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค.');
toast.error('', {
description: '์™€์ธ ๋“ฑ๋ก์— ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค.',
});
console.log('์™€์ธ ๋“ฑ๋ก ์‹คํŒจ', error);
},
});

//WineForm์˜ price๋ฅผ string์œผ๋กœ ํ•ด์„œ ์‰ผํ‘œ ๊ฐ€๋Šฅ ํ›„ >>์„œ๋ฒ„์— ์ €์žฅํ• ๋•Œ๋Š” ์‰ผํ‘œ ์ œ๊ฑฐ ํ›„ ์ˆซ์ž๋กœ ๋ณ€ํ™˜์„ ์œ„ํ•ด์„œ
const onSubmit = async (form: WineForm) => {
////์ด๋ฏธ์ง€ ํ™•์žฅ์ž ๊ฒ€์‚ฌ ๋ฐ ์ž˜๋ชป๋œ ํ™•์žฅ์ž ์—…๋กœ๋“œ ๋ฐฉ์ง€////
const file = form.wineImage?.[0];

// 1. ์ด๋ฏธ์ง€ ์—†์„ ๋•Œ ๋ง‰๊ธฐ (์„ ํƒ์‚ฌํ•ญ: ์ด๋ฏธ react-hook-form required๋กœ ๋ง‰๊ณ  ์žˆ์Œ)
Expand All @@ -118,7 +118,6 @@ const AddWineModal = ({ showRegisterModal, setShowRegisterModal }: AddWineModalP
});
return;
}

// 2. ํ™•์žฅ์ž ์ œํ•œ
if (!ALLOWED_IMAGE_TYPES.includes(file.type)) {
setError('wineImage', {
Expand All @@ -127,13 +126,20 @@ const AddWineModal = ({ showRegisterModal, setShowRegisterModal }: AddWineModalP
});
return;
}
//// ////

// 3. ํŒŒ์ผ ์ •๊ทœํ™” ๋ฐ ์ œ์ถœ
const renamedFile = renameFileIfNeeded(file);
postWineMutation.mutate({
...form,
wineImage: [renamedFile] as unknown as FileList,
});
const imageUrl = await uploadImage(file); //์ด๋ฏธ์ง€ ์—…๋กœ๋“œํ•ด์„œ URL์–ป๊ณ 

const numberPrice = Number(form.winePrice.replace(/,/g, '')); //๊ฐ€๊ฒฉ ์‰ผํ‘œ ์ œ๊ฑฐ ํ›„ ์ˆซ์ž๋กœ ๋ณ€ํ™˜

const requestData: PostWineRequest = {
name: form.wineName,
region: form.wineOrigin,
image: imageUrl,
price: numberPrice,
type: form.wineType.toUpperCase() as 'RED' | 'WHITE' | 'SPARKLING',
};
postWineMutation.mutate(requestData);
};
const categoryOptions = [
{ label: 'Red', value: 'Red' },
Expand All @@ -157,7 +163,7 @@ const AddWineModal = ({ showRegisterModal, setShowRegisterModal }: AddWineModalP
<Input
{...register('wineName', {
required: '์™€์ธ ์ด๋ฆ„์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”.',
onChange: () => clearErrors('wineName'),
...getTrimmedHandlers<WineForm>('wineName', setValue, clearErrors), //์ •๊ทœ์‹ ๊ฒ€์‚ฌ ํ•จ์ˆ˜
})}
errorMessage={errors.wineName?.message}
id='wineName'
Expand All @@ -170,11 +176,16 @@ const AddWineModal = ({ showRegisterModal, setShowRegisterModal }: AddWineModalP
<Input
{...register('winePrice', {
required: '๊ฐ€๊ฒฉ์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”.',
onChange: () => clearErrors('winePrice'),
pattern: {
value: /^[1-9][0-9]{0,9}(,[0-9]{3})*$/, // ์ด 10์ž๋ฆฌ, 1~9๋กœ ์‹œ์ž‘
message: '์ˆซ์ž๋งŒ ์ž…๋ ฅ ๊ฐ€๋Šฅํ•˜๋ฉฐ, 0์œผ๋กœ ์‹œ์ž‘ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.',
},
...getCommaNumberHandlers<WineForm>('winePrice', setValue, clearErrors), //์ •๊ทœ์‹ ๊ฒ€์‚ฌ ํ•จ์ˆ˜
})}
errorMessage={errors.winePrice?.message}
id='winePrice'
type='number'
type='text'
inputMode='numeric' //๋ชจ๋ฐ”์ผ์‹œ ์ˆซ์žํŒจ๋“œ
variant='name'
placeholder='๊ฐ€๊ฒฉ ์ž…๋ ฅ'
className='[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none custom-text-md-regular md:custom-text-lg-regular'
Expand All @@ -183,7 +194,7 @@ const AddWineModal = ({ showRegisterModal, setShowRegisterModal }: AddWineModalP
<Input
{...register('wineOrigin', {
required: '์›์‚ฐ์ง€๋ฅผ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”.',
onChange: () => clearErrors('wineOrigin'),
...getTrimmedHandlers<WineForm>('wineOrigin', setValue, clearErrors), //์ •๊ทœ์‹ ๊ฒ€์‚ฌ ํ•จ์ˆ˜
})}
errorMessage={errors.wineOrigin?.message}
id='wineOrigin'
Expand Down Expand Up @@ -266,6 +277,20 @@ const AddWineModal = ({ showRegisterModal, setShowRegisterModal }: AddWineModalP
</div>
</form>
);

const watchedName = watch('wineName');
const watchedPrice = watch('winePrice');
const watchedOrigin = watch('wineOrigin');
const watchedType = watch('wineType');
const watchedImage = watch('wineImage');

const isFormValid =
watchedName?.trim() &&
watchedPrice &&
watchedOrigin?.trim() &&
watchedType?.trim() &&
watchedImage?.length > 0;

const renderButton = (
<div className='flex gap-2 w-full'>
<Button
Expand All @@ -288,7 +313,12 @@ const AddWineModal = ({ showRegisterModal, setShowRegisterModal }: AddWineModalP
size='xl'
fontSize='lg'
width='full'
className='w-full md:w-[294px] lg:w-[294px]'
className={
!isFormValid
? 'cursor-not-allowed w-full md:w-[294px] lg:w-[294px]'
: 'w-full md:w-[294px] lg:w-[294px]'
}
disabled={!isFormValid}
>
์™€์ธ ๋“ฑ๋กํ•˜๊ธฐ
</Button>
Expand Down
Loading