@0xsequence/marketplace-sdk 0.4.7 → 0.4.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-6R4G7J6Q.js → chunk-7HWJ4DUX.js} +29 -5
- package/dist/chunk-7HWJ4DUX.js.map +1 -0
- package/dist/{chunk-5UCKYAMR.js → chunk-BJLOO4NP.js} +647 -376
- package/dist/chunk-BJLOO4NP.js.map +1 -0
- package/dist/{chunk-2OJB35FS.js → chunk-BMWCIHCB.js} +5 -5
- package/dist/{chunk-2OJB35FS.js.map → chunk-BMWCIHCB.js.map} +1 -1
- package/dist/{chunk-R7GVMKMM.js → chunk-CUA4SGWT.js} +540 -18
- package/dist/chunk-CUA4SGWT.js.map +1 -0
- package/dist/{chunk-ZEH4JI2U.js → chunk-FCF57DZI.js} +7 -2
- package/dist/chunk-FCF57DZI.js.map +1 -0
- package/dist/{chunk-JEOUQFT3.js → chunk-TFCSNRD5.js} +4 -3
- package/dist/chunk-TFCSNRD5.js.map +1 -0
- package/dist/{chunk-AQT3BQ67.js → chunk-Y3KINNAU.js} +12 -5
- package/dist/chunk-Y3KINNAU.js.map +1 -0
- package/dist/{create-config-D5WqfUft.d.ts → create-config-CgYQDyMD.d.ts} +2 -2
- package/dist/index.css +31 -17
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/{marketplace-config-C_fDWzz0.d.ts → marketplace-config-BP5-XnFG.d.ts} +1 -1
- package/dist/{marketplace.gen-B8S8fflj.d.ts → marketplace.gen-BzmWLP9L.d.ts} +14 -4
- package/dist/react/_internal/api/index.d.ts +2 -2
- package/dist/react/_internal/api/index.js +3 -1
- package/dist/react/_internal/index.d.ts +5 -5
- package/dist/react/_internal/index.js +4 -2
- package/dist/react/_internal/wagmi/index.d.ts +3 -3
- package/dist/react/_internal/wagmi/index.js +1 -1
- package/dist/react/hooks/index.d.ts +46 -5
- package/dist/react/hooks/index.js +12 -5
- package/dist/react/index.css +37 -17
- package/dist/react/index.css.map +1 -1
- package/dist/react/index.d.ts +6 -6
- package/dist/react/index.js +15 -8
- package/dist/react/ssr/index.js +6 -1
- package/dist/react/ssr/index.js.map +1 -1
- package/dist/react/ui/components/collectible-card/index.css +37 -17
- package/dist/react/ui/components/collectible-card/index.css.map +1 -1
- package/dist/react/ui/components/collectible-card/index.d.ts +27 -4
- package/dist/react/ui/components/collectible-card/index.js +7 -8
- package/dist/react/ui/index.css +37 -17
- package/dist/react/ui/index.css.map +1 -1
- package/dist/react/ui/index.d.ts +3 -3
- package/dist/react/ui/index.js +7 -8
- package/dist/react/ui/modals/_internal/components/actionModal/index.d.ts +3 -3
- package/dist/react/ui/modals/_internal/components/actionModal/index.js +5 -5
- package/dist/{sdk-config-BXVH8PS2.d.ts → sdk-config-LbbmA85k.d.ts} +27 -7
- package/dist/{services-CdXAIjt1.d.ts → services-DW26ougH.d.ts} +1 -1
- package/dist/styles/index.css +31 -17
- package/dist/styles/index.css.map +1 -1
- package/dist/styles/index.d.ts +2 -2
- package/dist/styles/index.js +1 -1
- package/dist/types/index.d.ts +3 -3
- package/dist/types/index.js +2 -2
- package/dist/{types-eX4P9xju.d.ts → types-DhTZWw1U.d.ts} +2 -2
- package/dist/utils/index.d.ts +3 -3
- package/dist/utils/index.js +3 -3
- package/package.json +3 -2
- package/src/react/__tests__/provider.test.tsx +75 -0
- package/src/react/_internal/api/__mocks__/marketplace.msw.ts +4 -0
- package/src/react/_internal/api/marketplace.gen.ts +42 -7
- package/src/react/_internal/wagmi/__tests__/create-config.test.ts +196 -0
- package/src/react/_internal/wagmi/create-config.ts +9 -1
- package/src/react/_internal/wallet/useWallet.ts +30 -46
- package/src/react/_internal/wallet/wallet.ts +52 -6
- package/src/react/hooks/index.ts +2 -0
- package/src/react/hooks/options/__mocks__/marketplaceConfig.msw.ts +10 -1
- package/src/react/hooks/useCollectionDetails.tsx +35 -0
- package/src/react/hooks/useCollectionDetailsPolling.tsx +60 -0
- package/src/react/provider.tsx +5 -0
- package/src/react/ui/components/_internals/action-button/ActionButton.tsx +36 -118
- package/src/react/ui/components/_internals/action-button/components/ActionButtonBody.tsx +52 -0
- package/src/react/ui/components/_internals/action-button/components/NonOwnerActions.tsx +72 -0
- package/src/react/ui/components/_internals/action-button/components/OwnerActions.tsx +81 -0
- package/src/react/ui/components/_internals/action-button/hooks/useActionButtonLogic.ts +93 -0
- package/src/react/ui/components/_internals/action-button/store.ts +47 -0
- package/src/react/ui/components/_internals/action-button/styles.css.ts +8 -0
- package/src/react/ui/components/collectible-card/CollectibleCard.tsx +34 -14
- package/src/react/ui/components/collectible-card/Footer.tsx +9 -9
- package/src/react/ui/components/collectible-card/styles.css.ts +44 -31
- package/src/react/ui/icons/CartIcon.tsx +46 -0
- package/src/react/ui/modals/BuyModal/Modal.tsx +11 -4
- package/src/react/ui/modals/BuyModal/__tests__/Modal.test.tsx +253 -0
- package/src/react/ui/modals/BuyModal/__tests__/store.test.ts +100 -0
- package/src/react/ui/modals/BuyModal/hooks/__tests__/useBuyCollectable.test.tsx +402 -0
- package/src/react/ui/modals/BuyModal/hooks/__tests__/useCheckoutOptions.test.tsx +267 -0
- package/src/react/ui/modals/BuyModal/hooks/__tests__/useFees.test.tsx +166 -0
- package/src/react/ui/modals/BuyModal/hooks/__tests__/useLoadData.test.tsx +209 -0
- package/src/react/ui/modals/BuyModal/hooks/useCheckoutOptions.ts +19 -17
- package/src/react/ui/modals/BuyModal/hooks/useLoadData.ts +9 -7
- package/src/react/ui/modals/BuyModal/modals/CheckoutModal.tsx +7 -0
- package/src/react/ui/modals/BuyModal/modals/Modal1155.tsx +36 -18
- package/src/react/ui/modals/BuyModal/modals/__tests__/CheckoutModal.test.tsx +162 -0
- package/src/react/ui/modals/BuyModal/modals/__tests__/Modal1155.test.tsx +243 -0
- package/src/react/ui/modals/BuyModal/store.ts +11 -10
- package/src/react/ui/modals/CreateListingModal/Modal.tsx +26 -3
- package/src/react/ui/modals/CreateListingModal/__tests__/Modal.test.tsx +141 -29
- package/src/react/ui/modals/SuccessfulPurchaseModal/__tests__/Modal.test.tsx +145 -0
- package/src/react/ui/modals/_internal/components/actionModal/ActionModal.tsx +20 -11
- package/src/react/ui/modals/_internal/components/currencyOptionsSelect/__tests__/index.test.tsx +13 -58
- package/src/react/ui/modals/_internal/components/priceInput/__tests__/index.test.tsx +2 -0
- package/src/react/ui/modals/_internal/components/transactionStatusModal/__tests__/TransactionStatusModal.test.tsx +18 -19
- package/src/react/ui/modals/_internal/components/transactionStatusModal/__tests__/utils.test.ts +2 -0
- package/src/react/ui/modals/_internal/components/transactionStatusModal/hooks/useTransactionStatus.ts +62 -0
- package/src/react/ui/modals/_internal/components/transactionStatusModal/index.tsx +53 -100
- package/src/react/ui/modals/_internal/components/transactionStatusModal/store.ts +2 -10
- package/src/utils/_internal/error/config.ts +16 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/chunk-5UCKYAMR.js.map +0 -1
- package/dist/chunk-6R4G7J6Q.js.map +0 -1
- package/dist/chunk-AQT3BQ67.js.map +0 -1
- package/dist/chunk-FWN2MCLI.js +0 -425
- package/dist/chunk-FWN2MCLI.js.map +0 -1
- package/dist/chunk-JEOUQFT3.js.map +0 -1
- package/dist/chunk-R7GVMKMM.js.map +0 -1
- package/dist/chunk-ZEH4JI2U.js.map +0 -1
|
@@ -4,19 +4,21 @@ import { useCollectible, useCollection } from '../../../../hooks';
|
|
|
4
4
|
|
|
5
5
|
import { useCheckoutOptions } from './useCheckoutOptions';
|
|
6
6
|
|
|
7
|
+
export type UseLoadDataProps = {
|
|
8
|
+
chainId: number;
|
|
9
|
+
collectionAddress: Hex;
|
|
10
|
+
collectibleId: string;
|
|
11
|
+
orderId: string;
|
|
12
|
+
marketplace: MarketplaceKind;
|
|
13
|
+
};
|
|
14
|
+
|
|
7
15
|
export const useLoadData = ({
|
|
8
16
|
chainId,
|
|
9
17
|
collectionAddress,
|
|
10
18
|
collectibleId,
|
|
11
19
|
orderId,
|
|
12
20
|
marketplace,
|
|
13
|
-
}: {
|
|
14
|
-
chainId: number;
|
|
15
|
-
collectionAddress: Hex;
|
|
16
|
-
collectibleId: string;
|
|
17
|
-
orderId: string;
|
|
18
|
-
marketplace: MarketplaceKind;
|
|
19
|
-
}) => {
|
|
21
|
+
}: UseLoadDataProps) => {
|
|
20
22
|
const {
|
|
21
23
|
data: collection,
|
|
22
24
|
isLoading: collectionLoading,
|
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
MarketplaceKind,
|
|
6
6
|
TokenMetadata,
|
|
7
7
|
} from '../../../../_internal/api/marketplace.gen';
|
|
8
|
+
import { buyModal$ } from '../store';
|
|
8
9
|
|
|
9
10
|
export interface BuyInput {
|
|
10
11
|
orderId: string;
|
|
@@ -22,6 +23,12 @@ export interface CheckoutModalProps {
|
|
|
22
23
|
export function CheckoutModal({ buy, collectable, order }: CheckoutModalProps) {
|
|
23
24
|
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
|
|
24
25
|
useEffect(() => {
|
|
26
|
+
if (
|
|
27
|
+
buyModal$.state.checkoutModalIsLoading.get() ||
|
|
28
|
+
buyModal$.state.checkoutModalLoaded.get()
|
|
29
|
+
)
|
|
30
|
+
return;
|
|
31
|
+
|
|
25
32
|
const executeBuy = () => {
|
|
26
33
|
buy({
|
|
27
34
|
orderId: order.orderId,
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { Box, Text, TokenImage } from '@0xsequence/design-system';
|
|
2
2
|
import { observer } from '@legendapp/state/react';
|
|
3
|
-
import type {
|
|
3
|
+
import type { Hex } from 'viem';
|
|
4
4
|
import { formatUnits, parseUnits } from 'viem';
|
|
5
|
-
import {
|
|
6
|
-
import { useCurrencyOptions } from '../../../../hooks/useCurrencyOptions';
|
|
5
|
+
import { useCurrency } from '../../../../hooks';
|
|
7
6
|
import QuantityInput from '../../_internal/components/quantityInput';
|
|
8
7
|
import { ActionModal } from '../../_internal/components/actionModal';
|
|
9
8
|
import { buyModal$ } from '../store';
|
|
@@ -17,23 +16,20 @@ interface ERC1155QuantityModalProps extends CheckoutModalProps {
|
|
|
17
16
|
|
|
18
17
|
export const ERC1155QuantityModal = observer(
|
|
19
18
|
({ buy, collectable, order }: ERC1155QuantityModalProps) => {
|
|
20
|
-
const
|
|
21
|
-
collectionAddress: order.collectionContractAddress as Address,
|
|
22
|
-
});
|
|
23
|
-
const { data: currencies } = useCurrencies({
|
|
19
|
+
const { data: currency, isLoading: isCurrencyLoading } = useCurrency({
|
|
24
20
|
chainId: order.chainId,
|
|
25
|
-
|
|
21
|
+
currencyAddress: order.priceCurrencyAddress,
|
|
26
22
|
});
|
|
27
23
|
|
|
28
|
-
const currency = currencies?.find(
|
|
29
|
-
(currency) => currency.contractAddress === order.priceCurrencyAddress,
|
|
30
|
-
);
|
|
31
|
-
|
|
32
24
|
const quantity = Number(buyModal$.state.quantity.get());
|
|
33
25
|
const pricePerToken = order.priceAmount;
|
|
34
26
|
const totalPrice = (BigInt(quantity) * BigInt(pricePerToken)).toString();
|
|
35
27
|
|
|
36
|
-
if (
|
|
28
|
+
if (
|
|
29
|
+
buyModal$.state.checkoutModalLoaded.get() &&
|
|
30
|
+
buyModal$.isOpen.get() &&
|
|
31
|
+
buyModal$.state.checkoutModalIsLoading.get()
|
|
32
|
+
) {
|
|
37
33
|
return null;
|
|
38
34
|
}
|
|
39
35
|
|
|
@@ -72,14 +68,36 @@ export const ERC1155QuantityModal = observer(
|
|
|
72
68
|
maxQuantity={order.quantityRemaining}
|
|
73
69
|
/>
|
|
74
70
|
<Box display="flex" justifyContent="space-between">
|
|
75
|
-
<Text color="text50" fontSize="small">
|
|
71
|
+
<Text color="text50" fontSize="small" fontFamily="body">
|
|
76
72
|
Total Price
|
|
77
73
|
</Text>
|
|
78
74
|
<Box display="flex" alignItems="center" gap="2">
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
75
|
+
{isCurrencyLoading || !currency ? (
|
|
76
|
+
<Box display="flex" alignItems="center" gap="2">
|
|
77
|
+
<Text color="text50" fontSize="small" fontFamily="body">
|
|
78
|
+
Loading...
|
|
79
|
+
</Text>
|
|
80
|
+
</Box>
|
|
81
|
+
) : (
|
|
82
|
+
<>
|
|
83
|
+
{currency.imageUrl && (
|
|
84
|
+
<TokenImage src={currency.imageUrl} size="xs" />
|
|
85
|
+
)}
|
|
86
|
+
|
|
87
|
+
<Text
|
|
88
|
+
color="text100"
|
|
89
|
+
fontSize="small"
|
|
90
|
+
fontWeight="bold"
|
|
91
|
+
fontFamily="body"
|
|
92
|
+
>
|
|
93
|
+
{formatUnits(BigInt(totalPrice), currency.decimals || 0)}
|
|
94
|
+
</Text>
|
|
95
|
+
|
|
96
|
+
<Text color="text80" fontSize="small" fontFamily="body">
|
|
97
|
+
{currency?.symbol}
|
|
98
|
+
</Text>
|
|
99
|
+
</>
|
|
100
|
+
)}
|
|
83
101
|
</Box>
|
|
84
102
|
</Box>
|
|
85
103
|
</Box>
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { render, waitFor } from '@testing-library/react';
|
|
2
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
3
|
+
import { CheckoutModal } from '../CheckoutModal';
|
|
4
|
+
import { parseUnits } from 'viem';
|
|
5
|
+
import type { Order, TokenMetadata } from '../../../../../_internal';
|
|
6
|
+
import {
|
|
7
|
+
MarketplaceKind,
|
|
8
|
+
OrderSide,
|
|
9
|
+
OrderStatus,
|
|
10
|
+
} from '../../../../../_internal';
|
|
11
|
+
|
|
12
|
+
describe('CheckoutModal', () => {
|
|
13
|
+
const mockBuy = vi.fn();
|
|
14
|
+
|
|
15
|
+
const defaultOrder: Order = {
|
|
16
|
+
orderId: '123',
|
|
17
|
+
marketplace: MarketplaceKind.sequence_marketplace_v2,
|
|
18
|
+
priceAmount: '1000000000000000000',
|
|
19
|
+
priceCurrencyAddress: '0x0',
|
|
20
|
+
quantityRemaining: '1',
|
|
21
|
+
priceUSDFormatted: '$1,800.00',
|
|
22
|
+
createdAt: new Date().toISOString(),
|
|
23
|
+
side: OrderSide.listing,
|
|
24
|
+
status: OrderStatus.active,
|
|
25
|
+
chainId: 1,
|
|
26
|
+
originName: 'test',
|
|
27
|
+
collectionContractAddress: '0x1234567890123456789012345678901234567890',
|
|
28
|
+
tokenId: '1',
|
|
29
|
+
createdBy: '0x1234567890123456789012345678901234567890',
|
|
30
|
+
priceAmountFormatted: '1.0',
|
|
31
|
+
priceAmountNet: '950000000000000000',
|
|
32
|
+
priceAmountNetFormatted: '0.95',
|
|
33
|
+
priceDecimals: 18,
|
|
34
|
+
priceUSD: 1800.0,
|
|
35
|
+
quantityInitial: '1',
|
|
36
|
+
quantityInitialFormatted: '1',
|
|
37
|
+
quantityRemainingFormatted: '1',
|
|
38
|
+
quantityAvailable: '1',
|
|
39
|
+
quantityAvailableFormatted: '1',
|
|
40
|
+
quantityDecimals: 0,
|
|
41
|
+
feeBps: 500,
|
|
42
|
+
feeBreakdown: [],
|
|
43
|
+
validFrom: new Date().toISOString(),
|
|
44
|
+
validUntil: new Date(Date.now() + 86400000).toISOString(),
|
|
45
|
+
blockNumber: 1234567,
|
|
46
|
+
updatedAt: new Date().toISOString(),
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const defaultCollectable: TokenMetadata = {
|
|
50
|
+
tokenId: '1',
|
|
51
|
+
name: 'Test NFT',
|
|
52
|
+
description: 'Test Description',
|
|
53
|
+
image: 'https://test.com/image.png',
|
|
54
|
+
attributes: [],
|
|
55
|
+
decimals: 0,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
beforeEach(() => {
|
|
59
|
+
vi.clearAllMocks();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should auto-execute buy function when mounted', async () => {
|
|
63
|
+
render(
|
|
64
|
+
<CheckoutModal
|
|
65
|
+
buy={mockBuy}
|
|
66
|
+
order={defaultOrder}
|
|
67
|
+
collectable={defaultCollectable}
|
|
68
|
+
/>,
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
await waitFor(() => {
|
|
72
|
+
expect(mockBuy).toHaveBeenCalledTimes(1);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should execute buy even when loading (current implementation)', async () => {
|
|
77
|
+
render(
|
|
78
|
+
<CheckoutModal
|
|
79
|
+
buy={mockBuy}
|
|
80
|
+
order={defaultOrder}
|
|
81
|
+
collectable={defaultCollectable}
|
|
82
|
+
isLoading={true}
|
|
83
|
+
/>,
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
await waitFor(() => {
|
|
87
|
+
expect(mockBuy).toHaveBeenCalledTimes(1);
|
|
88
|
+
expect(mockBuy).toHaveBeenCalledWith({
|
|
89
|
+
orderId: defaultOrder.orderId,
|
|
90
|
+
collectableDecimals: defaultCollectable.decimals || 0,
|
|
91
|
+
quantity: '1',
|
|
92
|
+
marketplace: defaultOrder.marketplace,
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should call buy with correct parameters', async () => {
|
|
98
|
+
render(
|
|
99
|
+
<CheckoutModal
|
|
100
|
+
buy={mockBuy}
|
|
101
|
+
order={defaultOrder}
|
|
102
|
+
collectable={defaultCollectable}
|
|
103
|
+
/>,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
await waitFor(() => {
|
|
107
|
+
expect(mockBuy).toHaveBeenCalledWith({
|
|
108
|
+
orderId: defaultOrder.orderId,
|
|
109
|
+
collectableDecimals: defaultCollectable.decimals || 0,
|
|
110
|
+
quantity: '1',
|
|
111
|
+
marketplace: defaultOrder.marketplace,
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should handle decimals correctly', async () => {
|
|
117
|
+
const collectableWithDecimals: TokenMetadata = {
|
|
118
|
+
...defaultCollectable,
|
|
119
|
+
decimals: 18,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
render(
|
|
123
|
+
<CheckoutModal
|
|
124
|
+
buy={mockBuy}
|
|
125
|
+
order={defaultOrder}
|
|
126
|
+
collectable={collectableWithDecimals}
|
|
127
|
+
/>,
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
await waitFor(() => {
|
|
131
|
+
expect(mockBuy).toHaveBeenCalledWith({
|
|
132
|
+
orderId: defaultOrder.orderId,
|
|
133
|
+
collectableDecimals: collectableWithDecimals.decimals,
|
|
134
|
+
quantity: parseUnits('1', 18).toString(),
|
|
135
|
+
marketplace: defaultOrder.marketplace,
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Test with different decimal values
|
|
140
|
+
const collectableWithCustomDecimals: TokenMetadata = {
|
|
141
|
+
...defaultCollectable,
|
|
142
|
+
decimals: 6,
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
render(
|
|
146
|
+
<CheckoutModal
|
|
147
|
+
buy={mockBuy}
|
|
148
|
+
order={defaultOrder}
|
|
149
|
+
collectable={collectableWithCustomDecimals}
|
|
150
|
+
/>,
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
await waitFor(() => {
|
|
154
|
+
expect(mockBuy).toHaveBeenCalledWith({
|
|
155
|
+
orderId: defaultOrder.orderId,
|
|
156
|
+
collectableDecimals: collectableWithCustomDecimals.decimals,
|
|
157
|
+
quantity: parseUnits('1', 6).toString(),
|
|
158
|
+
marketplace: defaultOrder.marketplace,
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
});
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import '@testing-library/jest-dom/vitest';
|
|
2
|
+
import {
|
|
3
|
+
render,
|
|
4
|
+
screen,
|
|
5
|
+
waitFor,
|
|
6
|
+
within,
|
|
7
|
+
fireEvent,
|
|
8
|
+
act,
|
|
9
|
+
cleanup,
|
|
10
|
+
} from '../../../../../_internal/test-utils';
|
|
11
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
12
|
+
import { ERC1155QuantityModal } from '../Modal1155';
|
|
13
|
+
import { buyModal$ } from '../../store';
|
|
14
|
+
import { MarketplaceKind } from '../../../../../_internal';
|
|
15
|
+
import type { Order, TokenMetadata } from '../../../../../_internal';
|
|
16
|
+
|
|
17
|
+
describe('ERC1155QuantityModal', () => {
|
|
18
|
+
const mockOrder = {
|
|
19
|
+
orderId: '1',
|
|
20
|
+
priceAmount: '1000000000000000000', // 1 ETH in wei
|
|
21
|
+
priceCurrencyAddress: '0x0',
|
|
22
|
+
quantityRemaining: '10',
|
|
23
|
+
quantityDecimals: 0,
|
|
24
|
+
chainId: 1,
|
|
25
|
+
marketplace: MarketplaceKind.sequence_marketplace_v2,
|
|
26
|
+
createdAt: new Date().toISOString(),
|
|
27
|
+
quantityAvailableFormatted: '1',
|
|
28
|
+
} as Order;
|
|
29
|
+
|
|
30
|
+
const mockCollectable: TokenMetadata = {
|
|
31
|
+
decimals: 0,
|
|
32
|
+
tokenId: '1',
|
|
33
|
+
name: 'Test Token',
|
|
34
|
+
description: 'Test Description',
|
|
35
|
+
image: 'https://example.com/image.png',
|
|
36
|
+
attributes: [],
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const mockBuy = vi.fn();
|
|
40
|
+
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
vi.clearAllMocks();
|
|
43
|
+
|
|
44
|
+
// Reset the modal state
|
|
45
|
+
buyModal$.close();
|
|
46
|
+
buyModal$.state.checkoutModalLoaded.set(false);
|
|
47
|
+
buyModal$.state.checkoutModalIsLoading.set(false);
|
|
48
|
+
buyModal$.state.invalidQuantity.set(false);
|
|
49
|
+
buyModal$.state.quantity.set('1');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
afterEach(() => {
|
|
53
|
+
buyModal$.close();
|
|
54
|
+
cleanup();
|
|
55
|
+
vi.clearAllMocks();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should render quantity input correctly', async () => {
|
|
59
|
+
// Open the modal with initial state
|
|
60
|
+
buyModal$.open({
|
|
61
|
+
order: mockOrder,
|
|
62
|
+
callbacks: {},
|
|
63
|
+
chainId: '1',
|
|
64
|
+
collectionAddress: '0x123',
|
|
65
|
+
tokenId: '1',
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
render(
|
|
69
|
+
<ERC1155QuantityModal
|
|
70
|
+
buy={mockBuy}
|
|
71
|
+
collectable={mockCollectable}
|
|
72
|
+
order={mockOrder}
|
|
73
|
+
chainId="1"
|
|
74
|
+
collectionAddress="0x123"
|
|
75
|
+
collectibleId="1"
|
|
76
|
+
/>,
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
// Verify modal title
|
|
80
|
+
expect(screen.getByText('Select Quantity')).toBeInTheDocument();
|
|
81
|
+
|
|
82
|
+
// Verify quantity input is present with initial value
|
|
83
|
+
const quantityInput = screen.getByRole('textbox', {
|
|
84
|
+
name: /enter quantity/i,
|
|
85
|
+
});
|
|
86
|
+
expect(quantityInput).toBeInTheDocument();
|
|
87
|
+
expect(quantityInput).toHaveValue('1');
|
|
88
|
+
|
|
89
|
+
// Verify total price calculation (1 ETH)
|
|
90
|
+
const priceLabels = screen.getAllByText('Total Price');
|
|
91
|
+
const priceContainer = priceLabels[0].parentElement;
|
|
92
|
+
if (!priceContainer) throw new Error('Price container not found');
|
|
93
|
+
expect(within(priceContainer).getByText('1')).toBeInTheDocument();
|
|
94
|
+
expect(within(priceContainer).getByText('ETH')).toBeInTheDocument();
|
|
95
|
+
|
|
96
|
+
// Verify buy button
|
|
97
|
+
const buyButton = screen.getByRole('button', { name: /buy now/i });
|
|
98
|
+
expect(buyButton).toBeInTheDocument();
|
|
99
|
+
expect(buyButton).not.toBeDisabled();
|
|
100
|
+
|
|
101
|
+
// Verify token image
|
|
102
|
+
const tokenImage = screen.getByRole('img', { name: '' });
|
|
103
|
+
expect(tokenImage).toBeInTheDocument();
|
|
104
|
+
expect(tokenImage).toHaveAttribute('src', 'https://example.com/eth.png');
|
|
105
|
+
|
|
106
|
+
// Ensure loading state is false and button is enabled before clicking
|
|
107
|
+
buyModal$.state.checkoutModalIsLoading.set(false);
|
|
108
|
+
buyModal$.state.invalidQuantity.set(false);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should update total price when quantity changes', async () => {
|
|
112
|
+
buyModal$.open({
|
|
113
|
+
order: mockOrder,
|
|
114
|
+
callbacks: {},
|
|
115
|
+
chainId: '1',
|
|
116
|
+
collectionAddress: '0x123',
|
|
117
|
+
tokenId: '1',
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
render(
|
|
121
|
+
<ERC1155QuantityModal
|
|
122
|
+
buy={mockBuy}
|
|
123
|
+
collectable={mockCollectable}
|
|
124
|
+
order={mockOrder}
|
|
125
|
+
chainId="1"
|
|
126
|
+
collectionAddress="0x123"
|
|
127
|
+
collectibleId="1"
|
|
128
|
+
/>,
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
const quantityInput = screen.getByRole('textbox', {
|
|
132
|
+
name: /enter quantity/i,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Change quantity to 2
|
|
136
|
+
await act(async () => {
|
|
137
|
+
fireEvent.change(quantityInput, { target: { value: '2' } });
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Verify total price updates to 2 ETH
|
|
141
|
+
const priceLabels = screen.getAllByText('Total Price');
|
|
142
|
+
const priceContainer = priceLabels[0].parentElement;
|
|
143
|
+
if (!priceContainer) throw new Error('Price container not found');
|
|
144
|
+
|
|
145
|
+
await waitFor(() => {
|
|
146
|
+
expect(within(priceContainer).getByText('2')).toBeInTheDocument();
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should disable buy button when quantity is invalid', async () => {
|
|
151
|
+
buyModal$.open({
|
|
152
|
+
order: mockOrder,
|
|
153
|
+
callbacks: {},
|
|
154
|
+
chainId: '1',
|
|
155
|
+
collectionAddress: '0x123',
|
|
156
|
+
tokenId: '1',
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Set invalid quantity and loading state
|
|
160
|
+
buyModal$.state.invalidQuantity.set(true);
|
|
161
|
+
buyModal$.state.checkoutModalIsLoading.set(true);
|
|
162
|
+
|
|
163
|
+
render(
|
|
164
|
+
<ERC1155QuantityModal
|
|
165
|
+
buy={mockBuy}
|
|
166
|
+
collectable={mockCollectable}
|
|
167
|
+
order={mockOrder}
|
|
168
|
+
chainId="1"
|
|
169
|
+
collectionAddress="0x123"
|
|
170
|
+
collectibleId="1"
|
|
171
|
+
/>,
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
const buyButton = screen.getByRole('button', { name: /buy now/i });
|
|
175
|
+
|
|
176
|
+
// Wait for the button to be disabled
|
|
177
|
+
await waitFor(() => {
|
|
178
|
+
expect(buyButton).toBeDisabled();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Click should not trigger buy function
|
|
182
|
+
fireEvent.click(buyButton);
|
|
183
|
+
expect(mockBuy).not.toHaveBeenCalled();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('should calculate total price correctly', async () => {
|
|
187
|
+
// Open the modal with initial state
|
|
188
|
+
buyModal$.open({
|
|
189
|
+
order: mockOrder,
|
|
190
|
+
callbacks: {},
|
|
191
|
+
chainId: '1',
|
|
192
|
+
collectionAddress: '0x123',
|
|
193
|
+
tokenId: '1',
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
render(
|
|
197
|
+
<ERC1155QuantityModal
|
|
198
|
+
buy={mockBuy}
|
|
199
|
+
collectable={mockCollectable}
|
|
200
|
+
order={mockOrder}
|
|
201
|
+
chainId="1"
|
|
202
|
+
collectionAddress="0x123"
|
|
203
|
+
collectibleId="1"
|
|
204
|
+
/>,
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
// Wait for modal content to be fully loaded
|
|
208
|
+
await waitFor(() => {
|
|
209
|
+
expect(screen.getByText('Select Quantity')).toBeInTheDocument();
|
|
210
|
+
expect(
|
|
211
|
+
screen.getByRole('textbox', { name: /enter quantity/i }),
|
|
212
|
+
).toBeInTheDocument();
|
|
213
|
+
expect(screen.getAllByText('Total Price')[0]).toBeInTheDocument();
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// Verify initial total price (1 ETH)
|
|
217
|
+
const priceLabels = screen.getAllByText('Total Price');
|
|
218
|
+
const priceContainer = priceLabels[0].parentElement;
|
|
219
|
+
if (!priceContainer) throw new Error('Price container not found');
|
|
220
|
+
|
|
221
|
+
await waitFor(() => {
|
|
222
|
+
expect(within(priceContainer).getByText('1')).toBeInTheDocument();
|
|
223
|
+
expect(within(priceContainer).getByText('ETH')).toBeInTheDocument();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Change quantity to 3
|
|
227
|
+
const quantityInput = screen.getByRole('textbox', {
|
|
228
|
+
name: /enter quantity/i,
|
|
229
|
+
});
|
|
230
|
+
await act(async () => {
|
|
231
|
+
fireEvent.change(quantityInput, { target: { value: '3' } });
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// Verify total price updates to 3 ETH
|
|
235
|
+
await waitFor(() => {
|
|
236
|
+
expect(within(priceContainer).getByText('3')).toBeInTheDocument();
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// Verify modal is in a valid state
|
|
240
|
+
expect(buyModal$.state.checkoutModalIsLoading.get()).toBe(false);
|
|
241
|
+
expect(buyModal$.state.invalidQuantity.get()).toBe(false);
|
|
242
|
+
});
|
|
243
|
+
});
|
|
@@ -3,6 +3,14 @@ import type { ShowBuyModalArgs } from '.';
|
|
|
3
3
|
import type { Order } from '../../../_internal';
|
|
4
4
|
import type { ModalCallbacks } from '../_internal/types';
|
|
5
5
|
|
|
6
|
+
const buyState = {
|
|
7
|
+
order: undefined as unknown as Order,
|
|
8
|
+
quantity: '1',
|
|
9
|
+
invalidQuantity: false,
|
|
10
|
+
checkoutModalIsLoading: false,
|
|
11
|
+
checkoutModalLoaded: false,
|
|
12
|
+
} as const;
|
|
13
|
+
|
|
6
14
|
export interface BuyModalState {
|
|
7
15
|
isOpen: boolean;
|
|
8
16
|
open: (
|
|
@@ -15,7 +23,6 @@ export interface BuyModalState {
|
|
|
15
23
|
state: {
|
|
16
24
|
order: Order;
|
|
17
25
|
quantity: string;
|
|
18
|
-
modalId: number;
|
|
19
26
|
invalidQuantity: boolean;
|
|
20
27
|
checkoutModalIsLoading: boolean;
|
|
21
28
|
checkoutModalLoaded: boolean;
|
|
@@ -38,7 +45,6 @@ export const initialState: BuyModalState = {
|
|
|
38
45
|
buyModal$.state.set({
|
|
39
46
|
quantity: args.order.quantityAvailableFormatted,
|
|
40
47
|
order: args.order,
|
|
41
|
-
modalId: buyModal$.state.modalId.get() + 1,
|
|
42
48
|
invalidQuantity: false,
|
|
43
49
|
checkoutModalIsLoading: false,
|
|
44
50
|
checkoutModalLoaded: false,
|
|
@@ -48,15 +54,10 @@ export const initialState: BuyModalState = {
|
|
|
48
54
|
},
|
|
49
55
|
close: () => {
|
|
50
56
|
buyModal$.isOpen.set(false);
|
|
57
|
+
buyModal$.callbacks.set(undefined);
|
|
58
|
+
buyModal$.state.set(buyState);
|
|
51
59
|
},
|
|
52
|
-
state:
|
|
53
|
-
order: undefined as unknown as Order,
|
|
54
|
-
quantity: '1',
|
|
55
|
-
modalId: 0,
|
|
56
|
-
invalidQuantity: false,
|
|
57
|
-
checkoutModalIsLoading: false,
|
|
58
|
-
checkoutModalLoaded: false,
|
|
59
|
-
},
|
|
60
|
+
state: buyState,
|
|
60
61
|
setCheckoutModalIsLoading: (isLoading: boolean) => {
|
|
61
62
|
buyModal$.state.checkoutModalIsLoading.set(isLoading);
|
|
62
63
|
},
|
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
useBalanceOfCollectible,
|
|
8
8
|
useCollectible,
|
|
9
9
|
useCollection,
|
|
10
|
+
useCurrencies,
|
|
11
|
+
useCurrencyOptions,
|
|
10
12
|
} from '../../../hooks';
|
|
11
13
|
import {
|
|
12
14
|
ActionModal,
|
|
@@ -49,7 +51,16 @@ const Modal = observer(() => {
|
|
|
49
51
|
collectionAddress,
|
|
50
52
|
collectibleId,
|
|
51
53
|
});
|
|
52
|
-
|
|
54
|
+
const currencyOptions = useCurrencyOptions({ collectionAddress });
|
|
55
|
+
const {
|
|
56
|
+
data: currencies,
|
|
57
|
+
isLoading: currenciesLoading,
|
|
58
|
+
isError: currenciesIsError,
|
|
59
|
+
} = useCurrencies({
|
|
60
|
+
chainId,
|
|
61
|
+
currencyOptions,
|
|
62
|
+
includeNativeCurrency: true,
|
|
63
|
+
});
|
|
53
64
|
const {
|
|
54
65
|
data: collection,
|
|
55
66
|
isLoading: collectionIsLoading,
|
|
@@ -91,7 +102,7 @@ const Modal = observer(() => {
|
|
|
91
102
|
steps$: steps$,
|
|
92
103
|
});
|
|
93
104
|
|
|
94
|
-
if (collectableIsLoading || collectionIsLoading) {
|
|
105
|
+
if (collectableIsLoading || collectionIsLoading || currenciesLoading) {
|
|
95
106
|
return (
|
|
96
107
|
<LoadingModal
|
|
97
108
|
isOpen={createListingModal$.isOpen.get()}
|
|
@@ -102,13 +113,25 @@ const Modal = observer(() => {
|
|
|
102
113
|
);
|
|
103
114
|
}
|
|
104
115
|
|
|
105
|
-
if (collectableIsError || collectionIsError) {
|
|
116
|
+
if (collectableIsError || collectionIsError || currenciesIsError) {
|
|
117
|
+
return (
|
|
118
|
+
<ErrorModal
|
|
119
|
+
isOpen={createListingModal$.isOpen.get()}
|
|
120
|
+
chainId={Number(chainId)}
|
|
121
|
+
onClose={createListingModal$.close}
|
|
122
|
+
title="List item for sale"
|
|
123
|
+
/>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!currencies || currencies.length === 0) {
|
|
106
128
|
return (
|
|
107
129
|
<ErrorModal
|
|
108
130
|
isOpen={createListingModal$.isOpen.get()}
|
|
109
131
|
chainId={Number(chainId)}
|
|
110
132
|
onClose={createListingModal$.close}
|
|
111
133
|
title="List item for sale"
|
|
134
|
+
message="No currencies are configured for the marketplace, contact the marketplace owners"
|
|
112
135
|
/>
|
|
113
136
|
);
|
|
114
137
|
}
|