Skip to content

Commit

Permalink
feat(currency): use marketplace currency data (#202)
Browse files Browse the repository at this point in the history
  • Loading branch information
gershon authored Nov 19, 2024
1 parent eba5838 commit d1513e1
Show file tree
Hide file tree
Showing 13 changed files with 183 additions and 121 deletions.
1 change: 1 addition & 0 deletions apps/arkmarket/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@ark-market/ui": "workspace:*",
"@ark-project/core": "^2.1.1",
"@ark-project/react": "^1.1.1",
"@avnu/avnu-sdk": "^2.1.1",
"@hookform/error-message": "^2.0.1",
"@starknet-react/chains": "^0.1.7",
"@starknet-react/core": "^2.0.0",
Expand Down
102 changes: 58 additions & 44 deletions apps/arkmarket/src/__tests__/buy-now-dialog.test.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,45 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-explicit-any */
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import { vi, describe, it, expect } from "vitest";
import { fireEvent, render, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";

import type { CollectionToken, Token } from "~/types";
import { ETH } from "~/constants/tokens";
import BuyNowDialog from "../components/buy-now-dialog";
import type { Token, CollectionToken } from "~/types";

vi.mock('@ark-market/ui/button', () => ({
Button: ({ children, onClick }: any) => <button onClick={onClick}>{children}</button>,
vi.mock("@ark-market/ui/button", () => ({
Button: ({ children, onClick }: any) => (
<button onClick={onClick}>{children}</button>
),
}));

vi.mock('@ark-market/ui/dialog', () => ({
Dialog: ({ children, open }: any) => open ? <div>{children}</div> : null,
vi.mock("@ark-market/ui/dialog", () => ({
Dialog: ({ children, open }: any) => (open ? <div>{children}</div> : null),
DialogContent: ({ children }: any) => <div>{children}</div>,
DialogTitle: ({ children }: any) => <h2>{children}</h2>,
}));

vi.mock('@ark-market/ui/icons', () => ({
vi.mock("@ark-market/ui/icons", () => ({
LoaderCircle: () => <div>LoaderCircle</div>,
NoListing: () => <div>NoListing</div>,
Success: () => <div>Success</div>,
}));

vi.mock('~/app/token/[contractAddress]/[tokenId]/components/token-actions-token-overview', () => ({
default: () => <div>TokenActionsTokenOverview</div>,
}));

vi.mock(
"~/app/token/[contractAddress]/[tokenId]/components/token-actions-token-overview",
() => ({
default: () => <div>TokenActionsTokenOverview</div>,
}),
);

describe("BuyNowDialog", () => {
const mockToken: Token = {
collection_image: "https://example.com/image.jpg",
collection_name: "Test Collection",
collection_address: '0x1234567890123456789012345678901234567890',
owner: '0x0987654321098765432109876543210987654321',
token_id: '1',
last_price: '1000000000000000000',
collection_address: "0x1234567890123456789012345678901234567890",
owner: "0x0987654321098765432109876543210987654321",
token_id: "1",
last_price: "1000000000000000000",
price: "1500000000000000000",
metadata: {
name: "Test Token",
Expand All @@ -48,18 +53,23 @@ describe("BuyNowDialog", () => {

const mockCollectionToken: CollectionToken = {
buy_in_progress: false,
collection_address: '0x1234567890123456789012345678901234567890',
collection_name: 'Test Collection',
collection_address: "0x1234567890123456789012345678901234567890",
collection_name: "Test Collection",
floor_difference: 10,
is_listed: true,
listed_at: Date.now(),
listing: {
is_auction: false,
currency: {
contract: ETH,
decimals: 18,
symbol: "ETH",
},
},
owner: '0x0987654321098765432109876543210987654321',
token_id: '2',
last_price: '1000000000000000000',
price: '1500000000000000000',
owner: "0x0987654321098765432109876543210987654321",
token_id: "2",
last_price: "1000000000000000000",
price: "1500000000000000000",
metadata: {
name: "Test Collection Token",
image: "https://example.com/collection-token-image.jpg",
Expand All @@ -72,55 +82,59 @@ describe("BuyNowDialog", () => {

const mockSetIsOpen = vi.fn();

it('renders the dialog for Token when open', () => {
it("renders the dialog for Token when open", () => {
render(
<BuyNowDialog
isOpen={true}
setIsOpen={mockSetIsOpen}
isSuccess={false}
token={mockToken}
price="1500000000000000000"
/>
/>,
);

expect(screen.getByText('Confirm your purchase')).toBeInTheDocument();
expect(screen.getByText('NoListing')).toBeInTheDocument();
expect(screen.getByText('TokenActionsTokenOverview')).toBeInTheDocument();
expect(screen.getByText('Checking your payment')).toBeInTheDocument();
expect(screen.getByText("Confirm your purchase")).toBeInTheDocument();
expect(screen.getByText("NoListing")).toBeInTheDocument();
expect(screen.getByText("TokenActionsTokenOverview")).toBeInTheDocument();
expect(screen.getByText("Checking your payment")).toBeInTheDocument();
});

it('renders the dialog for CollectionToken when open', () => {
it("renders the dialog for CollectionToken when open", () => {
render(
<BuyNowDialog
isOpen={true}
setIsOpen={mockSetIsOpen}
isSuccess={false}
token={mockCollectionToken}
price="1500000000000000000"
/>
/>,
);

expect(screen.getByText('Confirm your purchase')).toBeInTheDocument();
expect(screen.getByText('NoListing')).toBeInTheDocument();
expect(screen.getByText('TokenActionsTokenOverview')).toBeInTheDocument();
expect(screen.getByText('Checking your payment')).toBeInTheDocument();
expect(screen.getByText("Confirm your purchase")).toBeInTheDocument();
expect(screen.getByText("NoListing")).toBeInTheDocument();
expect(screen.getByText("TokenActionsTokenOverview")).toBeInTheDocument();
expect(screen.getByText("Checking your payment")).toBeInTheDocument();
});

it('renders success state correctly', () => {
it("renders success state correctly", () => {
render(
<BuyNowDialog
isOpen={true}
setIsOpen={mockSetIsOpen}
isSuccess={true}
token={mockToken}
price="1500000000000000000"
/>
/>,
);

expect(screen.getByText('Congratulations for your purchase')).toBeInTheDocument();
expect(screen.getByText('Success')).toBeInTheDocument();
expect(screen.getByText('Nice purchase, this NFT is now in your wallet ;)')).toBeInTheDocument();
expect(screen.getByText('Continue to explore NFTs')).toBeInTheDocument();
expect(
screen.getByText("Congratulations for your purchase"),
).toBeInTheDocument();
expect(screen.getByText("Success")).toBeInTheDocument();
expect(
screen.getByText("Nice purchase, this NFT is now in your wallet ;)"),
).toBeInTheDocument();
expect(screen.getByText("Continue to explore NFTs")).toBeInTheDocument();
});

it('calls setIsOpen when "Continue to explore NFTs" button is clicked', () => {
Expand All @@ -131,10 +145,10 @@ describe("BuyNowDialog", () => {
isSuccess={true}
token={mockToken}
price="1500000000000000000"
/>
/>,
);

fireEvent.click(screen.getByText('Continue to explore NFTs'));
fireEvent.click(screen.getByText("Continue to explore NFTs"));
expect(mockSetIsOpen).toHaveBeenCalledWith(false);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,7 @@ export default function CollectionItemsBuyNow({
return;
}

if (
!data ||
data.value < BigInt(tokenMarketData.listing.start_amount ?? 0)
) {
if (!data || data.value < BigInt(tokenMarketData.listing.start_amount)) {
sonner.error("Insufficient balance");
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,9 @@ export default function CollectionItemsDataGridView({
)}
>
{formatUnits(token.price, 18)}{" "}
<span className="text-muted-foreground">ETH</span>
<span className="text-muted-foreground">
{token.listing.currency.symbol}
</span>
</div>
) : (
<div
Expand All @@ -129,7 +131,8 @@ export default function CollectionItemsDataGridView({
{token.last_price ? (
<>
Last {viewType === "large-grid" ? "sale" : ""}{" "}
{formatUnits(token.last_price, 18)} ETH
{formatUnits(token.last_price, 18)}{" "}
{token.listing.currency.symbol}
</>
) : null}
</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ export default function TokenActionsCancelListing({
title: "The listing could not be canceled",
additionalContent: (
<ToastRejectedTransactionContent
price={BigInt(tokenMarketData.listing.start_amount ?? 0)}
price={BigInt(tokenMarketData.listing.start_amount)}
formattedPrice={formatEther(
BigInt(tokenMarketData.listing.start_amount ?? 0),
BigInt(tokenMarketData.listing.start_amount),
)}
collectionName={token.collection_name}
tokenId={token.token_id}
Expand All @@ -53,9 +53,9 @@ export default function TokenActionsCancelListing({
title: "Your listing is successfully canceled",
additionalContent: (
<ToastExecutedTransactionContent
price={BigInt(tokenMarketData.listing.start_amount ?? 0)}
price={BigInt(tokenMarketData.listing.start_amount)}
formattedPrice={formatEther(
BigInt(tokenMarketData.listing.start_amount ?? 0),
BigInt(tokenMarketData.listing.start_amount),
)}
collectionName={token.collection_name}
tokenId={token.token_id}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,7 @@ export default function TokenActionsMakeBid({
mode: "all",
resolver: zodResolver(formSchema),
defaultValues: {
startAmount: formatEther(
BigInt(tokenMarketData.listing.start_amount ?? 0),
),
startAmount: formatEther(BigInt(tokenMarketData.listing.start_amount)),
duration: "719",
endDateTime: moment().add(1, "month").toDate(),
},
Expand All @@ -122,7 +120,7 @@ export default function TokenActionsMakeBid({
title: "Your bid could not be submitted",
additionalContent: (
<ToastRejectedTransactionContent
price={BigInt(tokenMarketData.listing.start_amount ?? 0)}
price={BigInt(tokenMarketData.listing.start_amount)}
formattedPrice={startAmount}
collectionName={token.collection_name}
tokenId={token.token_id}
Expand All @@ -137,7 +135,7 @@ export default function TokenActionsMakeBid({
title: "Your bid has been sucessfullly sent",
additionalContent: (
<ToastExecutedTransactionContent
price={BigInt(tokenMarketData.listing.start_amount ?? 0)}
price={BigInt(tokenMarketData.listing.start_amount)}
formattedPrice={startAmount}
collectionName={token.collection_name}
tokenId={token.token_id}
Expand Down Expand Up @@ -176,7 +174,7 @@ export default function TokenActionsMakeBid({
!form.formState.isValid || form.formState.isSubmitting || isLoading;
const startAmount = form.watch("startAmount");
const formattedStartAmount = formatAmount(startAmount);
const price = formatEther(BigInt(tokenMarketData.listing.start_amount ?? 0));
const price = formatEther(BigInt(tokenMarketData.listing.start_amount));
const reservePrice = formatEther(
BigInt(tokenMarketData.listing.end_amount ?? 0),
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,42 +1,32 @@
"use client";

import { formatEther } from "viem";
import { formatUnits } from "viem";

import { cn, ellipsableStyles } from "@ark-market/ui";

import usePrices from "~/hooks/usePrices";
import type { TokenMarketData } from "~/types";
import useUsdPrice from "~/hooks/useUsdPrice";

interface TokenActionsPriceProps {
startAmount: string | null;
isListed: boolean;
isAuction: boolean;
hasOffer: boolean;
topOffer: {
amount: string;
order_hash: string;
};
tokenMarketData: TokenMarketData;
}

export default function TokenActionsPrice({
startAmount,
isAuction,
isListed,
topOffer,
tokenMarketData,
}: TokenActionsPriceProps) {
const { convertInUsd } = usePrices();
const amountHex = isListed ? startAmount : topOffer.amount;
const amount = formatEther(BigInt(amountHex ?? 0));
const amountInUsd = convertInUsd({ amount: BigInt(amountHex ?? 0) });

let label = "Best offer";

if (isListed) {
if (isAuction) {
label = "Minimum starting price";
} else {
label = "Current Price";
}
}
const label = tokenMarketData.is_listed
? tokenMarketData.listing.is_auction
? "Minimum starting price"
: "Current Price"
: "Best offer";
const amount = tokenMarketData.is_listed
? BigInt(tokenMarketData.listing.start_amount)
: BigInt(tokenMarketData.top_offer.amount);
const currency = tokenMarketData.is_listed
? tokenMarketData.listing.currency
: tokenMarketData.top_offer.currency;
const usdPrice = useUsdPrice({ amount, currency });
const amountFormatted = formatUnits(amount, currency.decimals);

return (
<div className="mb-6">
Expand All @@ -48,12 +38,12 @@ export default function TokenActionsPrice({
ellipsableStyles,
)}
>
{amount} ETH
{amountFormatted} {currency.symbol}
</div>
<div className="text-lg font-semibold leading-none text-muted-foreground lg:text-2xl">
${amountInUsd}
${usdPrice}
</div>
<div className="flex items-center whitespace-nowrap rounded-full bg-secondary px-2 px-3 py-1 text-xs font-semibold text-secondary-foreground lg:text-sm">
<div className="flex items-center whitespace-nowrap rounded-full bg-secondary px-3 py-1 text-xs font-semibold text-secondary-foreground lg:text-sm">
Royalties 5%
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,7 @@ export default function TokenActions({
isAuction={data.listing.is_auction}
expiresAt={data.listing.end_date}
/>
<TokenActionsPrice
startAmount={data.listing.start_amount}
isListed={data.is_listed}
isAuction={data.listing.is_auction}
hasOffer={data.has_offer}
topOffer={data.top_offer}
/>
<TokenActionsPrice tokenMarketData={data} />
<TokenActionsButtons
isListed={data.is_listed}
isAuction={data.listing.is_auction}
Expand Down
Loading

0 comments on commit d1513e1

Please sign in to comment.