diff --git a/apps/arkmarket/src/app/assets/[contract_address]/[token_id]/components/accept-offer.tsx b/apps/arkmarket/src/app/assets/[contract_address]/[token_id]/components/accept-offer.tsx index c233c80f..7fce5932 100644 --- a/apps/arkmarket/src/app/assets/[contract_address]/[token_id]/components/accept-offer.tsx +++ b/apps/arkmarket/src/app/assets/[contract_address]/[token_id]/components/accept-offer.tsx @@ -12,30 +12,38 @@ import { useAccount } from "@starknet-react/core"; import { areAddressesEqual } from "@ark-market/ui"; import { Button } from "@ark-market/ui/button"; -import type { Offer, Token, TokenMarketData } from "~/types"; import { env } from "~/env"; interface AcceptOfferProps { - token: Token; - tokenMarketData: TokenMarketData; - offer: Offer; + offerOrderHash: string; + offerAmount: string; + + tokenOwner: string; + tokenContractAddress: string; + tokenId: string; + tokenIsListed: boolean; + tokenListingOrderHash: string; } const AcceptOffer: React.FC = ({ - token, - tokenMarketData, - offer, + offerAmount, + offerOrderHash, + tokenIsListed, + tokenListingOrderHash, + tokenContractAddress, + tokenId, + tokenOwner, }) => { const { address, account } = useAccount(); const { fulfillOffer, status } = useFulfillOffer(); const { fulfill: fulfillAuction, status: statusAuction } = useFulfillAuction(); const type = useOrderType({ - orderHash: BigInt(tokenMarketData.order_hash), + orderHash: BigInt(tokenListingOrderHash), }); const isAuction = type === "AUCTION"; - const isOwner = areAddressesEqual(token.owner, address); - const isListed = tokenMarketData.is_listed; + const isOwner = areAddressesEqual(tokenOwner, address); + const isListed = tokenIsListed; if (!account || !isOwner) { return null; @@ -46,19 +54,19 @@ const AcceptOffer: React.FC = ({ await fulfillAuction({ starknetAccount: account, brokerId: env.NEXT_PUBLIC_BROKER_ID, - tokenAddress: token.contract_address, - tokenId: token.token_id, - orderHash: tokenMarketData.order_hash, - relatedOrderHash: offer.order_hash, - startAmount: offer.offer_amount, + tokenAddress: tokenContractAddress, + tokenId, + orderHash: tokenListingOrderHash, + relatedOrderHash: offerOrderHash, + startAmount: offerAmount, }); } else { await fulfillOffer({ starknetAccount: account, brokerId: env.NEXT_PUBLIC_BROKER_ID, - tokenAddress: token.contract_address, - tokenId: token.token_id, - orderHash: offer.order_hash, + tokenAddress: tokenContractAddress, + tokenId, + orderHash: offerOrderHash, }); } }; diff --git a/apps/arkmarket/src/app/assets/[contract_address]/[token_id]/components/cancel-offer.tsx b/apps/arkmarket/src/app/assets/[contract_address]/[token_id]/components/cancel-offer.tsx index 4e57dab2..92292f01 100644 --- a/apps/arkmarket/src/app/assets/[contract_address]/[token_id]/components/cancel-offer.tsx +++ b/apps/arkmarket/src/app/assets/[contract_address]/[token_id]/components/cancel-offer.tsx @@ -8,14 +8,17 @@ import { useAccount } from "@starknet-react/core"; import { Button } from "@ark-market/ui/button"; import { toast } from "@ark-market/ui/toast"; -import type { Offer, Token } from "~/types"; - interface CancelOfferProps { - token: Token; - offer: Offer; + tokenContractAddress: string; + tokenId: string; + offerOrderHash: string; } -const CancelOffer: React.FC = ({ token, offer }) => { +const CancelOffer = ({ + offerOrderHash, + tokenContractAddress, + tokenId, +}: CancelOfferProps) => { const { account } = useAccount(); const { cancel, status } = useCancel(); @@ -32,9 +35,9 @@ const CancelOffer: React.FC = ({ token, offer }) => { await cancel({ starknetAccount: account, - tokenAddress: token.contract_address, - tokenId: BigInt(token.token_id), - orderHash: BigInt(offer.order_hash), + tokenAddress: tokenContractAddress, + tokenId: BigInt(tokenId), + orderHash: BigInt(offerOrderHash), }); }; @@ -45,7 +48,7 @@ const CancelOffer: React.FC = ({ token, offer }) => { const isLoading = ["loading", "cancelling"].includes(status); return ( - ); diff --git a/apps/arkmarket/src/app/token/[contractAddress]/[tokenId]/components/token-actions-buttons.tsx b/apps/arkmarket/src/app/token/[contractAddress]/[tokenId]/components/token-actions-buttons.tsx index dc244c37..687be2b4 100644 --- a/apps/arkmarket/src/app/token/[contractAddress]/[tokenId]/components/token-actions-buttons.tsx +++ b/apps/arkmarket/src/app/token/[contractAddress]/[tokenId]/components/token-actions-buttons.tsx @@ -59,8 +59,8 @@ export default function TokenActionsButtons({ ) : ( )} diff --git a/apps/arkmarket/src/app/token/[contractAddress]/[tokenId]/components/token-actions-create-listing.tsx b/apps/arkmarket/src/app/token/[contractAddress]/[tokenId]/components/token-actions-create-listing.tsx index 2f5f0748..37f9a8ba 100644 --- a/apps/arkmarket/src/app/token/[contractAddress]/[tokenId]/components/token-actions-create-listing.tsx +++ b/apps/arkmarket/src/app/token/[contractAddress]/[tokenId]/components/token-actions-create-listing.tsx @@ -1,8 +1,9 @@ "use client"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { useCreateAuction, useCreateListing } from "@ark-project/react"; import { zodResolver } from "@hookform/resolvers/zod"; +import { ReloadIcon } from "@radix-ui/react-icons"; import { useAccount } from "@starknet-react/core"; import { List } from "lucide-react"; import moment from "moment"; @@ -25,8 +26,7 @@ import { FormLabel, FormMessage, } from "@ark-market/ui/form"; -import { Input } from "@ark-market/ui/input"; -import { RadioGroup, RadioGroupItem } from "@ark-market/ui/radio-group"; +import { NumericalInput } from "@ark-market/ui/numerical-input"; import { Select, SelectContent, @@ -34,51 +34,102 @@ import { SelectTrigger, SelectValue, } from "@ark-market/ui/select"; +import { toast } from "@ark-market/ui/toast"; -import type { Token, TokenMarketData } from "~/types"; -import TokenMedia from "~/app/assets/[contract_address]/[token_id]/components/token-media"; +import type { Collection, Token } from "~/types"; import { env } from "~/env"; +import formatAmount from "~/lib/formatAmount"; +import TokenActionsTokenOverview from "./token-actions-token-overview"; interface TokenActionsCreateListingProps { + collection: Collection; token: Token; - tokenMarketData?: TokenMarketData; } -const FIXED = "fixed"; -const AUCTION = "auction"; - -const formSchema = z.object({ - startAmount: z.string({ - invalid_type_error: "Please enter a valid amount", - }), - endAmount: z - .string({ - invalid_type_error: "Please enter a valid amount", - }) - .optional(), - duration: z.string(), - type: z.enum([FIXED, AUCTION]), -}); - export function TokenActionsCreateListing({ + collection, token, - // tokenMarketData, }: TokenActionsCreateListingProps) { const { account } = useAccount(); const [isOpen, setIsOpen] = useState(false); + const [isAuction, setIsAuction] = useState(false); const { createListing, status } = useCreateListing(); const { create: createAuction, status: auctionStatus } = useCreateAuction(); - const form = useForm>({ + const formSchema = z + .object({ + startAmount: z.string().refine( + (val) => { + const num = parseFloat(val); + return !isNaN(num) && num > 0; + }, + { + message: "Must be a valid amount", + }, + ), + endAmount: z + .string() + .refine( + (val) => { + const num = parseFloat(val); + + if (!isAuction) { + return true; + } + + return !isNaN(num); + }, + { + message: "Must be a valid amount", + }, + ) + .optional(), + duration: z.string(), + }) + .refine( + (data) => { + if (!isAuction) { + return true; + } + + if (data.endAmount !== undefined) { + const sa = parseFloat(data.startAmount); + const ea = parseFloat(data.endAmount); + return ea > sa; + } + return true; + }, + { + message: "Must be greater than start amount", + path: ["endAmount"], + }, + ); + + const form = useForm({ + mode: "all", resolver: zodResolver(formSchema), - mode: "onBlur", defaultValues: { - type: FIXED, - startAmount: "0.1", + startAmount: "", + endAmount: "", duration: "1", }, }); + useEffect(() => { + if (status === "error") { + setIsOpen(false); + toast.error("Your token listing failed."); + } else if (status === "success") { + setIsOpen(false); + toast.success("Your token is successfully listed."); + } + }, [status]); + + useEffect(() => { + setIsAuction(false); + form.reset(); + }, [form, isOpen]); + async function onSubmit(values: z.infer) { if (!account) { // TODO: Handle error with toast @@ -96,7 +147,7 @@ export function TokenActionsCreateListing({ }; try { - if (values.type === AUCTION) { + if (isAuction) { await createAuction({ starknetAccount: account, brokerId: env.NEXT_PUBLIC_BROKER_ID, @@ -116,28 +167,21 @@ export function TokenActionsCreateListing({ startAmount: processedValues.startAmount, }); } - - // queryClient.setQueryData( - // ["tokenMarketData", token.contract_address, token.token_id], - // { - // ...tokenMarketData, - // is_listed: true, - // type: values.type === AUCTION ? "AUCTION" , - // start_amount: processedValues.startAmount, - // end_amount: processedValues.endAmount, - // end_date: processedValues.endDate, - // }, - // ); } catch (error) { console.error("error: create listing failed", error); } } - const isAuction = form.getValues("type") === AUCTION; - // const duration = form.watch("duration"); - // const expiredAt = moment().add(duration, "hours").format("LLLL"); + const startAmount = form.watch("startAmount"); + const formattedStartAmount = formatAmount(startAmount); const isLoading = status === "loading" || auctionStatus === "loading"; + const isDisabled = + !form.formState.isValid || + form.formState.isSubmitting || + status === "loading" || + auctionStatus === "loading"; + return ( @@ -147,82 +191,69 @@ export function TokenActionsCreateListing({ - - {/* List for sale */} - -
+ +
List for sale
-
-
- -
-
-
Duo #{token.token_id}
-
Everai
-
-
-
- +
- ( - - Choose a type of sale - - - - - Fixed price - - The item is listed at the price you set. - - - - - - - - - - Sell to highest bidder - - - The item is listed for auction. - - - - - - - - - - - )} - /> + + Type of sale +
+ + +
+
+ ( Set starting price + - + - + {formattedStartAmount !== "-" && } )} /> @@ -234,7 +265,10 @@ export function TokenActionsCreateListing({ Set reserve price - + @@ -246,10 +280,7 @@ export function TokenActionsCreateListing({ name="duration" render={({ field }) => ( -
- Set expiration - {/*
Expires {expiredAt}
*/} -
+ Set expiration