diff --git a/src/components/bid/BidFooter.tsx b/src/components/bid/BidFooter.tsx new file mode 100644 index 00000000..de962a82 --- /dev/null +++ b/src/components/bid/BidFooter.tsx @@ -0,0 +1,47 @@ +import Button from '../common/Button'; +import { MAX_BID_COUNT } from '@/constants/bid'; + +interface BidFooterProps { + remain: number; + check: boolean; + isSubmitting: boolean; + handlePost: (e?: React.BaseSyntheticEvent | undefined) => Promise; + handlePatch: () => void; +} + +const BidFooter = ({ remain, check, isSubmitting, handlePost, handlePatch }: BidFooterProps) => { + if (remain === MAX_BID_COUNT) { + return ( + + ); + } + + return ( + <> + + + + ); +}; + +export default BidFooter; diff --git a/src/constants/bid.ts b/src/constants/bid.ts new file mode 100644 index 00000000..d56f8093 --- /dev/null +++ b/src/constants/bid.ts @@ -0,0 +1 @@ +export const MAX_BID_COUNT = 3; diff --git a/src/constants/schema.ts b/src/constants/schema.ts index a704c3d9..5e8287b4 100644 --- a/src/constants/schema.ts +++ b/src/constants/schema.ts @@ -45,4 +45,22 @@ export const AddressBookSchema = z.object({ bank: z.string().min(1, '은행을 선택해주세요.'), }); -export const BidSchema = RegisterSchema.pick({ minPrice: true }); +export const getBidSchema = (minPrice: number) => + z.object({ + bidAmount: z.string().superRefine((value, ctx) => { + const num = Number(value.replace(/[^\d]/g, '')); + if (Number.isNaN(num) || num <= minPrice) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: `시작가보다 높은 금액을 입력해주세요.`, + }); + } + + if (num % 1000 !== 0) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: '1000원 단위로 입력해 주세요.', + }); + } + }), + }); diff --git a/src/constants/sortType.ts b/src/constants/sortType.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/src/hooks/useEditableNumberInput.ts b/src/hooks/useEditableNumberInput.ts index 9dcf321b..1903d7f5 100644 --- a/src/hooks/useEditableNumberInput.ts +++ b/src/hooks/useEditableNumberInput.ts @@ -1,6 +1,7 @@ -import { formatCurrencyWithWon } from '@/utils/formatCurrencyWithWon'; -import { FocusEvent, useState } from 'react'; import { FieldValues, Path, PathValue, UseFormGetValues, UseFormSetValue } from 'react-hook-form'; +import { FocusEvent, useState } from 'react'; + +import { formatCurrencyWithWon } from '@/utils/formatCurrencyWithWon'; interface UseEditableNumberProps { name: Path; @@ -15,7 +16,7 @@ export const useEditableNumberInput = ({ name, setValue, const { value } = e.target; const num = Number(value.replace(/,/g, '')); if (!Number.isNaN(num)) { - setValue(name, formatCurrencyWithWon(num) as PathValue>); + setValue(name, num === 0 ? ('' as PathValue>) : (formatCurrencyWithWon(num) as PathValue>)); setIsEditing(false); } }; diff --git a/src/pages/Bid.tsx b/src/pages/Bid.tsx index 78ef7742..7035b837 100644 --- a/src/pages/Bid.tsx +++ b/src/pages/Bid.tsx @@ -1,26 +1,23 @@ -import { LoaderFunction, useLoaderData, useNavigate } from 'react-router-dom'; +import { LoaderFunction, useLoaderData } from 'react-router-dom'; import { SubmitHandler, useForm } from 'react-hook-form'; import { usePatchBid, usePostBid } from '@/components/bid/queries'; import AuctionItem from '@/components/common/item/AuctionItem'; import BidCaution from '@/components/bid/BidCaution'; -import { BidSchema } from '@/constants/schema'; -import Button from '@/components/common/Button'; +import BidFooter from '@/components/bid/BidFooter'; import FormField from '@/components/common/form/FormField'; import { Input } from '@/components/ui/input'; import Layout from '@/components/layout/Layout'; import { convertCurrencyToNumber } from '@/utils/convertCurrencyToNumber'; import { formatCurrencyWithWon } from '@/utils/formatCurrencyWithWon'; +import { getBidSchema } from '@/constants/schema'; import { useEditableNumberInput } from '@/hooks/useEditableNumberInput'; import { useGetAuctionDetails } from '@/components/details/queries'; import { useState } from 'react'; import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; -type FormFields = z.infer; - const Bid = () => { - const navigate = useNavigate(); const [check, setCheck] = useState(false); const toggleCheckBox = () => setCheck((state) => !state); const auctionId = useLoaderData() as number; @@ -28,6 +25,12 @@ const Bid = () => { const { mutate: postBid } = usePostBid(auctionId); const { mutate: patchBid } = usePatchBid(auctionId); + const { imageList, name, minPrice, participantCount, remainingBidCount, bidAmount, timeRemaining, isParticipating, bidId } = auctionDetails; + const title = isParticipating ? '금액 수정하기' : '경매 참여하기'; + + const BidSchema = getBidSchema(minPrice); + type FormFields = z.infer; + const { control, formState: { errors, isSubmitting }, @@ -35,25 +38,20 @@ const Bid = () => { getValues, handleSubmit, } = useForm({ - defaultValues: { minPrice: '' }, + defaultValues: { bidAmount: '' }, resolver: zodResolver(BidSchema), }); const { isEditing, handleBlur, handleFocus } = useEditableNumberInput({ - name: 'minPrice', + name: 'bidAmount', setValue, getValues, }); - const { imageList, name, minPrice, participantCount, remainingBidCount, bidAmount, timeRemaining, isParticipating, bidId } = auctionDetails; - - const buttonName = isSubmitting ? '제안 중...' : '제안하기'; - const title = isParticipating ? '금액 수정하기' : '경매 참여하기'; - const onPostSubmit: SubmitHandler = async (data) => { const bidData = { auctionId: Number(auctionId), - amount: convertCurrencyToNumber(data.minPrice), + amount: convertCurrencyToNumber(data.bidAmount), }; postBid(bidData); @@ -64,12 +62,12 @@ const Bid = () => { return ( - navigate(-1)} /> +
- + {isParticipating && (
@@ -81,14 +79,14 @@ const Bid = () => { )} ( {
- {!isParticipating ? ( - - ) : ( - <> - - - - )} + ); diff --git a/src/pages/BidderList.tsx b/src/pages/BidderList.tsx index fe844b6e..a9751f0e 100644 --- a/src/pages/BidderList.tsx +++ b/src/pages/BidderList.tsx @@ -1,4 +1,4 @@ -import { LoaderFunction, useLoaderData, useNavigate } from 'react-router-dom'; +import { LoaderFunction, useLoaderData } from 'react-router-dom'; import AuctionItem from '@/components/common/item/AuctionItem'; import { BIDDER_LIST_PRICE_FILTER } from '@/constants/filter'; @@ -11,7 +11,6 @@ import { useGetBidderList } from '@/components/bidderList/queries'; import { useState } from 'react'; const BidderList = () => { - const navigate = useNavigate(); const auctionId = useLoaderData() as number; const [filterState, setFilterState] = useState(BIDDER_LIST_PRICE_FILTER.HIGH); const handleFilterState = () => @@ -24,7 +23,7 @@ const BidderList = () => { return ( - navigate(-1)} /> +