Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import kebab from '@/assets/icons/menu.svg';

export interface WineDataProps {
name: string;
price: number;
price: number | null;
region: string;
type: 'RED' | 'WHITE' | 'SPARKLING';
image: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export default function MyWineListContainer({ setDataCount }: { setDataCount: Re
name={value.name}
region={value.region}
image={value.image}
price={value.price}
price={value.price ?? 0}
size='midium'
isKebab
onClick
Expand Down
2 changes: 1 addition & 1 deletion src/app/(with-header)/wines/_components/WineCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const WineCard = forwardRef<HTMLDivElement, WineCardProps>(({ wine }, ref) => {
<p className='line-clamp-2 text-ellipsis text-3xl font-semibold text-gray-800 pc:w-[390px] tablet:max-w-[600px] mobile:w-auto mobile:text-xl'>{wine.name}</p>
<p className='mb-4 mt-5 text-lg text-gray-500 mobile:mb-2 mobile:mt-0 mobile:text-md'>{wine.region}</p>
<span className='rounded-xl bg-purple-10 px-[15px] py-2 text-2lg font-bold text-purple-100 mobile:rounded-[10px] mobile:px-[10px] mobile:py-[6px] mobile:text-md'>
₩ {wine.price.toLocaleString()}
₩ {wine.price?.toLocaleString() ?? '가격 미제공'}
</span>
</div>
<div className='flex flex-col justify-between gap-1 mobile:mr-2 mobile:flex-row mobile:items-center mobile:gap-0'>
Expand Down
30 changes: 26 additions & 4 deletions src/app/(with-header)/wines/_components/WineListContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import WineCard from './WineCard';
import PostWineModal from '@/components/modal/PostWineModal';
import Refresh from '@/components/Refresh';
import WineListSkeleton from './skeleton/WineListSkeleton';
import LoadingSpinner from '@/components/LoadingSpinner';
import filterIcon from '@/assets/icons/filter.svg';

const MAX_PRICE = 2000000;
Expand All @@ -32,6 +33,7 @@ export default function WineListContainer() {
const [hasError, setHasError] = useState(false);
const [nextCursor, setNextCursor] = useState<number | null>(null);
const [hasMore, setHasMore] = useState(true);
const [isInitialLoading, setIsInitialLoading] = useState(true);
const lastWineRef = useRef<HTMLDivElement | null>(null);

const loadMoreWines = useCallback(async () => {
Expand All @@ -56,9 +58,23 @@ export default function WineListContainer() {
console.error(error);
} finally {
setIsLoading(false);
setIsInitialLoading(false);
}
}, [isLoading, hasMore, nextCursor, filters, searchQuery]);

useEffect(() => {
setWines([]);
setNextCursor(null);
setHasMore(true);
setIsInitialLoading(true);
}, [filters, searchQuery]);

useEffect(() => {
if (isInitialLoading) {
loadMoreWines();
}
}, [isInitialLoading, loadMoreWines]);

useEffect(() => {
if (!hasMore || !lastWineRef.current) return;
const observer = new IntersectionObserver(
Expand Down Expand Up @@ -160,10 +176,16 @@ export default function WineListContainer() {
/>
</div>
)}
{wines.map((wine, index) => (
<WineCard key={`${wine.id}-${index}`} ref={index === wines.length - 1 ? lastWineRef : null} wine={wine} />
))}
{isLoading && <WineListSkeleton count={2} />}
{isInitialLoading ? (
<WineListSkeleton count={2} />
) : (
<>
{wines.map((wine, index) => (
<WineCard key={`${wine.id}-${index}`} ref={index === wines.length - 1 ? lastWineRef : null} wine={wine} />
))}
{isLoading && <LoadingSpinner className='mt-[-30px]' />}
</>
)}
</div>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ interface ButtonProps {
export default function Button({ type = 'button', onClick, href, text, disabled = false, className = '', variant = 'primary' }: ButtonProps) {
const variantStyles =
variant === 'primary'
? 'bg-purple-100 font-bold text-white'
? 'bg-purple-100 font-bold text-white transition-all duration-300 hover:bg-purple-200'
: variant === 'oauth'
? 'rounded-2xl border border-gray-300 bg-white px-[120px] py-[14px] font-medium text-gray-800'
: 'rounded-xl bg-purple-10 px-[36px] py-[16px] text-lg font-bold text-purple-100';
Expand Down
42 changes: 36 additions & 6 deletions src/components/modal/PatchWineModal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { useState } from 'react';
import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
import Image from 'next/image';
import { useForm, SubmitHandler } from 'react-hook-form';
Expand All @@ -15,7 +15,7 @@ interface FormValues {
name: string;
region: string;
image: string;
price: number;
price: number | null;
type: string;
}

Expand All @@ -28,7 +28,8 @@ interface postWinePorps {
editMyWine: (id: number, editWineData: WineDataProps) => void;
}

export default function PatchWineForm({ onClose, id, wineInitialData, editMyWine }: postWinePorps) {
export default function PatchWineModal({ onClose, id, wineInitialData, editMyWine }: postWinePorps) {
const [formattedPrice, setFormattedPrice] = useState<string>('');
const [preview, setPreview] = useState<string | null>(wineInitialData.image);
const router = useRouter();

Expand All @@ -40,6 +41,35 @@ export default function PatchWineForm({ onClose, id, wineInitialData, editMyWine
{ value: () => setValue('type', 'SPARKLING'), label: 'Sparkling' },
];

const handlePriceChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const rawValue = event.target.value.replaceAll(',', '');

if (rawValue === '') {
setFormattedPrice('');
setValue('price', null);
return;
}

if (!/^\d*$/.test(rawValue)) return;

const numericValue = Number(rawValue);

if (numericValue > 2000000) {
alert('가격은 200만원 이하로 입력해 주세요.');
return;
}

setFormattedPrice(numericValue.toLocaleString());
setValue('price', numericValue);
};

useEffect(() => {
if (wineInitialData.price !== null) {
setFormattedPrice(wineInitialData.price.toLocaleString());
setValue('price', wineInitialData.price);
}
}, [wineInitialData.price, setValue]);

const handlePatchWine: SubmitHandler<FormValues> = async (data) => {
const { name, region, image, price, type } = data;

Expand Down Expand Up @@ -114,12 +144,12 @@ export default function PatchWineForm({ onClose, id, wineInitialData, editMyWine
가격
</label>
<input
type='number'
type='text'
id='price'
placeholder='가격 입력 (200만원 이하)'
defaultValue={wineInitialData.price}
value={formattedPrice}
onChange={handlePriceChange}
className='h-[48px] rounded-2xl border border-gray-300 bg-white px-5 py-[14px] text-lg focus:outline-purple-100 mobile:h-[42px] mobile:rounded-xl'
{...register('price')}
/>
</div>

Expand Down
14 changes: 9 additions & 5 deletions src/components/modal/PostReviewModal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
'use client';
import { useState, useEffect } from 'react';
import { useState, useEffect, useCallback } from 'react';
import { useParams } from 'next/navigation';
import Image from 'next/image';
import { useForm, SubmitHandler } from 'react-hook-form';
Expand Down Expand Up @@ -94,9 +94,7 @@ export default function PostReviewModal({ addReview }: { addReview: (newReview:
setIsOpen(true);
};

const closeModal = () => {
setSelectedAroma([]);
setResetTrigger((prev) => !prev);
const closeModal = useCallback(() => {
reset({
rating: 0,
lightBold: 0,
Expand All @@ -107,8 +105,14 @@ export default function PostReviewModal({ addReview }: { addReview: (newReview:
content: '',
wineId: wineData.id,
});
setSelectedAroma([]);
setResetTrigger((prev) => !prev);
setIsOpen(false);
};
}, [reset, wineData.id]);

useEffect(() => {
if (!isOpen) closeModal();
}, [isOpen, closeModal]);

const handleAromaClick = (aroma: string) => {
setSelectedAroma((prevSelectedAroma) => (prevSelectedAroma.includes(aroma) ? prevSelectedAroma.filter((a) => a !== aroma) : [...prevSelectedAroma, aroma]));
Expand Down
40 changes: 35 additions & 5 deletions src/components/modal/PostWineModal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
import Image from 'next/image';
import { useForm, SubmitHandler } from 'react-hook-form';
Expand All @@ -15,14 +15,15 @@ interface FormValues {
name: string;
region: string;
image: string;
price: number;
price: number | null;
type: string;
}

type ImageValues = { image: FileList };

export default function PostWineModal() {
const [isOpen, setIsOpen] = useState<boolean>(false);
const [formattedPrice, setFormattedPrice] = useState<string>('');
const [preview, setPreview] = useState<string | null>(null);
const router = useRouter();

Expand All @@ -43,11 +44,39 @@ export default function PostWineModal() {
setIsOpen(true);
};

const closeModal = () => {
const closeModal = useCallback(() => {
reset();
setValue('type', '');
setValue('price', null);
setFormattedPrice('');
setPreview(null);
setIsOpen(false);
}, [reset, setValue]);

useEffect(() => {
if (!isOpen) closeModal();
}, [isOpen, closeModal]);

const handlePriceChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const rawValue = event.target.value.replaceAll(',', '');

if (rawValue === '') {
setFormattedPrice('');
setValue('price', null);
return;
}

if (!/^\d*$/.test(rawValue)) return;

const numericValue = Number(rawValue);

if (numericValue > 2000000) {
alert('가격은 200만원 이하로 입력해 주세요.');
return;
}

setFormattedPrice(numericValue.toLocaleString());
setValue('price', numericValue);
};

const handlePostWine: SubmitHandler<FormValues> = async (data) => {
Expand Down Expand Up @@ -142,11 +171,12 @@ export default function PostWineModal() {
가격
</label>
<input
type='number'
type='text'
id='price'
placeholder='가격 입력 (200만원 이하)'
value={formattedPrice}
onChange={handlePriceChange}
className='h-[48px] rounded-2xl border border-gray-300 bg-white px-5 py-[14px] text-lg focus:outline-purple-100 mobile:h-[42px] mobile:rounded-xl'
{...register('price')}
/>
</div>

Expand Down
2 changes: 1 addition & 1 deletion src/types/wine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export interface WineDetails {
name: string;
region: string;
image: string;
price: number;
price: number | null;
type: 'RED' | 'WHITE' | 'SPARKLING';
avgRating: number;
reviewCount: number;
Expand Down
1 change: 1 addition & 0 deletions tailwind.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export default {
purple: {
10: '#F1EDFC',
100: '#6A42DB',
200: '#5C37C2',
},
},
screens: {
Expand Down