@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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { http, HttpResponse } from 'msw';
|
|
2
2
|
import { WalletOptions, type MarketplaceConfig } from '../../../../types';
|
|
3
|
+
import { mockCurrencies } from '../../../_internal/api/__mocks__/marketplace.msw';
|
|
3
4
|
|
|
4
5
|
// Mock data
|
|
5
6
|
export const mockConfig: MarketplaceConfig = {
|
|
@@ -12,7 +13,15 @@ export const mockConfig: MarketplaceConfig = {
|
|
|
12
13
|
logoUrl: 'https://example.com/logo.png',
|
|
13
14
|
titleTemplate: '%s | Test Marketplace',
|
|
14
15
|
walletOptions: [WalletOptions.Sequence],
|
|
15
|
-
collections: [
|
|
16
|
+
collections: [
|
|
17
|
+
{
|
|
18
|
+
collectionAddress: '0x1234567890123456789012345678901234567890',
|
|
19
|
+
chainId: 1,
|
|
20
|
+
marketplaceFeePercentage: 2.5,
|
|
21
|
+
marketplaceType: 'orderbook',
|
|
22
|
+
currencyOptions: mockCurrencies.map((c) => c.contractAddress),
|
|
23
|
+
},
|
|
24
|
+
],
|
|
16
25
|
landingPageLayout: 'default',
|
|
17
26
|
cssString: '',
|
|
18
27
|
manifestUrl: '',
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { queryOptions, useQuery } from '@tanstack/react-query';
|
|
2
|
+
import type { SdkConfig } from '../../types';
|
|
3
|
+
import { getMarketplaceClient } from '../_internal';
|
|
4
|
+
import { useConfig } from './useConfig';
|
|
5
|
+
|
|
6
|
+
type UseCollectionDetails = {
|
|
7
|
+
collectionAddress: string;
|
|
8
|
+
chainId: number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const fetchCollectionDetails = async (
|
|
12
|
+
args: { collectionAddress: string },
|
|
13
|
+
marketplaceClient: Awaited<ReturnType<typeof getMarketplaceClient>>,
|
|
14
|
+
) => {
|
|
15
|
+
const { collection } = await marketplaceClient.getCollectionDetail({
|
|
16
|
+
contractAddress: args.collectionAddress,
|
|
17
|
+
});
|
|
18
|
+
return collection;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const collectionDetailsOptions = (
|
|
22
|
+
args: UseCollectionDetails,
|
|
23
|
+
config: SdkConfig,
|
|
24
|
+
) => {
|
|
25
|
+
const marketplaceClient = getMarketplaceClient(args.chainId, config);
|
|
26
|
+
return queryOptions({
|
|
27
|
+
queryKey: ['collectionDetails', args],
|
|
28
|
+
queryFn: () => fetchCollectionDetails(args, marketplaceClient),
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const useCollectionDetails = (args: UseCollectionDetails) => {
|
|
33
|
+
const config = useConfig();
|
|
34
|
+
return useQuery(collectionDetailsOptions(args, config));
|
|
35
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { queryOptions, useQuery } from '@tanstack/react-query';
|
|
2
|
+
import type { SdkConfig } from '../../types';
|
|
3
|
+
import { CollectionStatus } from '../_internal/api/marketplace.gen';
|
|
4
|
+
import { useConfig } from './useConfig';
|
|
5
|
+
import { collectionDetailsOptions } from './useCollectionDetails';
|
|
6
|
+
|
|
7
|
+
type UseCollectionDetailsPolling = {
|
|
8
|
+
collectionAddress: string;
|
|
9
|
+
chainId: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const INITIAL_POLLING_INTERVAL = 2000; // 2 seconds
|
|
13
|
+
const MAX_POLLING_INTERVAL = 30000; // 30 seconds
|
|
14
|
+
const MAX_ATTEMPTS = 30;
|
|
15
|
+
|
|
16
|
+
const isTerminalState = (status: CollectionStatus): boolean => {
|
|
17
|
+
return [
|
|
18
|
+
CollectionStatus.active,
|
|
19
|
+
CollectionStatus.failed,
|
|
20
|
+
CollectionStatus.inactive,
|
|
21
|
+
CollectionStatus.incompatible_type,
|
|
22
|
+
].includes(status);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const collectionDetailsPollingOptions = (
|
|
26
|
+
args: UseCollectionDetailsPolling,
|
|
27
|
+
config: SdkConfig,
|
|
28
|
+
) => {
|
|
29
|
+
return queryOptions({
|
|
30
|
+
...collectionDetailsOptions(args, config),
|
|
31
|
+
refetchInterval: (query) => {
|
|
32
|
+
const data = query.state.data;
|
|
33
|
+
if (data && isTerminalState(data.status)) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Calculate exponential backoff interval
|
|
38
|
+
const currentAttempt = (query.state.dataUpdateCount || 0) + 1;
|
|
39
|
+
if (currentAttempt >= MAX_ATTEMPTS) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const interval = Math.min(
|
|
44
|
+
INITIAL_POLLING_INTERVAL * Math.pow(1.5, currentAttempt),
|
|
45
|
+
MAX_POLLING_INTERVAL,
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
return interval;
|
|
49
|
+
},
|
|
50
|
+
refetchOnWindowFocus: false,
|
|
51
|
+
retry: false,
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const useCollectionDetailsPolling = (
|
|
56
|
+
args: UseCollectionDetailsPolling,
|
|
57
|
+
) => {
|
|
58
|
+
const config = useConfig();
|
|
59
|
+
return useQuery(collectionDetailsPollingOptions(args, config));
|
|
60
|
+
};
|
package/src/react/provider.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import '@0xsequence/design-system/styles.css';
|
|
|
6
6
|
import type { SdkConfig } from '../types';
|
|
7
7
|
import { PROVIDER_ID } from './_internal/get-provider';
|
|
8
8
|
import { getQueryClient } from './_internal/api/get-query-client';
|
|
9
|
+
import { InvalidProjectAccessKeyError } from '../utils/_internal/error/config';
|
|
9
10
|
|
|
10
11
|
export const MarketplaceSdkContext = createContext({} as SdkConfig);
|
|
11
12
|
|
|
@@ -29,6 +30,10 @@ export function MarketplaceProvider({
|
|
|
29
30
|
config,
|
|
30
31
|
children,
|
|
31
32
|
}: MarketplaceSdkProviderProps) {
|
|
33
|
+
if (config.projectAccessKey === '' || !config.projectAccessKey) {
|
|
34
|
+
throw new InvalidProjectAccessKeyError(config.projectAccessKey);
|
|
35
|
+
}
|
|
36
|
+
|
|
32
37
|
return (
|
|
33
38
|
<MarketplaceQueryClientProvider>
|
|
34
39
|
<MarketplaceSdkContext.Provider value={config}>
|
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { Button } from '@0xsequence/design-system';
|
|
4
3
|
import { observer } from '@legendapp/state/react';
|
|
5
4
|
import type { Hex } from 'viem';
|
|
6
|
-
import { InvalidStepError } from '../../../../../utils/_internal/error/transaction';
|
|
7
5
|
import type { Order, OrderbookKind } from '../../../../_internal';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { useTransferModal } from '../../../modals/TransferModal';
|
|
13
|
-
|
|
14
|
-
import { CollectibleCardAction } from './types';
|
|
6
|
+
import type { CollectibleCardAction } from './types';
|
|
7
|
+
import { useActionButtonLogic } from './hooks/useActionButtonLogic';
|
|
8
|
+
import { OwnerActions } from './components/OwnerActions';
|
|
9
|
+
import { NonOwnerActions } from './components/NonOwnerActions';
|
|
15
10
|
|
|
16
11
|
type ActionButtonProps = {
|
|
17
12
|
chainId: string;
|
|
@@ -20,9 +15,12 @@ type ActionButtonProps = {
|
|
|
20
15
|
orderbookKind?: OrderbookKind;
|
|
21
16
|
isTransfer?: boolean;
|
|
22
17
|
action: CollectibleCardAction;
|
|
23
|
-
|
|
18
|
+
owned?: boolean;
|
|
24
19
|
highestOffer?: Order;
|
|
25
20
|
lowestListing?: Order;
|
|
21
|
+
onCannotPerformAction?: (
|
|
22
|
+
action: CollectibleCardAction.BUY | CollectibleCardAction.OFFER,
|
|
23
|
+
) => void;
|
|
26
24
|
};
|
|
27
25
|
|
|
28
26
|
export const ActionButton = observer(
|
|
@@ -32,124 +30,44 @@ export const ActionButton = observer(
|
|
|
32
30
|
tokenId,
|
|
33
31
|
orderbookKind,
|
|
34
32
|
action,
|
|
33
|
+
owned,
|
|
35
34
|
highestOffer,
|
|
36
35
|
lowestListing,
|
|
36
|
+
onCannotPerformAction,
|
|
37
37
|
}: ActionButtonProps) => {
|
|
38
|
-
const {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (action === CollectibleCardAction.BUY) {
|
|
45
|
-
if (!lowestListing)
|
|
46
|
-
throw new InvalidStepError('BUY', 'lowestListing is required');
|
|
47
|
-
|
|
48
|
-
return (
|
|
49
|
-
<ActionButtonBody
|
|
50
|
-
label="Buy"
|
|
51
|
-
onClick={() =>
|
|
52
|
-
showBuyModal({
|
|
53
|
-
collectionAddress,
|
|
54
|
-
chainId: chainId,
|
|
55
|
-
tokenId: tokenId,
|
|
56
|
-
order: lowestListing,
|
|
57
|
-
})
|
|
58
|
-
}
|
|
59
|
-
/>
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (action === CollectibleCardAction.SELL) {
|
|
64
|
-
if (!highestOffer)
|
|
65
|
-
throw new InvalidStepError('SELL', 'highestOffer is required');
|
|
66
|
-
|
|
67
|
-
return (
|
|
68
|
-
<ActionButtonBody
|
|
69
|
-
label="Sell"
|
|
70
|
-
onClick={() =>
|
|
71
|
-
showSellModal({
|
|
72
|
-
collectionAddress,
|
|
73
|
-
chainId: chainId,
|
|
74
|
-
tokenId: tokenId,
|
|
75
|
-
order: highestOffer,
|
|
76
|
-
})
|
|
77
|
-
}
|
|
78
|
-
/>
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (action === CollectibleCardAction.LIST) {
|
|
83
|
-
return (
|
|
84
|
-
<ActionButtonBody
|
|
85
|
-
label="Create listing"
|
|
86
|
-
onClick={() =>
|
|
87
|
-
showCreateListingModal({
|
|
88
|
-
collectionAddress: collectionAddress as Hex,
|
|
89
|
-
chainId: chainId,
|
|
90
|
-
collectibleId: tokenId,
|
|
91
|
-
orderbookKind,
|
|
92
|
-
})
|
|
93
|
-
}
|
|
94
|
-
/>
|
|
95
|
-
);
|
|
96
|
-
}
|
|
38
|
+
const { shouldShowAction, isOwnerAction } = useActionButtonLogic({
|
|
39
|
+
tokenId,
|
|
40
|
+
owned,
|
|
41
|
+
action,
|
|
42
|
+
onCannotPerformAction,
|
|
43
|
+
});
|
|
97
44
|
|
|
98
|
-
if (
|
|
99
|
-
return
|
|
100
|
-
<ActionButtonBody
|
|
101
|
-
label="Make an offer"
|
|
102
|
-
onClick={() =>
|
|
103
|
-
showMakeOfferModal({
|
|
104
|
-
collectionAddress: collectionAddress as Hex,
|
|
105
|
-
chainId: chainId,
|
|
106
|
-
collectibleId: tokenId,
|
|
107
|
-
orderbookKind,
|
|
108
|
-
})
|
|
109
|
-
}
|
|
110
|
-
/>
|
|
111
|
-
);
|
|
45
|
+
if (!shouldShowAction) {
|
|
46
|
+
return null;
|
|
112
47
|
}
|
|
113
48
|
|
|
114
|
-
if (
|
|
49
|
+
if (isOwnerAction) {
|
|
115
50
|
return (
|
|
116
|
-
<
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
})
|
|
124
|
-
}
|
|
51
|
+
<OwnerActions
|
|
52
|
+
action={action}
|
|
53
|
+
tokenId={tokenId}
|
|
54
|
+
collectionAddress={collectionAddress}
|
|
55
|
+
chainId={chainId}
|
|
56
|
+
orderbookKind={orderbookKind}
|
|
57
|
+
highestOffer={highestOffer}
|
|
125
58
|
/>
|
|
126
59
|
);
|
|
127
60
|
}
|
|
128
61
|
|
|
129
|
-
return
|
|
62
|
+
return (
|
|
63
|
+
<NonOwnerActions
|
|
64
|
+
action={action}
|
|
65
|
+
tokenId={tokenId}
|
|
66
|
+
collectionAddress={collectionAddress}
|
|
67
|
+
chainId={chainId}
|
|
68
|
+
orderbookKind={orderbookKind}
|
|
69
|
+
lowestListing={lowestListing}
|
|
70
|
+
/>
|
|
71
|
+
);
|
|
130
72
|
},
|
|
131
73
|
);
|
|
132
|
-
|
|
133
|
-
type ActionButtonBodyProps = {
|
|
134
|
-
label: string;
|
|
135
|
-
onClick: () => void;
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
function ActionButtonBody({ label, onClick }: ActionButtonBodyProps) {
|
|
139
|
-
return (
|
|
140
|
-
<Button
|
|
141
|
-
variant="primary"
|
|
142
|
-
label={label}
|
|
143
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
144
|
-
onClick={(e) => {
|
|
145
|
-
e.preventDefault();
|
|
146
|
-
e.stopPropagation();
|
|
147
|
-
onClick();
|
|
148
|
-
}}
|
|
149
|
-
// leftIcon={leftIcon}
|
|
150
|
-
size="xs"
|
|
151
|
-
shape="square"
|
|
152
|
-
width="full"
|
|
153
|
-
/>
|
|
154
|
-
);
|
|
155
|
-
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Button } from '@0xsequence/design-system';
|
|
2
|
+
import { useAccount } from 'wagmi';
|
|
3
|
+
import { useOpenConnectModal } from '@0xsequence/kit';
|
|
4
|
+
import type { CollectibleCardAction } from '../types';
|
|
5
|
+
import { setPendingAction } from '../store';
|
|
6
|
+
import { actionButton } from '../styles.css';
|
|
7
|
+
|
|
8
|
+
type ActionButtonBodyProps = {
|
|
9
|
+
label: 'Buy now' | 'Sell' | 'Make an offer' | 'Create listing' | 'Transfer';
|
|
10
|
+
tokenId: string;
|
|
11
|
+
onClick: () => void;
|
|
12
|
+
icon?: React.ComponentType<{
|
|
13
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | undefined;
|
|
14
|
+
}>;
|
|
15
|
+
action?: CollectibleCardAction.BUY | CollectibleCardAction.OFFER;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function ActionButtonBody({
|
|
19
|
+
tokenId,
|
|
20
|
+
label,
|
|
21
|
+
onClick,
|
|
22
|
+
icon,
|
|
23
|
+
action,
|
|
24
|
+
}: ActionButtonBodyProps) {
|
|
25
|
+
const { address } = useAccount();
|
|
26
|
+
const { setOpenConnectModal } = useOpenConnectModal();
|
|
27
|
+
|
|
28
|
+
const handleClick = (e: React.MouseEvent) => {
|
|
29
|
+
e.preventDefault();
|
|
30
|
+
e.stopPropagation();
|
|
31
|
+
|
|
32
|
+
if (!address && action) {
|
|
33
|
+
setPendingAction(action, onClick, tokenId);
|
|
34
|
+
setOpenConnectModal(true);
|
|
35
|
+
} else {
|
|
36
|
+
onClick();
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<Button
|
|
42
|
+
className={actionButton}
|
|
43
|
+
variant="primary"
|
|
44
|
+
label={label}
|
|
45
|
+
onClick={handleClick}
|
|
46
|
+
leftIcon={icon}
|
|
47
|
+
size="xs"
|
|
48
|
+
shape="square"
|
|
49
|
+
width="full"
|
|
50
|
+
/>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { Hex } from 'viem';
|
|
2
|
+
import type { Order, OrderbookKind } from '../../../../../_internal';
|
|
3
|
+
import { CollectibleCardAction } from '../types';
|
|
4
|
+
import { ActionButtonBody } from './ActionButtonBody';
|
|
5
|
+
import { useBuyModal } from '../../../../modals/BuyModal';
|
|
6
|
+
import { useMakeOfferModal } from '../../../../modals/MakeOfferModal';
|
|
7
|
+
import { InvalidStepError } from '../../../../../../utils/_internal/error/transaction';
|
|
8
|
+
import SvgCartIcon from '../../../../icons/CartIcon';
|
|
9
|
+
|
|
10
|
+
type NonOwnerActionsProps = {
|
|
11
|
+
action: CollectibleCardAction;
|
|
12
|
+
tokenId: string;
|
|
13
|
+
collectionAddress: Hex;
|
|
14
|
+
chainId: string;
|
|
15
|
+
orderbookKind?: OrderbookKind;
|
|
16
|
+
lowestListing?: Order;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function NonOwnerActions({
|
|
20
|
+
action,
|
|
21
|
+
tokenId,
|
|
22
|
+
collectionAddress,
|
|
23
|
+
chainId,
|
|
24
|
+
orderbookKind,
|
|
25
|
+
lowestListing,
|
|
26
|
+
}: NonOwnerActionsProps) {
|
|
27
|
+
const { show: showBuyModal } = useBuyModal();
|
|
28
|
+
const { show: showMakeOfferModal } = useMakeOfferModal();
|
|
29
|
+
|
|
30
|
+
if (action === CollectibleCardAction.BUY) {
|
|
31
|
+
if (!lowestListing) {
|
|
32
|
+
throw new InvalidStepError('BUY', 'lowestListing is required');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<ActionButtonBody
|
|
37
|
+
action={CollectibleCardAction.BUY}
|
|
38
|
+
tokenId={tokenId}
|
|
39
|
+
label="Buy now"
|
|
40
|
+
onClick={() =>
|
|
41
|
+
showBuyModal({
|
|
42
|
+
collectionAddress,
|
|
43
|
+
chainId,
|
|
44
|
+
tokenId,
|
|
45
|
+
order: lowestListing,
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
icon={SvgCartIcon}
|
|
49
|
+
/>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (action === CollectibleCardAction.OFFER) {
|
|
54
|
+
return (
|
|
55
|
+
<ActionButtonBody
|
|
56
|
+
action={CollectibleCardAction.OFFER}
|
|
57
|
+
tokenId={tokenId}
|
|
58
|
+
label="Make an offer"
|
|
59
|
+
onClick={() =>
|
|
60
|
+
showMakeOfferModal({
|
|
61
|
+
collectionAddress,
|
|
62
|
+
chainId,
|
|
63
|
+
collectibleId: tokenId,
|
|
64
|
+
orderbookKind,
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
/>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { Hex } from 'viem';
|
|
2
|
+
import type { Order, OrderbookKind } from '../../../../../_internal';
|
|
3
|
+
import { CollectibleCardAction } from '../types';
|
|
4
|
+
import { ActionButtonBody } from './ActionButtonBody';
|
|
5
|
+
import { useCreateListingModal } from '../../../../modals/CreateListingModal';
|
|
6
|
+
import { useSellModal } from '../../../../modals/SellModal';
|
|
7
|
+
import { useTransferModal } from '../../../../modals/TransferModal';
|
|
8
|
+
|
|
9
|
+
type OwnerActionsProps = {
|
|
10
|
+
action: CollectibleCardAction;
|
|
11
|
+
tokenId: string;
|
|
12
|
+
collectionAddress: Hex;
|
|
13
|
+
chainId: string;
|
|
14
|
+
orderbookKind?: OrderbookKind;
|
|
15
|
+
highestOffer?: Order;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function OwnerActions({
|
|
19
|
+
action,
|
|
20
|
+
tokenId,
|
|
21
|
+
collectionAddress,
|
|
22
|
+
chainId,
|
|
23
|
+
orderbookKind,
|
|
24
|
+
highestOffer,
|
|
25
|
+
}: OwnerActionsProps) {
|
|
26
|
+
const { show: showCreateListingModal } = useCreateListingModal();
|
|
27
|
+
const { show: showSellModal } = useSellModal();
|
|
28
|
+
const { show: showTransferModal } = useTransferModal();
|
|
29
|
+
|
|
30
|
+
if (action === CollectibleCardAction.LIST) {
|
|
31
|
+
return (
|
|
32
|
+
<ActionButtonBody
|
|
33
|
+
label="Create listing"
|
|
34
|
+
tokenId={tokenId}
|
|
35
|
+
onClick={() =>
|
|
36
|
+
showCreateListingModal({
|
|
37
|
+
collectionAddress: collectionAddress as Hex,
|
|
38
|
+
chainId,
|
|
39
|
+
collectibleId: tokenId,
|
|
40
|
+
orderbookKind,
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (action === CollectibleCardAction.SELL && highestOffer) {
|
|
48
|
+
return (
|
|
49
|
+
<ActionButtonBody
|
|
50
|
+
tokenId={tokenId}
|
|
51
|
+
label="Sell"
|
|
52
|
+
onClick={() =>
|
|
53
|
+
showSellModal({
|
|
54
|
+
collectionAddress,
|
|
55
|
+
chainId,
|
|
56
|
+
tokenId,
|
|
57
|
+
order: highestOffer,
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
/>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (action === CollectibleCardAction.TRANSFER) {
|
|
65
|
+
return (
|
|
66
|
+
<ActionButtonBody
|
|
67
|
+
label="Transfer"
|
|
68
|
+
tokenId={tokenId}
|
|
69
|
+
onClick={() =>
|
|
70
|
+
showTransferModal({
|
|
71
|
+
collectionAddress: collectionAddress as Hex,
|
|
72
|
+
chainId,
|
|
73
|
+
collectibleId: tokenId,
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
/>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { useAccount } from 'wagmi';
|
|
3
|
+
import { CollectibleCardAction } from '../types';
|
|
4
|
+
import {
|
|
5
|
+
actionButtonStore,
|
|
6
|
+
clearPendingAction,
|
|
7
|
+
executePendingActionIfExists,
|
|
8
|
+
} from '../store';
|
|
9
|
+
|
|
10
|
+
type UseActionButtonLogicProps = {
|
|
11
|
+
tokenId: string;
|
|
12
|
+
owned?: boolean;
|
|
13
|
+
action: CollectibleCardAction;
|
|
14
|
+
onCannotPerformAction?: (
|
|
15
|
+
action: CollectibleCardAction.BUY | CollectibleCardAction.OFFER,
|
|
16
|
+
) => void;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const useActionButtonLogic = ({
|
|
20
|
+
tokenId,
|
|
21
|
+
owned,
|
|
22
|
+
action,
|
|
23
|
+
onCannotPerformAction,
|
|
24
|
+
}: UseActionButtonLogicProps) => {
|
|
25
|
+
const { address } = useAccount();
|
|
26
|
+
const actionsThatOwnersCannotPerform = [
|
|
27
|
+
CollectibleCardAction.BUY,
|
|
28
|
+
CollectibleCardAction.OFFER,
|
|
29
|
+
];
|
|
30
|
+
const pendingActionType = actionButtonStore.pendingAction.type.get();
|
|
31
|
+
|
|
32
|
+
// Handle owner restrictions
|
|
33
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (
|
|
36
|
+
owned &&
|
|
37
|
+
actionButtonStore.pendingAction.get() &&
|
|
38
|
+
address &&
|
|
39
|
+
!actionsThatOwnersCannotPerform.includes(action) &&
|
|
40
|
+
actionButtonStore.pendingAction.get()?.collectibleId === tokenId
|
|
41
|
+
) {
|
|
42
|
+
onCannotPerformAction?.(
|
|
43
|
+
pendingActionType as
|
|
44
|
+
| CollectibleCardAction.BUY
|
|
45
|
+
| CollectibleCardAction.OFFER,
|
|
46
|
+
);
|
|
47
|
+
clearPendingAction();
|
|
48
|
+
}
|
|
49
|
+
}, [
|
|
50
|
+
owned,
|
|
51
|
+
actionButtonStore.pendingAction.get(),
|
|
52
|
+
address,
|
|
53
|
+
action,
|
|
54
|
+
tokenId,
|
|
55
|
+
onCannotPerformAction,
|
|
56
|
+
pendingActionType,
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
// Execute pending action when user becomes connected
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (
|
|
62
|
+
address &&
|
|
63
|
+
!owned &&
|
|
64
|
+
actionButtonStore.pendingAction.get() &&
|
|
65
|
+
actionButtonStore.pendingAction.get()?.collectibleId === tokenId
|
|
66
|
+
) {
|
|
67
|
+
// TODO: Remove this timeout once pointer-events: none issue is fixed on Radix UI side
|
|
68
|
+
setTimeout(() => {
|
|
69
|
+
executePendingActionIfExists();
|
|
70
|
+
clearPendingAction();
|
|
71
|
+
}, 1000);
|
|
72
|
+
}
|
|
73
|
+
}, [address, owned, tokenId]);
|
|
74
|
+
|
|
75
|
+
const shouldShowAction = !address
|
|
76
|
+
? [CollectibleCardAction.BUY, CollectibleCardAction.OFFER].includes(action)
|
|
77
|
+
: true;
|
|
78
|
+
|
|
79
|
+
const isOwnerAction =
|
|
80
|
+
address &&
|
|
81
|
+
owned &&
|
|
82
|
+
[
|
|
83
|
+
CollectibleCardAction.LIST,
|
|
84
|
+
CollectibleCardAction.TRANSFER,
|
|
85
|
+
CollectibleCardAction.SELL,
|
|
86
|
+
].includes(action);
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
address,
|
|
90
|
+
shouldShowAction,
|
|
91
|
+
isOwnerAction,
|
|
92
|
+
};
|
|
93
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { observable } from '@legendapp/state';
|
|
2
|
+
import type { CollectibleCardAction } from './types';
|
|
3
|
+
|
|
4
|
+
type PendingAction = {
|
|
5
|
+
type: CollectibleCardAction.BUY | CollectibleCardAction.OFFER;
|
|
6
|
+
collectibleId: string;
|
|
7
|
+
callback: () => void;
|
|
8
|
+
timestamp: number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const actionButtonStore = observable({
|
|
12
|
+
pendingAction: null as PendingAction | null,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export const setPendingAction = (
|
|
16
|
+
type: CollectibleCardAction.BUY | CollectibleCardAction.OFFER,
|
|
17
|
+
callback: () => void,
|
|
18
|
+
collectibleId: string,
|
|
19
|
+
) => {
|
|
20
|
+
actionButtonStore.pendingAction.set({
|
|
21
|
+
type,
|
|
22
|
+
callback,
|
|
23
|
+
timestamp: Date.now(),
|
|
24
|
+
collectibleId,
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const clearPendingAction = () => {
|
|
29
|
+
actionButtonStore.pendingAction.set(null);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const executePendingActionIfExists = () => {
|
|
33
|
+
const timestamp = actionButtonStore.pendingAction.get()?.timestamp;
|
|
34
|
+
const callback = actionButtonStore.pendingAction.get()?.callback as
|
|
35
|
+
| (() => void)
|
|
36
|
+
| undefined;
|
|
37
|
+
|
|
38
|
+
if (timestamp && callback) {
|
|
39
|
+
// Only execute if the pending action is less than 5 minutes old
|
|
40
|
+
if (
|
|
41
|
+
Date.now() - timestamp < 5 * 60 * 1000 &&
|
|
42
|
+
typeof callback === 'function'
|
|
43
|
+
) {
|
|
44
|
+
callback();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|