Skip to content

Commit

Permalink
[FEAT] 입찰 기능 validation 추가 (#54)
Browse files Browse the repository at this point in the history
* refactor: '-' => '0' 표현으로 변경

* refactor: 에러 alert문으로 띄우기

* feat: 입찰 희망가 버튼 추가

* feat: 모달로 입찰 알림 확인하기

* refactor: 입찰 희망가 현재가 + 입찰 단위로 수정
  • Loading branch information
kimeodml authored Oct 11, 2024
1 parent 561d52a commit 8f9f3c9
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 18 deletions.
12 changes: 10 additions & 2 deletions src/apis/queryHooks/basicAuction/usePostBasicAuctionBid.ts
Original file line number Diff line number Diff line change
@@ -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();
Expand All @@ -14,8 +16,14 @@ function usePostBasicAuctionBid() {
queryClient.invalidateQueries({ queryKey: ['basicAuctionList'] });
queryClient.invalidateQueries({ queryKey: ['basicAuctionBidList', params.id] });
},
onError: () => {
console.log('bid 실패');
onError: (e: AxiosError<Response<string>>) => {
if (e.response) {
alert(`${e.response.data.result_msg}`);
} else {
// 네트워크 에러나 기타 처리되지 않은 에러 처리
console.log('알 수 없는 오류 발생', e.message);
alert('알 수 없는 오류가 발생했습니다.');
}
},
});

Expand Down
1 change: 1 addition & 0 deletions src/app/basicauction/[id]/BasicAuctionInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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}
/>
);
}
Expand Down
7 changes: 7 additions & 0 deletions src/components/AuctionInfo/AuctionBidConfirmModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
interface AuctionBidConfirmModalProps {
bidPrice: string | undefined;
}

export default function AuctionBidConfirmModal({ bidPrice }: AuctionBidConfirmModalProps) {
return <div>{`${bidPrice} 원`}에 입찰을 진행하시겠습니까?</div>;
}
4 changes: 4 additions & 0 deletions src/components/AuctionInfo/AuctionInfo.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,7 @@ export const bidButtonExplain = style({
marginTop: '4px',
fontSize: '12px',
});

export const bidPriceButton = style({
cursor: 'pointer',
});
88 changes: 76 additions & 12 deletions src/components/AuctionInfo/index.tsx
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -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;
Expand All @@ -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 = () => {
Expand All @@ -55,7 +86,6 @@ function AuctionInfo(SAMPLE: AuctionInfoProps) {
if (!token) {
return '로그인 후 사용 가능한 서비스입니다.';
}

return '';
};

Expand Down Expand Up @@ -94,22 +124,56 @@ function AuctionInfo(SAMPLE: AuctionInfoProps) {
<AuctionBidListModal id={id} />
</Modal>
</div>
<div className={S.infoRow}>
<span className={S.infoRowTitle}>입찰 단위</span>
<div className={S.infoRight}>
<span>{`${bidUnit} 원`}</span>
</div>
<Modal isOpen={isOpenBidListModal} onOpenChange={setIsOpenBidListModal}>
<AuctionBidListModal id={id} />
</Modal>
</div>
<div className={S.infoRow}>
<span className={S.infoRowTitle}>입찰 희망가</span>
<div className={S.infoRight}>
<input type="number" ref={bidInputRef} />
<input type="number" ref={bidInputRef} disabled />
<span></span>
<div>
<button
className={S.bidPriceButton}
type="button"
onClick={() => {
if (bidInputRef.current) {
const newBidInput = Number(bidInputRef.current.value) + bidUnit;
bidInputRef.current.value = newBidInput.toString();
}
}}
>
<ChevronUpIcon />
</button>
<button className={S.bidPriceButton} type="button" onClick={handleBidPriceDown}>
<ChevronDownIcon />
</button>
</div>
</div>
</div>
<button
disabled={expired || !token}
type="button"
className={expired || !token ? S.bidButton.disabled : S.bidButton.default}
onClick={handleBidButton}
onClick={openBidConfirmModal}
>
입찰하기
<p className={S.bidButtonExplain}>{canNotBid()}</p>
</button>
<ModalFooter
isOpen={isOpenBidComfirmModal}
onOpenChange={setIsOpenBidConfirmModal}
positiveButton="확인"
positiveButtonEvent={handleBidButton}
>
<AuctionBidConfirmModal bidPrice={bidInputRef?.current?.value} />
</ModalFooter>
</div>
);
}
Expand Down
8 changes: 4 additions & 4 deletions src/hooks/useCountdownTimer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
Expand Down

0 comments on commit 8f9f3c9

Please sign in to comment.