Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test / bid flow #83

Merged
merged 6 commits into from
Oct 2, 2024
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
47 changes: 47 additions & 0 deletions src/components/bid/BidFooter.tsx
Original file line number Diff line number Diff line change
@@ -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<object, any, any> | undefined) => Promise<void>;
handlePatch: () => void;
}

const BidFooter = ({ remain, check, isSubmitting, handlePost, handlePatch }: BidFooterProps) => {
if (remain === MAX_BID_COUNT) {
return (
<Button
type='button'
color='cheeseYellow'
className='w-full h-full transition-colors rounded text-button active:bg-black'
aria-label='μ œμ•ˆν•˜κΈ° λ²„νŠΌ'
onClick={handlePost}
disabled={!check || isSubmitting}
>
μ œμ•ˆν•˜κΈ°
</Button>
);
}

return (
<>
<Button type='button' color='white' className='flex-1 h-full transition-colors rounded text-button active:bg-black' onClick={handlePatch}>
μ°Έμ—¬ μ·¨μ†Œ
</Button>
<Button
type='button'
color='cheeseYellow'
className='flex-[2] h-full rounded text-button active:bg-black transition-colors'
disabled={!check || isSubmitting || remain === 0}
onClick={handlePost}
>
κΈˆμ•‘ μˆ˜μ •
{remain !== 0 ? `(${remain}회 κ°€λŠ₯)` : '(μ†Œμ§„)'}
</Button>
</>
);
};

export default BidFooter;
1 change: 1 addition & 0 deletions src/constants/bid.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const MAX_BID_COUNT = 3;
20 changes: 19 additions & 1 deletion src/constants/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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원 λ‹¨μœ„λ‘œ μž…λ ₯ν•΄ μ£Όμ„Έμš”.',
});
}
}),
});
Empty file removed src/constants/sortType.ts
Empty file.
7 changes: 4 additions & 3 deletions src/hooks/useEditableNumberInput.ts
Original file line number Diff line number Diff line change
@@ -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<T extends FieldValues> {
name: Path<T>;
Expand All @@ -15,7 +16,7 @@ export const useEditableNumberInput = <T extends FieldValues>({ name, setValue,
const { value } = e.target;
const num = Number(value.replace(/,/g, ''));
if (!Number.isNaN(num)) {
setValue(name, formatCurrencyWithWon(num) as PathValue<T, Path<T>>);
setValue(name, num === 0 ? ('' as PathValue<T, Path<T>>) : (formatCurrencyWithWon(num) as PathValue<T, Path<T>>));
setIsEditing(false);
}
};
Expand Down
65 changes: 18 additions & 47 deletions src/pages/Bid.tsx
Original file line number Diff line number Diff line change
@@ -1,59 +1,57 @@
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<typeof BidSchema>;

const Bid = () => {
const navigate = useNavigate();
const [check, setCheck] = useState<boolean>(false);
const toggleCheckBox = () => setCheck((state) => !state);
const auctionId = useLoaderData() as number;
const { auctionDetails } = useGetAuctionDetails(auctionId);
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<typeof BidSchema>;

const {
control,
formState: { errors, isSubmitting },
setValue,
getValues,
handleSubmit,
} = useForm<FormFields>({
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<FormFields> = async (data) => {
const bidData = {
auctionId: Number(auctionId),
amount: convertCurrencyToNumber(data.minPrice),
amount: convertCurrencyToNumber(data.bidAmount),
};

postBid(bidData);
Expand All @@ -64,12 +62,12 @@ const Bid = () => {

return (
<Layout>
<Layout.Header title={title} handleBack={() => navigate(-1)} />
<Layout.Header title={title} />
<Layout.Main>
<div className='flex flex-col gap-8'>
<AuctionItem axis='row' label='μž…μ°° μƒν’ˆ'>
<AuctionItem.Image src={imageList[0]} time={timeRemaining} />
<AuctionItem.Main kind='register' name={name} count={participantCount} price={minPrice} />
<AuctionItem.Main kind='register' name={name} count={participantCount} price={bidAmount} />
</AuctionItem>
{isParticipating && (
<div className='flex flex-col gap-2'>
Expand All @@ -81,14 +79,14 @@ const Bid = () => {
)}
<FormField
label='가격 μ œμ•ˆν•˜κΈ°'
name='minPrice'
name='bidAmount'
control={control}
error={errors.minPrice?.message}
error={errors.bidAmount?.message}
render={(field) => (
<Input
id='가격 μ œμ•ˆν•˜κΈ°'
type={isEditing ? 'number' : 'text'}
placeholder='μ΅œμ†Œ μž…μ°°κ°€λŠ” 1,000원 μž…λ‹ˆλ‹€.'
placeholder={`μ΅œμ†Œ μž…μ°°κ°€λŠ” ${formatCurrencyWithWon(minPrice + 1000)} μž…λ‹ˆλ‹€.`}
className=' focus-visible:ring-cheeseYellow'
{...field}
onBlur={handleBlur}
Expand All @@ -100,34 +98,7 @@ const Bid = () => {
</div>
</Layout.Main>
<Layout.Footer type={isParticipating ? 'double' : 'single'}>
{!isParticipating ? (
<Button
type='button'
color='cheeseYellow'
className='w-full h-full transition-colors rounded text-button active:bg-black'
aria-label='μ œμ•ˆν•˜κΈ° λ²„νŠΌ'
onClick={handleSubmit(onPostSubmit)}
disabled={!check || isSubmitting}
>
{buttonName}
</Button>
) : (
<>
<Button type='button' color='white' className='flex-1 h-full transition-colors rounded text-button active:bg-black' onClick={onPatchSubmit}>
μ°Έμ—¬ μ·¨μ†Œ
</Button>
<Button
type='button'
color='cheeseYellow'
className='flex-[2] h-full rounded text-button active:bg-black transition-colors'
disabled={!check || isSubmitting}
onClick={handleSubmit(onPostSubmit)}
>
κΈˆμ•‘ μˆ˜μ •
{isParticipating && remainingBidCount !== 0 ? `(${remainingBidCount}회 κ°€λŠ₯)` : '(μ†Œμ§„)'}
</Button>
</>
)}
<BidFooter remain={remainingBidCount} check={check} isSubmitting={isSubmitting} handlePatch={onPatchSubmit} handlePost={handleSubmit(onPostSubmit)} />
</Layout.Footer>
</Layout>
);
Expand Down
5 changes: 2 additions & 3 deletions src/pages/BidderList.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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 = () =>
Expand All @@ -24,7 +23,7 @@ const BidderList = () => {

return (
<Layout>
<Layout.Header title='경맀 μ°Έμ—¬μž λͺ©λ‘' handleBack={() => navigate(-1)} />
<Layout.Header title='경맀 μ°Έμ—¬μž λͺ©λ‘' />
<Layout.Main>
<div className='flex flex-col gap-8 pt-4'>
<AuctionItem axis='row' label='μž…μ°°μž λͺ©λ‘ μƒν’ˆ'>
Expand Down