diff --git a/src/app/(with-header)/page.tsx b/src/app/(with-header)/page.tsx index 6a01858..c2cb4bf 100644 --- a/src/app/(with-header)/page.tsx +++ b/src/app/(with-header)/page.tsx @@ -1,5 +1,4 @@ import Image from 'next/image'; -import { Metadata } from 'next'; import Button from '@/components/Button'; import bannerDesktop from '@/assets/images/banner_desktop.png'; import bannerTablet from '@/assets/images/banner_tablet.png'; @@ -11,17 +10,6 @@ import section2Small from '@/assets/images/section2_sm.png'; import section3Large from '@/assets/images/section3_lg.png'; import section3Small from '@/assets/images/section3_sm.png'; -export const metadata: Metadata = { - metadataBase: new URL('https://wine-lab.vercel.app/'), - title: 'WINE - 나만의 와인 창고', - description: '한 곳에서 관리하는 나만의 와인 창고', - openGraph: { - title: 'WINE - 나만의 와인 창고', - description: '한 곳에서 관리하는 나만의 와인 창고', - images: ['/thumbnail.png'], - }, -}; - export default function Home() { return (
diff --git a/src/app/(with-header)/wines/_components/FilterPrice.tsx b/src/app/(with-header)/wines/_components/FilterPrice.tsx index fc6a58f..21ef3f2 100644 --- a/src/app/(with-header)/wines/_components/FilterPrice.tsx +++ b/src/app/(with-header)/wines/_components/FilterPrice.tsx @@ -5,9 +5,10 @@ const MAX_PRICE = 2000000; type FilterPriceProps = { priceRange: [number, number]; onPriceChange: (values: number[]) => void; + onFinalChange: (values: number[]) => void; }; -const FilterPrice = ({ priceRange, onPriceChange }: FilterPriceProps) => { +const FilterPrice = ({ priceRange, onPriceChange, onFinalChange }: FilterPriceProps) => { return (
PRICE
@@ -23,6 +24,7 @@ const FilterPrice = ({ priceRange, onPriceChange }: FilterPriceProps) => { max={MAX_PRICE} values={priceRange} onChange={onPriceChange} + onFinalChange={onFinalChange} renderTrack={({ props, children }) => (
{ {children}
)} - renderThumb={({ props, index }) =>
} + renderThumb={({ props, index }) => { + return ( +
+
+
+ ); + }} />
diff --git a/src/app/(with-header)/wines/_components/FilterRating.tsx b/src/app/(with-header)/wines/_components/FilterRating.tsx index 073a193..a6fcfdf 100644 --- a/src/app/(with-header)/wines/_components/FilterRating.tsx +++ b/src/app/(with-header)/wines/_components/FilterRating.tsx @@ -20,7 +20,7 @@ const FilterRating = ({ selectedRating, onRatingChange }: FilterRatingProps) => onRatingChange(item.value)} />
- {item.label} + {item.label} ))}
diff --git a/src/app/(with-header)/wines/_components/FilterTypes.tsx b/src/app/(with-header)/wines/_components/FilterTypes.tsx index 15ea19d..e4d6cd4 100644 --- a/src/app/(with-header)/wines/_components/FilterTypes.tsx +++ b/src/app/(with-header)/wines/_components/FilterTypes.tsx @@ -16,7 +16,7 @@ const FilterTypes = ({ selectedType, onTypeChange }: FilterTypesProps) => { {['Red', 'White', 'Sparkling'].map((type) => (
diff --git a/src/app/(with-header)/wines/_components/skeleton/RecommendWineCardSkeleton.tsx b/src/app/(with-header)/wines/_components/skeleton/RecommendWineCardSkeleton.tsx index 7037dfc..db178ed 100644 --- a/src/app/(with-header)/wines/_components/skeleton/RecommendWineCardSkeleton.tsx +++ b/src/app/(with-header)/wines/_components/skeleton/RecommendWineCardSkeleton.tsx @@ -1,11 +1,11 @@ export default function RecommendWineCardSkeleton() { return (
-
+
-
-
-
+
+
+
); diff --git a/src/app/(with-header)/wines/_components/skeleton/WineCardSkeleton.tsx b/src/app/(with-header)/wines/_components/skeleton/WineCardSkeleton.tsx new file mode 100644 index 0000000..40ce495 --- /dev/null +++ b/src/app/(with-header)/wines/_components/skeleton/WineCardSkeleton.tsx @@ -0,0 +1,31 @@ +export default function WineCardSkeleton() { + return ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ); +} diff --git a/src/app/(with-header)/wines/_components/skeleton/WineListSkeleton.tsx b/src/app/(with-header)/wines/_components/skeleton/WineListSkeleton.tsx new file mode 100644 index 0000000..9204ce5 --- /dev/null +++ b/src/app/(with-header)/wines/_components/skeleton/WineListSkeleton.tsx @@ -0,0 +1,11 @@ +import WineCardSkeleton from './WineCardSkeleton'; + +export default function WineListSkeleton({ count }: { count: number }) { + return ( +
+ {new Array(count).fill(0).map((_, idx) => ( + + ))} +
+ ); +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 7db81b3..972fcb3 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,7 +1,8 @@ import localFont from 'next/font/local'; +import { Metadata } from 'next'; import { AuthProvider } from '@/contexts/AuthProvider'; -import './globals.css'; import { ToastContainer, Slide } from 'react-toastify'; +import './globals.css'; const pretendard = localFont({ src: '../fonts/PretendardVariable.woff2', @@ -9,6 +10,17 @@ const pretendard = localFont({ variable: '--font-pretendard', }); +export const metadata: Metadata = { + metadataBase: new URL('https://wine-lab.vercel.app/'), + title: 'WINE - 나만의 와인 창고', + description: '한 곳에서 관리하는 나만의 와인 창고', + openGraph: { + title: 'WINE - 나만의 와인 창고', + description: '한 곳에서 관리하는 나만의 와인 창고', + images: ['/thumbnail.png'], + }, +}; + export default function RootLayout({ children, }: Readonly<{ diff --git a/src/app/signin/_components/LoginForm.tsx b/src/app/signin/_components/LoginForm.tsx index a0f2a6b..534233d 100644 --- a/src/app/signin/_components/LoginForm.tsx +++ b/src/app/signin/_components/LoginForm.tsx @@ -65,7 +65,7 @@ export default function LoginForm() { }; return ( -
+
- +
-
); } diff --git a/src/components/modal/PostWineModal.tsx b/src/components/modal/PostWineModal.tsx index a0d3b1c..af33405 100644 --- a/src/components/modal/PostWineModal.tsx +++ b/src/components/modal/PostWineModal.tsx @@ -8,6 +8,7 @@ import { fetchWithAuth } from '@/lib/auth'; import Modal from '@/components/modal/Modal'; import Dropdown from '../Dropdown'; import Button from '../Button'; +import { toast } from 'react-toastify'; import camera from '@/assets/icons/photo.svg'; interface FormValues { @@ -25,7 +26,12 @@ export default function PostWineModal() { const [preview, setPreview] = useState(null); const router = useRouter(); - const { register, handleSubmit, setValue } = useForm(); + const { register, handleSubmit, setValue, watch, reset } = useForm(); + const name = watch('name'); + const region = watch('region'); + const image = watch('image'); + const price = watch('price'); + const type = watch('type'); const options = [ { value: () => setValue('type', 'RED'), label: 'Red' }, @@ -38,33 +44,31 @@ export default function PostWineModal() { }; const closeModal = () => { + reset(); + setValue('type', ''); + setPreview(null); setIsOpen(false); }; const handlePostWine: SubmitHandler = async (data) => { - const { name, region, image, price, type } = data; - - if (!name || !region || !image || !price || !type) { - alert('모든 정보를 입력해 주세요.'); - return; - } - try { const response = await fetchWithAuth(`${process.env.NEXT_PUBLIC_BASE_URL}/wines`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ name, region, image, price: Number(price), type }), + body: JSON.stringify({ ...data, price: Number(data.price) }), }); - if (!response?.ok || response === null) { + if (!response?.ok) { throw new Error('와인 등록에 실패했습니다'); } const body = await response.json(); + toast.success('와인이 등록되었습니다.'); router.push(`/wines/${body.id}`); } catch (error) { - console.error('와인 등록 에러:', error); - console.log(data); + console.error('와인 등록 실패:', error); + toast.error('와인 등록에 실패했습니다.'); + closeModal(); } }; @@ -72,26 +76,33 @@ export default function PostWineModal() { const { image } = data; const formData = new FormData(); formData.append('image', image[0]); + try { - const response = await fetchWithAuth(`${process.env.NEXT_PUBLIC_BASE_URL}/images/upload`, { method: 'POST', body: formData }); + const response = await fetchWithAuth(`${process.env.NEXT_PUBLIC_BASE_URL}/images/upload`, { + method: 'POST', + body: formData, + }); - if (!response?.ok || response === null) { - throw new Error('Failed to upload image'); + if (!response?.ok) { + throw new Error('이미지 업로드에 실패했습니다'); } const uploadResult = await response.json(); return uploadResult.url; } catch (error) { - console.log(error); + console.error('이미지 업로드 실패:', error); } } const handleFileChange = async (event: React.ChangeEvent) => { - const image = event.target.files; - if (!image) return; - const imageUrl = await postImageApi({ image }); - setValue('image', `${imageUrl}`); - setPreview(imageUrl); + const files = event.target.files; + if (!files || files.length === 0) return; + + const imageUrl = await postImageApi({ image: files }); + if (imageUrl) { + setValue('image', imageUrl); + setPreview(imageUrl); + } }; return ( @@ -155,14 +166,7 @@ export default function PostWineModal() { - { - option.value?.(); - }} - placeholder='Red' - changeButton - /> + option.value?.()} placeholder='Red' changeButton />
@@ -174,7 +178,7 @@ export default function PostWineModal() { className='relative flex h-[140px] w-[140px] cursor-pointer items-center justify-center overflow-hidden rounded-2xl border border-gray-300 mobile:h-[120px] mobile:w-[120px]' > {preview ? ( - preview + preview ) : ( camera )} @@ -185,7 +189,13 @@ export default function PostWineModal() {