From 8f9f3c9bfc5c18df15741d52bd40365116949658 Mon Sep 17 00:00:00 2001 From: Daeeui Kim Date: Fri, 11 Oct 2024 16:17:09 +0900 Subject: [PATCH] =?UTF-8?q?[FEAT]=20=EC=9E=85=EC=B0=B0=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20validation=20=EC=B6=94=EA=B0=80=20(#54)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: '-' => '0' 표현으로 변경 * refactor: 에러 alert문으로 띄우기 * feat: 입찰 희망가 버튼 추가 * feat: 모달로 입찰 알림 확인하기 * refactor: 입찰 희망가 현재가 + 입찰 단위로 수정 --- .../basicAuction/usePostBasicAuctionBid.ts | 12 ++- .../basicauction/[id]/BasicAuctionInfo.tsx | 1 + .../AuctionInfo/AuctionBidConfirmModal.tsx | 7 ++ src/components/AuctionInfo/AuctionInfo.css.ts | 4 + src/components/AuctionInfo/index.tsx | 88 ++++++++++++++++--- src/hooks/useCountdownTimer.ts | 8 +- 6 files changed, 102 insertions(+), 18 deletions(-) create mode 100644 src/components/AuctionInfo/AuctionBidConfirmModal.tsx diff --git a/src/apis/queryHooks/basicAuction/usePostBasicAuctionBid.ts b/src/apis/queryHooks/basicAuction/usePostBasicAuctionBid.ts index d89737d..ca48f86 100644 --- a/src/apis/queryHooks/basicAuction/usePostBasicAuctionBid.ts +++ b/src/apis/queryHooks/basicAuction/usePostBasicAuctionBid.ts @@ -1,7 +1,9 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { AxiosError } from 'axios'; import { postBasicAuctionBid } from '@/apis/queryFunctions/basicAuction'; import { PostBasicAuctionBidParams } from '@/apis/types/basicAuction'; +import { Response } from '@/apis/types/common'; function usePostBasicAuctionBid() { const queryClient = useQueryClient(); @@ -14,8 +16,14 @@ function usePostBasicAuctionBid() { queryClient.invalidateQueries({ queryKey: ['basicAuctionList'] }); queryClient.invalidateQueries({ queryKey: ['basicAuctionBidList', params.id] }); }, - onError: () => { - console.log('bid 실패'); + onError: (e: AxiosError>) => { + if (e.response) { + alert(`${e.response.data.result_msg}`); + } else { + // 네트워크 에러나 기타 처리되지 않은 에러 처리 + console.log('알 수 없는 오류 발생', e.message); + alert('알 수 없는 오류가 발생했습니다.'); + } }, }); diff --git a/src/app/basicauction/[id]/BasicAuctionInfo.tsx b/src/app/basicauction/[id]/BasicAuctionInfo.tsx index be06ebd..5f42ba7 100644 --- a/src/app/basicauction/[id]/BasicAuctionInfo.tsx +++ b/src/app/basicauction/[id]/BasicAuctionInfo.tsx @@ -22,6 +22,7 @@ function BasicAuctionInfo({ id }: BasicAuctionInfoProps) { nowPrice={data.result_data.now_price} endTime={data.result_data.end_date} bidCount={data.result_data.bid_count} + bidUnit={data.result_data.bid_unit} /> ); } diff --git a/src/components/AuctionInfo/AuctionBidConfirmModal.tsx b/src/components/AuctionInfo/AuctionBidConfirmModal.tsx new file mode 100644 index 0000000..a668837 --- /dev/null +++ b/src/components/AuctionInfo/AuctionBidConfirmModal.tsx @@ -0,0 +1,7 @@ +interface AuctionBidConfirmModalProps { + bidPrice: string | undefined; +} + +export default function AuctionBidConfirmModal({ bidPrice }: AuctionBidConfirmModalProps) { + return
{`${bidPrice} 원`}에 입찰을 진행하시겠습니까?
; +} diff --git a/src/components/AuctionInfo/AuctionInfo.css.ts b/src/components/AuctionInfo/AuctionInfo.css.ts index bcb62a9..f1a91df 100644 --- a/src/components/AuctionInfo/AuctionInfo.css.ts +++ b/src/components/AuctionInfo/AuctionInfo.css.ts @@ -87,3 +87,7 @@ export const bidButtonExplain = style({ marginTop: '4px', fontSize: '12px', }); + +export const bidPriceButton = style({ + cursor: 'pointer', +}); diff --git a/src/components/AuctionInfo/index.tsx b/src/components/AuctionInfo/index.tsx index c94bbae..11fa401 100644 --- a/src/components/AuctionInfo/index.tsx +++ b/src/components/AuctionInfo/index.tsx @@ -1,29 +1,31 @@ -'use client'; - -import { useRef, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import usePostBasicAuctionBid from '@/apis/queryHooks/basicAuction/usePostBasicAuctionBid'; +import ChevronDownIcon from '@/assets/svg/chevron-down.svg'; +import ChevronUpIcon from '@/assets/svg/chevron-up.svg'; +import AuctionBidConfirmModal from '@/components/AuctionInfo/AuctionBidConfirmModal'; +import AuctionBidListModal from '@/components/AuctionInfo/AuctionBidListModal'; +import AuctionCountdown from '@/components/AuctionInfo/AuctionCountdown'; +import { Modal } from '@/components/Modal/Modal'; +import ModalFooter from '@/components/Modal/ModalFooter'; import useBooleanState from '@/hooks/useBooleanState'; import { useAuth } from '@/provider/authProvider'; -import { Modal } from '../Modal/Modal'; - -import AuctionBidListModal from './AuctionBidListModal'; -import AuctionCountdown from './AuctionCountdown'; import * as S from './AuctionInfo.css'; interface AuctionInfoProps { id: number; title: string; startPrice: number; - nowPrice: number; + nowPrice?: number; // nowPrice는 선택적 endTime: string; bidCount: number; + bidUnit: number; } function AuctionInfo(SAMPLE: AuctionInfoProps) { const { token } = useAuth(); - const { id, title, startPrice, nowPrice, bidCount, endTime } = SAMPLE; + const { id, title, startPrice, nowPrice, bidCount, endTime, bidUnit } = SAMPLE; const { mutate } = usePostBasicAuctionBid(); const [expired, setExpired] = useState(false); @@ -35,6 +37,20 @@ function AuctionInfo(SAMPLE: AuctionInfoProps) { setTrue: openBidListModal, } = useBooleanState(); + const { + value: isOpenBidComfirmModal, + toggle: setIsOpenBidConfirmModal, + setTrue: openBidConfirmModal, + } = useBooleanState(); + + useEffect(() => { + if (bidInputRef.current && nowPrice) { + bidInputRef.current.value = String(nowPrice + bidUnit); + } else if (bidInputRef.current && startPrice) { + bidInputRef.current.value = String(startPrice); + } + }, [nowPrice, startPrice]); + const handleBidButton = () => { if (bidInputRef.current) { const bidAmount = bidInputRef.current.value; @@ -46,6 +62,21 @@ function AuctionInfo(SAMPLE: AuctionInfoProps) { }, }); } + setIsOpenBidConfirmModal(); + }; + + const handleBidPriceDown = () => { + const bidInput = Number(bidInputRef.current?.value); + let currentBid = bidInput; + if (nowPrice && bidInput - bidUnit >= nowPrice) { + currentBid -= bidUnit; + } else if (bidInput - bidUnit >= startPrice) { + currentBid -= bidUnit; + } + + if (currentBid >= nowPrice! + bidUnit && bidInputRef.current) { + bidInputRef.current.value = String(currentBid); + } }; const canNotBid = () => { @@ -55,7 +86,6 @@ function AuctionInfo(SAMPLE: AuctionInfoProps) { if (!token) { return '로그인 후 사용 가능한 서비스입니다.'; } - return ''; }; @@ -94,22 +124,56 @@ function AuctionInfo(SAMPLE: AuctionInfoProps) { +
+ 입찰 단위 +
+ {`${bidUnit} 원`} +
+ + + +
입찰 희망가
- + +
+ + +
+ + + ); } diff --git a/src/hooks/useCountdownTimer.ts b/src/hooks/useCountdownTimer.ts index 4b6447d..3cf04fd 100644 --- a/src/hooks/useCountdownTimer.ts +++ b/src/hooks/useCountdownTimer.ts @@ -34,10 +34,10 @@ function useCountdownTimer({ endTime }: UseCountdownTimerProps) { }; }, []); - const day = Math.floor(remainingTime / DAY_IN_MILLIS) || '-'; - const hour = Math.floor((remainingTime % DAY_IN_MILLIS) / HOUR_IN_MILLIS) || '-'; - const minute = Math.floor((remainingTime % HOUR_IN_MILLIS) / MINUTE_IN_MILLIS) || '-'; - const second = Math.floor((remainingTime % MINUTE_IN_MILLIS) / 1000) || '-'; + const day = Math.floor(remainingTime / DAY_IN_MILLIS) ?? '-'; + const hour = Math.floor((remainingTime % DAY_IN_MILLIS) / HOUR_IN_MILLIS) ?? '-'; + const minute = Math.floor((remainingTime % HOUR_IN_MILLIS) / MINUTE_IN_MILLIS) ?? '-'; + const second = Math.floor((remainingTime % MINUTE_IN_MILLIS) / 1000) ?? '-'; const isTimeout = remainingTime <= 0; return { isTimeout, remainingTime, day, hour, minute, second };