Skip to content

Commit 808df23

Browse files
committed
feat: purchase items
1 parent f053ceb commit 808df23

File tree

6 files changed

+196
-25
lines changed

6 files changed

+196
-25
lines changed

src/components/Cart/CartModal.tsx

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@ import { RiCloseLine } from "react-icons/ri";
44
import { useSelector, useDispatch } from "react-redux";
55
import { CartItemListStorage } from "@/store/reducers/cartStorageReducer";
66
import { CartAction } from "@/enum/enum";
7-
import ListingCard from "@/components/Card/ListingCard";
87
import PrimaryLoadingButton from "@/components/Button/PrimaryLoadingButton";
98
import { toast } from "react-toastify";
9+
import CartRow from "./CartRow";
10+
import { AuctionDto } from "@/types/dtos/Auction.dto";
11+
import { mpContractService } from "@/services/mpContract";
12+
import { useAccount } from "wagmi";
1013

1114
interface CartModalProps {
1215
onClose: () => void;
@@ -15,7 +18,9 @@ interface CartModalProps {
1518
const CartModal: React.FC<CartModalProps> = ({ onClose }) => {
1619
const { theme } = useTheme();
1720
const dispatch = useDispatch();
21+
const { address } = useAccount();
1822
const [loadingBuy, setLoadingBuy] = useState(false);
23+
const [ignoreSold, setIgnoreSold] = useState(false);
1924
const cartItems: CartItemListStorage[] = useSelector(
2025
(state: { cartStorage: { cartItems: CartItemListStorage[] } }) =>
2126
state.cartStorage.cartItems
@@ -31,8 +36,8 @@ const CartModal: React.FC<CartModalProps> = ({ onClose }) => {
3136
dispatch({ type: CartAction.CLEAR });
3237
};
3338

34-
const handleRemove = (id: string) => {
35-
dispatch({ type: CartAction.REMOVE, payload: { id } });
39+
const handleRemove = (listing: AuctionDto) => {
40+
dispatch({ type: CartAction.REMOVE, payload: { id: listing.id } });
3641
};
3742

3843
const handleBuyAll = async () => {
@@ -43,11 +48,17 @@ const CartModal: React.FC<CartModalProps> = ({ onClose }) => {
4348
setLoadingBuy(false);
4449
return;
4550
}
46-
await new Promise(res => setTimeout(res, 1000));
51+
await mpContractService.bidAuctions(
52+
cartItems.map(item => item.listing),
53+
address,
54+
ignoreSold
55+
);
56+
toast.success("Buy successful!");
4757
dispatch({ type: CartAction.CLEAR });
48-
toast.success("Buy all successfully!");
4958
onClose();
50-
} catch {
59+
} catch (error) {
60+
const errorMessage = (error as Error).message || "Buy failed!";
61+
console.error("Buy failed:", errorMessage);
5162
toast.error("Buy failed!");
5263
} finally {
5364
setLoadingBuy(false);
@@ -135,36 +146,39 @@ const CartModal: React.FC<CartModalProps> = ({ onClose }) => {
135146
display: "flex",
136147
justifyContent: "space-between",
137148
alignItems: "center",
138-
marginTop: "15px"
149+
marginTop: "0px"
139150
}}
140151
>
141152
<div style={{ flex: 1 }}>
142-
<ListingCard listing={item.listing} canAddToCart={false} />
153+
<CartRow
154+
key={item.id}
155+
listing={item?.listing}
156+
onRemove={handleRemove}
157+
/>
143158
</div>
144-
<button
145-
onClick={() => handleRemove(item.id)}
146-
style={{
147-
marginLeft: 10,
148-
backgroundColor: "transparent",
149-
border: "none",
150-
color: theme.textColor,
151-
cursor: "pointer",
152-
fontSize: "18px"
153-
}}
154-
title="Remove"
155-
>
156-
<RiCloseLine />
157-
</button>
158159
</div>
159160
))}
160161
</div>
161162
<div
162163
style={{
163164
display: "flex",
164-
justifyContent: "space-between",
165+
flexDirection: "column",
166+
gap: "10px",
165167
marginTop: "20px"
166168
}}
167169
>
170+
<div style={{ display: "flex", alignItems: "center", gap: "10px" }}>
171+
<input
172+
type="checkbox"
173+
id="ignoreSold"
174+
checked={ignoreSold}
175+
onChange={e => setIgnoreSold(e.target.checked)}
176+
style={{ cursor: "pointer" }}
177+
/>
178+
<label htmlFor="ignoreSold" style={{ cursor: "pointer" }}>
179+
Ignore sold items and continue buying
180+
</label>
181+
</div>
168182
<PrimaryLoadingButton
169183
onClick={handleBuyAll}
170184
loading={loadingBuy}

src/components/Cart/CartModel.tsx

Whitespace-only changes.

src/components/Cart/CartRow.tsx

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import React from "react";
2+
import { AuctionDto } from "@/types/dtos/Auction.dto";
3+
import { shortenAddress, shortenNumber } from "@/utils/shorten";
4+
import { getBackgroundColor } from "@/utils/colorUtils";
5+
import PrototypeImage from "@/components/Image/PrototypeImage";
6+
7+
interface CartRowProps {
8+
listing: AuctionDto;
9+
onRemove: (listing: AuctionDto) => void;
10+
}
11+
12+
const CartRow: React.FC<CartRowProps> = ({ listing, onRemove }) => {
13+
const backgroundColor = getBackgroundColor(listing.prototype || 0);
14+
15+
return (
16+
<div className="flex items-center p-4 mb-2 rounded-lg" style={{ backgroundColor }}>
17+
<div className="flex-shrink-0 mr-4">
18+
<PrototypeImage width={60} height={60} prototype={listing.prototype || 0} />
19+
</div>
20+
21+
<div className="flex-grow">
22+
<div className="flex items-center gap-2">
23+
<span className="bg-yellow-500 text-black px-2 py-1 rounded-full text-xs">
24+
Lv. {listing.level}
25+
</span>
26+
<span className="text-white font-semibold">#{listing.prototype || 0}</span>
27+
</div>
28+
29+
<div className="flex items-center gap-4 mt-2">
30+
<div className="text-white">
31+
<p className="text-sm font-bold">{listing.lvHashrate}</p>
32+
<p className="text-xs text-gray-300">
33+
{(listing.hashrate || 0) > 5 ? `Lv. 1 - ${listing.hashrate}` : ""}
34+
</p>
35+
</div>
36+
<div className="text-white">
37+
<p className="text-sm">Seller:</p>
38+
<p className="text-sm">{shortenAddress(listing.auctor || "")}</p>
39+
</div>
40+
</div>
41+
</div>
42+
43+
<div className="flex-shrink-0 flex flex-col items-end gap-2">
44+
<span className="text-green-400 font-bold">
45+
{shortenNumber(listing.nowPrice || 0, 9, 3)} USDT
46+
</span>
47+
<button
48+
onClick={() => onRemove(listing)}
49+
className="text-red-400 hover:text-red-500 text-sm font-semibold"
50+
>
51+
Remove
52+
</button>
53+
</div>
54+
</div>
55+
);
56+
};
57+
58+
export default React.memo(CartRow);

src/services/mpContract.ts

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,12 +243,110 @@ const bidAuction = async (listing: AuctionDto, from: `0x${string}` | undefined)
243243
});
244244
};
245245

246+
const bidAuctions = async (
247+
listings: AuctionDto[],
248+
from: `0x${string}` | undefined,
249+
ignoreSold: boolean
250+
) => {
251+
if (!getWagmiConfig()) {
252+
throw new Error("Please connect your wallet first!");
253+
}
254+
if (listings.length === 0) {
255+
throw new Error("No listings to bid");
256+
}
257+
258+
const contractAddress =
259+
from?.toLowerCase() === PRO_BUYER.toLowerCase() ||
260+
from?.toLowerCase() === NORMAL_BUYER.toLowerCase()
261+
? (bidContract as `0x${string}`)
262+
: (MP_ADDRESS as `0x${string}`);
263+
264+
// Single item bid uses different function signature
265+
if (listings.length === 1) {
266+
return await writeContract(getWagmiConfig(), {
267+
abi: abiMp,
268+
address: contractAddress,
269+
functionName: "bid",
270+
args: [
271+
listings[0].auctor,
272+
listings[0].index,
273+
listings[0].uptime,
274+
((listings[0]?.nowPrice ?? 0) + 10 ** 5).toString() + "000000000"
275+
]
276+
});
277+
}
278+
279+
// Multiple items bid
280+
const auctors = listings.map(listing =>
281+
ethers.getAddress(listing.auctor ?? "")
282+
) as `0x${string}`[];
283+
const indexes = listings.map(listing => {
284+
if (listing.index === undefined) throw new Error("Listing index is undefined");
285+
return BigInt(listing.index);
286+
});
287+
const uptimes = listings.map(listing => {
288+
if (listing.uptime === undefined) throw new Error("Listing uptime is undefined");
289+
return BigInt(listing.uptime);
290+
});
291+
const prices = listings.map(listing =>
292+
BigInt(((listing?.nowPrice ?? 0) + 10 ** 5).toString() + "000000000")
293+
);
294+
295+
// Use function overload with array parameters
296+
try {
297+
return await writeContract(getWagmiConfig(), {
298+
abi: [
299+
{
300+
inputs: [
301+
{
302+
internalType: "address[]",
303+
name: "auctors_",
304+
type: "address[]"
305+
},
306+
{
307+
internalType: "uint256[]",
308+
name: "indexs_",
309+
type: "uint256[]"
310+
},
311+
{
312+
internalType: "uint256[]",
313+
name: "startTimes_",
314+
type: "uint256[]"
315+
},
316+
{
317+
internalType: "uint256[]",
318+
name: "prices_",
319+
type: "uint256[]"
320+
},
321+
{
322+
internalType: "bool",
323+
name: "ignoreSold",
324+
type: "bool"
325+
}
326+
],
327+
name: "bid",
328+
outputs: [],
329+
stateMutability: "payable",
330+
type: "function"
331+
}
332+
],
333+
address: contractAddress,
334+
functionName: "bid",
335+
args: [auctors, indexes, uptimes, prices, ignoreSold]
336+
});
337+
} catch (error) {
338+
console.error("Bulk bid failed:", error);
339+
throw error;
340+
}
341+
};
342+
246343
export const mpContractService = {
247344
transferERC20,
248345
getUserHash,
249346
changePrice,
250347
cancelAuction,
251348
createAuction,
252349
createAuctionBatch,
253-
bidAuction
350+
bidAuction,
351+
bidAuctions
254352
};

src/store/reducers/cartStorageReducer.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { CartAction } from "@/enum/enum";
22
import { AuctionDto } from "@/types/dtos/Auction.dto";
33

44
export interface CartItemListStorage {
5+
tokenId: Key | null | undefined;
56
id: string;
67
listing: AuctionDto;
78
}

src/utils/image/getImgUrl.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { images } from "./imgs";
22

33
export const getImgUrl = (prototype: number) => {
4-
// return "/images/defaultMomo.png";
4+
return "/images/defaultMomo.png";
55
const hash = images[prototype.toString() as keyof typeof images];
66
if (!hash) {
77
return "/images/defaultMomo.png";

0 commit comments

Comments
 (0)