diff --git a/public/images/image1.svg b/public/images/image1.svg deleted file mode 100644 index 92a835f4..00000000 --- a/public/images/image1.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/public/images/image2.svg b/public/images/image2.svg deleted file mode 100644 index 3985ef9d..00000000 --- a/public/images/image2.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/public/images/image3.svg b/public/images/image3.svg deleted file mode 100644 index beebe1c6..00000000 --- a/public/images/image3.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/public/images/image4.svg b/public/images/image4.svg deleted file mode 100644 index 1777f343..00000000 --- a/public/images/image4.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/components/Modal/WineModal/AddWineModal.tsx b/src/components/Modal/WineModal/AddWineModal.tsx index dcc79693..3cae5613 100644 --- a/src/components/Modal/WineModal/AddWineModal.tsx +++ b/src/components/Modal/WineModal/AddWineModal.tsx @@ -15,7 +15,6 @@ import SelectDropdown from '../../common/dropdown/SelectDropdown'; import Input from '../../common/Input'; import BasicModal from '../../common/Modal/BasicModal'; import { Button } from '../../ui/button'; - interface WineForm { wineName: string; winePrice: number; @@ -23,23 +22,19 @@ interface WineForm { wineImage: FileList; wineType: string; } - interface AddWineModalProps { showRegisterModal: boolean; setShowRegisterModal: (state: boolean) => void; } - const AddWineModal = ({ showRegisterModal, setShowRegisterModal }: AddWineModalProps) => { const [category, setCategory] = useState(''); const fileInputRef = useRef(null); const [previewImage, setPreviewImage] = useState(null); const queryClient = useQueryClient(); const isDesktop = useMediaQuery('(min-width: 640px)'); - const triggerFileSelect = () => { fileInputRef.current?.click(); }; - const handleImageChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { @@ -50,7 +45,6 @@ const AddWineModal = ({ showRegisterModal, setShowRegisterModal }: AddWineModalP setPreviewImage(null); } }; - const { register, handleSubmit, @@ -62,7 +56,6 @@ const AddWineModal = ({ showRegisterModal, setShowRegisterModal }: AddWineModalP } = useForm({ mode: 'onBlur', }); - const resetForm = () => { reset({ wineName: '', @@ -75,10 +68,11 @@ const AddWineModal = ({ showRegisterModal, setShowRegisterModal }: AddWineModalP setPreviewImage(null); if (fileInputRef.current) fileInputRef.current.value = ''; }; - const handlePostWine = async (form: WineForm) => { const file = form.wineImage[0]; - const imageUrl = await uploadImage(file); + 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, @@ -88,7 +82,6 @@ const AddWineModal = ({ showRegisterModal, setShowRegisterModal }: AddWineModalP }; return postWine(requestData); }; - const postWineMutation = useMutation({ mutationFn: handlePostWine, onSuccess: () => { @@ -103,7 +96,6 @@ const AddWineModal = ({ showRegisterModal, setShowRegisterModal }: AddWineModalP console.log('와인 등록 실패', error); }, }); - const onSubmit = async (form: WineForm) => { let file = form.wineImage[0]; file = renameFileIfNeeded(file); //이미지파일 이름 정규화 @@ -112,16 +104,12 @@ const AddWineModal = ({ showRegisterModal, setShowRegisterModal }: AddWineModalP wineImage: [file] as unknown as FileList, }); }; - const categoryOptions = [ { label: 'Red', value: 'Red' }, { label: 'White', value: 'White' }, { label: 'Sparkling', value: 'Sparkling' }, ]; - const selectedCategoryLabel = categoryOptions.find((opt) => opt.value === category)?.label; - - //모달창 끄면 리셋되게 const closeModalReset = (isOpen: boolean) => { setShowRegisterModal(isOpen); if (!isOpen) { @@ -130,11 +118,8 @@ const AddWineModal = ({ showRegisterModal, setShowRegisterModal }: AddWineModalP }, 50); } }; - //// - const renderForm = () => (
- {/* 이름 */}

와인 이름

@@ -150,7 +135,6 @@ const AddWineModal = ({ showRegisterModal, setShowRegisterModal }: AddWineModalP placeholder='와인 이름 입력' className='custom-text-md-regular md:custom-text-lg-regular' /> - {/* 가격 */}

가격

- {/* 원산지 */}

원산지

- {/* 타입 */}

타입

{errors.wineType.message}

- )}{' '} + )} - {/* 사진 */}

와인 사진

{previewImage ? ( - // eslint-disable-next-line @next/next/no-img-element 미리보기 ) : (
@@ -255,7 +235,6 @@ const AddWineModal = ({ showRegisterModal, setShowRegisterModal }: AddWineModalP
); - const renderButton = (
); - return isDesktop ? ( ); }; - export default AddWineModal; diff --git a/src/components/common/Filter/WineTypeFilter.tsx b/src/components/common/Filter/WineTypeFilter.tsx index a1e56256..6d895ef9 100644 --- a/src/components/common/Filter/WineTypeFilter.tsx +++ b/src/components/common/Filter/WineTypeFilter.tsx @@ -19,7 +19,7 @@ const WineTypeFilter = ({ }: WineTypeFilterProps) => { const { type, setType, minPrice, maxPrice, setPriceRange, rating, setRating } = useFilterStore(); - const wineTypeOptions: WineType[] = ['Red', 'White', 'Sparkling']; + const wineTypeOptions: WineType[] = ['RED', 'WHITE', 'SPARKLING']; const priceRange: [number, number] = [minPrice, maxPrice]; const borderClass = 'border-b border-gray-100'; @@ -65,19 +65,19 @@ const WineTypeFilter = ({
- +
- +
- +
- +
diff --git a/src/components/common/winelist/WineCard.tsx b/src/components/common/winelist/WineCard.tsx index 01b28aca..50a65b1e 100644 --- a/src/components/common/winelist/WineCard.tsx +++ b/src/components/common/winelist/WineCard.tsx @@ -1,5 +1,3 @@ -import React from 'react'; - import Link from 'next/link'; import StarIcon from '@/assets/icons/star.svg'; @@ -24,7 +22,6 @@ export default function WineCard({ id, image, name, rating }: WineCardProps) { 'md:w-[232px] md:h-[185px]', )} > - {/* 왼쪽: 이미지 카드 */}
- {/* 오른쪽: 평점 + 별점 + 이름 */}
{rating.toFixed(1)} diff --git a/src/components/common/winelist/WineListCard.tsx b/src/components/common/winelist/WineListCard.tsx index d5bf850f..52621467 100644 --- a/src/components/common/winelist/WineListCard.tsx +++ b/src/components/common/winelist/WineListCard.tsx @@ -9,6 +9,7 @@ import { Button } from '@/components/ui/button'; import { useInfiniteScroll } from '@/hooks/useInfiniteScroll'; import { useWineListQuery } from '@/hooks/useWineListQuery'; import { cn } from '@/lib/utils'; +import { GetWinesResponse } from '@/types/wineListType'; export default function WineListCard() { const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, isError } = @@ -27,8 +28,10 @@ export default function WineListCard() { if (isLoading) return

불러오는 중...

; if (isError || !data) return

와인 데이터를 불러올 수 없습니다.

; - /* 전체 와인 리스트 조합 */ - const wineList = data.pages.flatMap((page) => page.list); + const wineList = data.pages.flatMap( + (page) => + (page as GetWinesResponse)?.list?.filter((wine) => !wine.image.includes('example.com')) ?? [], + ); return (
@@ -68,7 +71,7 @@ export default function WineListCard() {
- {wine.rating.toFixed(1)} + {wine.avgRating?.toFixed(1)}
@@ -76,14 +79,14 @@ export default function WineListCard() { = i + 1 ? 'fill-purpleDark' : 'fill-gray-300', + wine.avgRating >= i + 1 ? 'fill-purpleDark' : 'fill-gray-300', 'w-3 h-3', )} /> ))}
- 47개의 후기 + {wine.reviewCount}개의 후기
@@ -98,7 +101,7 @@ export default function WineListCard() {
- {wine.rating.toFixed(1)} + {wine.avgRating?.toFixed(1)}
@@ -106,14 +109,14 @@ export default function WineListCard() { = i + 1 ? 'fill-purpleDark' : 'fill-gray-300', + wine.avgRating >= i + 1 ? 'fill-purpleDark' : 'fill-gray-300', 'w-[16px] h-[16px]', )} /> ))}
- 47개의 후기 + {wine.reviewCount}개의 후기
@@ -132,13 +135,12 @@ export default function WineListCard() { 최신 후기
- {wine.review} + {wine.recentReview ? wine.recentReview.content : '아직 후기가 없습니다.'}
))} - {/* 무한 스크롤 감지 */}
); diff --git a/src/components/common/winelist/WineSlider.tsx b/src/components/common/winelist/WineSlider.tsx index 54b6c340..73503f54 100644 --- a/src/components/common/winelist/WineSlider.tsx +++ b/src/components/common/winelist/WineSlider.tsx @@ -1,3 +1,5 @@ +import { useQuery } from '@tanstack/react-query'; + import { Carousel, CarouselContent, @@ -5,45 +7,20 @@ import { CarouselPrevious, CarouselNext, } from '@/components/ui/carousel'; +import { getRecommendedWines } from '@/lib/wineApi'; +import { RecommendedWineResponse } from '@/types/wineListType'; import WineCard from './WineCard'; -const baseWines = [ - { - id: 1, - name: 'Sentinel Carbernet Sauvignon 2016', - image: '/images/image1.svg', - rating: 4.8, - }, - { - id: 2, - name: 'Palazzo della Torre 2017', - image: '/images/image3.svg', - rating: 4.6, - }, - { - id: 3, - name: 'Sentinel Carbernet Sauvignon 2016', - image: '/images/image2.svg', - rating: 4.6, - }, - { - id: 4, - name: 'Palazzo della Torre 2017', - image: '/images/image4.svg', - rating: 3.1, - }, -]; - -const mockWines = Array.from({ length: 20 }).map((_, i) => { - const wine = baseWines[i % baseWines.length]; - return { - ...wine, - id: i + 1, - }; -}); +const TEAM_ID = process.env.NEXT_PUBLIC_TEAM; +const RECOMMENDED_WINES_LIMIT = 4; export default function WineSlider() { + const { data, isLoading, isError } = useQuery({ + queryKey: ['recommendedWines'], + queryFn: () => getRecommendedWines({ teamId: TEAM_ID!, limit: RECOMMENDED_WINES_LIMIT }), + }); + return (
@@ -51,19 +28,38 @@ export default function WineSlider() { 이번 달 추천 와인 - {/* 캐러셀 영역 */}
- - - {mockWines.map((wine) => ( - - - - ))} - - - - + {isLoading && !data ? ( +

불러오는 중...

+ ) : isError || !data ? ( +

추천 와인을 불러올 수 없습니다.

+ ) : ( + (() => { + const filteredWines = data.filter((wine) => !wine.image.includes('example.com')); + if (filteredWines.length === 0) { + return

추천 와인 목록이 없습니다.

; + } + + return ( + + + {filteredWines.map((wine) => ( + + + + ))} + + + + + ); + })() + )}
diff --git a/src/hooks/useWineListQuery.ts b/src/hooks/useWineListQuery.ts index 9809f8bf..e0aa51c1 100644 --- a/src/hooks/useWineListQuery.ts +++ b/src/hooks/useWineListQuery.ts @@ -1,28 +1,48 @@ -import { useInfiniteQuery } from '@tanstack/react-query'; +import { InfiniteData, useInfiniteQuery } from '@tanstack/react-query'; -import { getWines } from '@/lib/getWines'; +import { getWines } from '@/lib/wineApi'; import useFilterStore from '@/stores/filterStore'; import useWineSearchKeywordStore from '@/stores/searchStore'; +import { GetWinesResponse } from '@/types/wineListType'; const PAGE_LIMIT = 8; +const TEAM_ID = process.env.NEXT_PUBLIC_TEAM; export function useWineListQuery() { - /* 각 필터 상태 */ const type = useFilterStore((state) => state.type); const minPrice = useFilterStore((state) => state.minPrice); const maxPrice = useFilterStore((state) => state.maxPrice); const rating = useFilterStore((state) => state.rating); const searchTerm = useWineSearchKeywordStore((state) => state.searchTerm); - return useInfiniteQuery({ - queryKey: ['wines', { type, minPrice, maxPrice, rating, searchTerm }], - queryFn: ({ pageParam = 0 }) => - getWines({ - cursor: pageParam, + const apiRating = rating === 'all' ? undefined : Number(rating); + + return useInfiniteQuery>({ + queryKey: ['wines', { type, minPrice, maxPrice, rating: apiRating, name: searchTerm }], + queryFn: ({ pageParam = 0 }) => { + const filters = { + type: type.toUpperCase(), + minPrice, + maxPrice, + rating: apiRating, + name: searchTerm, + }; + + const filteredParams = Object.fromEntries( + Object.entries(filters).filter( + ([, value]) => value !== null && value !== undefined && value !== '', + ), + ); + + return getWines({ + teamId: TEAM_ID!, + cursor: pageParam as number, limit: PAGE_LIMIT, - filters: { type, minPrice, maxPrice, rating, searchTerm }, - }), + filters: filteredParams, + }); + }, + initialPageParam: 0, - getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined, + getNextPageParam: (lastPage) => lastPage?.nextCursor ?? undefined, }); } diff --git a/src/lib/getWines.ts b/src/lib/getWines.ts deleted file mode 100644 index 630fc350..00000000 --- a/src/lib/getWines.ts +++ /dev/null @@ -1,61 +0,0 @@ -import useWineAddStore, { Wine } from '@/stores/wineAddStore'; - -interface GetWinesParams { - cursor: number; - limit: number; - filters: { - type?: string; - minPrice?: number; - maxPrice?: number; - rating?: string; - searchTerm?: string; - }; -} - -interface GetWinesResponse { - list: Wine[]; - nextCursor: number | null; - totalCount: number; -} - -/* 평점 필터링 */ -const ratingRangeMap: Record = { - all: [0, 5], - '4.6': [4.5, 5], - '4.1': [4.0, 4.5], - '3.6': [3.5, 4.0], - '3.1': [3.0, 3.5], -}; - -/* 필터링 + 페이징 */ -export function getWines({ cursor, limit, filters }: GetWinesParams): GetWinesResponse { - const allWines = useWineAddStore.getState().wines; - - const { type, minPrice = 0, maxPrice = Infinity, rating = 'all', searchTerm = '' } = filters; - - const [minRating, maxRating] = ratingRangeMap[rating] ?? [0, 5]; - const keyword = searchTerm.toLowerCase(); - - const filtered = allWines.filter((wine) => { - if (type && wine.type !== type) return false; - if (wine.price < minPrice || wine.price > maxPrice) return false; - if (wine.rating < minRating || wine.rating > maxRating) return false; - if ( - keyword && - !wine.name.toLowerCase().includes(keyword) && - !wine.region.toLowerCase().includes(keyword) - ) - return false; - return true; - }); - - const totalCount = filtered.length; - const paged = filtered.slice(cursor, cursor + limit); - const nextCursor = cursor + limit < totalCount ? cursor + limit : null; - - return { - list: paged, - nextCursor, - totalCount, - }; -} diff --git a/src/lib/wineApi.ts b/src/lib/wineApi.ts new file mode 100644 index 00000000..69988bdc --- /dev/null +++ b/src/lib/wineApi.ts @@ -0,0 +1,55 @@ +import axios from 'axios'; + +import { GetWinesParams, GetWinesResponse, RecommendedWineResponse } from '@/types/wineListType'; + +const API_BASE_URL = 'https://winereview-api.vercel.app'; + +interface GetRecommendedWinesParams { + teamId: string; + limit: number; +} + +export const getWines = async ({ + teamId, + cursor, + limit, + filters, +}: GetWinesParams): Promise => { + try { + const params = new URLSearchParams(); + if (cursor !== undefined && cursor !== null) { + params.append('cursor', String(cursor)); + } + if (limit !== undefined) { + params.append('limit', String(limit)); + } + + // 필터 객체를 순회하며 유효한 필터만 파라미터에 추가 + if (filters) { + Object.entries(filters).forEach(([key, value]) => { + if (value !== null && value !== undefined && value !== '') { + params.append(key, String(value)); + } + }); + } + + const response = await axios.get(`${API_BASE_URL}/${teamId}/wines?${params.toString()}`); + return response.data; + } catch (error) { + console.error('Failed to fetch wines:', error); + throw error; + } +}; + +export const getRecommendedWines = async ({ + teamId, + limit, +}: GetRecommendedWinesParams): Promise => { + try { + const response = await axios.get(`${API_BASE_URL}/${teamId}/wines/recommended?limit=${limit}`); + return response.data; + } catch (error) { + console.error('Failed to fetch recommended wines:', error); + throw error; + } +}; diff --git a/src/pages/wines/index.tsx b/src/pages/wines/index.tsx index 4ea9ec66..4461d227 100644 --- a/src/pages/wines/index.tsx +++ b/src/pages/wines/index.tsx @@ -2,7 +2,6 @@ import WineFilter from '@/components/common/winelist/WineFilter'; import WineListCard from '@/components/common/winelist/WineListCard'; import WineSlider from '@/components/common/winelist/WineSlider'; -//// export default function Wines() { return (
diff --git a/src/stores/filterStore.ts b/src/stores/filterStore.ts index a59a8fd5..e5122aed 100644 --- a/src/stores/filterStore.ts +++ b/src/stores/filterStore.ts @@ -1,6 +1,6 @@ import { create } from 'zustand'; -export type WineType = 'Red' | 'White' | 'Sparkling'; +export type WineType = 'RED' | 'WHITE' | 'SPARKLING'; type FilterState = { type: WineType; diff --git a/src/stores/wineAddStore.ts b/src/stores/wineAddStore.ts index 108f8326..1a0373ea 100644 --- a/src/stores/wineAddStore.ts +++ b/src/stores/wineAddStore.ts @@ -29,215 +29,6 @@ const useWineAddStore = create((set) => ({ review: 'Cherry, cocoa, vanilla and clove - beautiful red fruit driven Amarone. Low acidity and medium tannins. Nice long velvety finish.', }, - { - id: 2, - name: 'Palazzo della Torre 2017', - region: 'Western Cape, South Africa', - image: '/images/image3.svg', - price: 64900, - rating: 4.6, - type: 'Sparkling', - review: - 'Cherry, cocoa, vanilla and clove - beautiful red fruit driven Amarone. Low acidity and medium tannins. Nice long velvety finish.', - }, - { - id: 3, - name: 'Sentinel Carbernet Sauvignon 2016', - region: 'Western Cape, South Africa', - image: '/images/image2.svg', - price: 59900, - rating: 3.6, - type: 'White', - review: - 'Cherry, cocoa, vanilla and clove - beautiful red fruit driven Amarone. Low acidity and medium tannins. Nice long velvety finish.', - }, - { - id: 4, - name: 'Palazzo della Torre 2017', - region: 'Western Cape, South Africa', - image: '/images/image4.svg', - price: 74000, - rating: 2.1, - type: 'Red', - review: - 'Cherry, cocoa, vanilla and clove - beautiful red fruit driven Amarone. Low acidity and medium tannins. Nice long velvety finish.', - }, - { - id: 5, - name: 'Sentinel Carbernet Sauvignon 2016', - region: 'Western Cape, South Africa', - image: '/images/image1.svg', - price: 64900, - rating: 4.5, - type: 'Red', - review: - 'Cherry, cocoa, vanilla and clove - beautiful red fruit driven Amarone. Low acidity and medium tannins. Nice long velvety finish.', - }, - { - id: 6, - name: 'Palazzo della Torre 2017', - region: 'Western Cape, South Africa', - image: '/images/image3.svg', - price: 64900, - rating: 4.6, - type: 'Red', - review: - 'Cherry, cocoa, vanilla and clove - beautiful red fruit driven Amarone. Low acidity and medium tannins. Nice long velvety finish.', - }, - { - id: 7, - name: 'Sentinel Carbernet Sauvignon 2016', - region: 'Western Cape, South Africa', - image: '/images/image2.svg', - price: 59900, - rating: 3.6, - type: 'Red', - review: - 'Cherry, cocoa, vanilla and clove - beautiful red fruit driven Amarone. Low acidity and medium tannins. Nice long velvety finish.', - }, - { - id: 8, - name: 'Palazzo della Torre 2017', - region: 'Western Cape, South Africa', - image: '/images/image4.svg', - price: 74000, - rating: 2.1, - type: 'Red', - review: - 'Cherry, cocoa, vanilla and clove - beautiful red fruit driven Amarone. Low acidity and medium tannins. Nice long velvety finish.', - }, - { - id: 9, - name: 'Sentinel Carbernet Sauvignon 2016', - region: 'Western Cape, South Africa', - image: '/images/image1.svg', - price: 64900, - rating: 4.5, - type: 'Red', - review: - 'Cherry, cocoa, vanilla and clove - beautiful red fruit driven Amarone. Low acidity and medium tannins. Nice long velvety finish.', - }, - { - id: 10, - name: 'Palazzo della Torre 2017', - region: 'Western Cape, South Africa', - image: '/images/image3.svg', - price: 64900, - rating: 4.6, - type: 'Red', - review: - 'Cherry, cocoa, vanilla and clove - beautiful red fruit driven Amarone. Low acidity and medium tannins. Nice long velvety finish.', - }, - { - id: 11, - name: 'Sentinel Carbernet Sauvignon 2016', - region: 'Western Cape, South Africa', - image: '/images/image2.svg', - price: 59900, - rating: 3.6, - type: 'Red', - review: - 'Cherry, cocoa, vanilla and clove - beautiful red fruit driven Amarone. Low acidity and medium tannins. Nice long velvety finish.', - }, - { - id: 12, - name: 'Palazzo della Torre 2017', - region: 'Western Cape, South Africa', - image: '/images/image4.svg', - price: 74000, - rating: 2.1, - type: 'Red', - review: - 'Cherry, cocoa, vanilla and clove - beautiful red fruit driven Amarone. Low acidity and medium tannins. Nice long velvety finish.', - }, - { - id: 13, - name: 'Sentinel Carbernet Sauvignon 2016', - region: 'Western Cape, South Africa', - image: '/images/image1.svg', - price: 64900, - rating: 4.5, - type: 'Red', - review: - 'Cherry, cocoa, vanilla and clove - beautiful red fruit driven Amarone. Low acidity and medium tannins. Nice long velvety finish.', - }, - { - id: 14, - name: 'Palazzo della Torre 2017', - region: 'Western Cape, South Africa', - image: '/images/image3.svg', - price: 64900, - rating: 4.6, - type: 'Red', - review: - 'Cherry, cocoa, vanilla and clove - beautiful red fruit driven Amarone. Low acidity and medium tannins. Nice long velvety finish.', - }, - { - id: 15, - name: 'Sentinel Carbernet Sauvignon 2016', - region: 'Western Cape, South Africa', - image: '/images/image2.svg', - price: 59900, - rating: 3.6, - type: 'Red', - review: - 'Cherry, cocoa, vanilla and clove - beautiful red fruit driven Amarone. Low acidity and medium tannins. Nice long velvety finish.', - }, - { - id: 16, - name: 'Palazzo della Torre 2017', - region: 'Western Cape, South Africa', - image: '/images/image4.svg', - price: 74000, - rating: 2.1, - type: 'Red', - review: - 'Cherry, cocoa, vanilla and clove - beautiful red fruit driven Amarone. Low acidity and medium tannins. Nice long velvety finish.', - }, - { - id: 17, - name: 'Sentinel Carbernet Sauvignon 2016', - region: 'Western Cape, South Africa', - image: '/images/image1.svg', - price: 64900, - rating: 4.5, - type: 'Red', - review: - 'Cherry, cocoa, vanilla and clove - beautiful red fruit driven Amarone. Low acidity and medium tannins. Nice long velvety finish.', - }, - { - id: 18, - name: 'Palazzo della Torre 2017', - region: 'Western Cape, South Africa', - image: '/images/image3.svg', - price: 64900, - rating: 4.6, - type: 'Red', - review: - 'Cherry, cocoa, vanilla and clove - beautiful red fruit driven Amarone. Low acidity and medium tannins. Nice long velvety finish.', - }, - { - id: 19, - name: 'Sentinel Carbernet Sauvignon 2016', - region: 'Western Cape, South Africa', - image: '/images/image2.svg', - price: 59900, - rating: 3.6, - type: 'Red', - review: - 'Cherry, cocoa, vanilla and clove - beautiful red fruit driven Amarone. Low acidity and medium tannins. Nice long velvety finish.', - }, - { - id: 20, - name: 'Palazzo della Torre 2017', - region: 'Western Cape, South Africa', - image: '/images/image4.svg', - price: 74000, - rating: 2.1, - type: 'Red', - review: - 'Cherry, cocoa, vanilla and clove - beautiful red fruit driven Amarone. Low acidity and medium tannins. Nice long velvety finish.', - }, ], addWine: (newWine) => diff --git a/src/types/wineListType.ts b/src/types/wineListType.ts new file mode 100644 index 00000000..164cbf29 --- /dev/null +++ b/src/types/wineListType.ts @@ -0,0 +1,48 @@ +export interface Wine { + id: number; + name: string; + region: string; + image: string; + price: number; + type: 'RED' | 'WHITE' | 'SPARKLING'; + avgRating: number; + reviewCount: number; + recentReview: { + id: number; + content: string; + createdAt: string; + updatedAt: string; + } | null; + userId: number; +} + +export interface RecommendedWine { + id: number; + name: string; + region: string; + image: string; + price: number; + type: 'RED' | 'WHITE' | 'SPARKLING'; + avgRating: number; +} + +export interface GetWinesParams { + teamId: string; + cursor?: number | null; + limit?: number; + filters: { + type?: string; + minPrice?: number; + maxPrice?: number; + rating?: number; + searchTerm?: string; + }; +} + +export interface GetWinesResponse { + list: Wine[]; + totalCount: number; + nextCursor: number | null; +} + +export type RecommendedWineResponse = RecommendedWine[];