@0xsequence/marketplace-sdk 0.4.8 → 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.
Files changed (101) hide show
  1. package/dist/{chunk-6R4G7J6Q.js → chunk-7HWJ4DUX.js} +29 -5
  2. package/dist/chunk-7HWJ4DUX.js.map +1 -0
  3. package/dist/{chunk-7WCZP6FN.js → chunk-BJLOO4NP.js} +628 -369
  4. package/dist/chunk-BJLOO4NP.js.map +1 -0
  5. package/dist/{chunk-2OJB35FS.js → chunk-BMWCIHCB.js} +5 -5
  6. package/dist/{chunk-2OJB35FS.js.map → chunk-BMWCIHCB.js.map} +1 -1
  7. package/dist/{chunk-WRMJ5FZM.js → chunk-CUA4SGWT.js} +524 -18
  8. package/dist/chunk-CUA4SGWT.js.map +1 -0
  9. package/dist/{chunk-JEOUQFT3.js → chunk-TFCSNRD5.js} +4 -3
  10. package/dist/chunk-TFCSNRD5.js.map +1 -0
  11. package/dist/{chunk-AQT3BQ67.js → chunk-Y3KINNAU.js} +12 -5
  12. package/dist/chunk-Y3KINNAU.js.map +1 -0
  13. package/dist/{create-config-D5WqfUft.d.ts → create-config-CgYQDyMD.d.ts} +2 -2
  14. package/dist/index.css +31 -17
  15. package/dist/index.d.ts +3 -3
  16. package/dist/index.js +1 -1
  17. package/dist/{marketplace-config-C_fDWzz0.d.ts → marketplace-config-BP5-XnFG.d.ts} +1 -1
  18. package/dist/{marketplace.gen-B8S8fflj.d.ts → marketplace.gen-BzmWLP9L.d.ts} +14 -4
  19. package/dist/react/_internal/api/index.d.ts +2 -2
  20. package/dist/react/_internal/api/index.js +3 -1
  21. package/dist/react/_internal/index.d.ts +5 -5
  22. package/dist/react/_internal/index.js +3 -1
  23. package/dist/react/_internal/wagmi/index.d.ts +3 -3
  24. package/dist/react/hooks/index.d.ts +46 -5
  25. package/dist/react/hooks/index.js +10 -3
  26. package/dist/react/index.css +37 -17
  27. package/dist/react/index.css.map +1 -1
  28. package/dist/react/index.d.ts +6 -6
  29. package/dist/react/index.js +13 -6
  30. package/dist/react/ui/components/collectible-card/index.css +37 -17
  31. package/dist/react/ui/components/collectible-card/index.css.map +1 -1
  32. package/dist/react/ui/components/collectible-card/index.d.ts +27 -4
  33. package/dist/react/ui/components/collectible-card/index.js +5 -6
  34. package/dist/react/ui/index.css +37 -17
  35. package/dist/react/ui/index.css.map +1 -1
  36. package/dist/react/ui/index.d.ts +3 -3
  37. package/dist/react/ui/index.js +5 -6
  38. package/dist/react/ui/modals/_internal/components/actionModal/index.d.ts +3 -3
  39. package/dist/react/ui/modals/_internal/components/actionModal/index.js +3 -3
  40. package/dist/{sdk-config-BXVH8PS2.d.ts → sdk-config-LbbmA85k.d.ts} +27 -7
  41. package/dist/{services-CdXAIjt1.d.ts → services-DW26ougH.d.ts} +1 -1
  42. package/dist/styles/index.css +31 -17
  43. package/dist/styles/index.css.map +1 -1
  44. package/dist/styles/index.d.ts +2 -2
  45. package/dist/styles/index.js +1 -1
  46. package/dist/types/index.d.ts +3 -3
  47. package/dist/types/index.js +1 -1
  48. package/dist/{types-eX4P9xju.d.ts → types-DhTZWw1U.d.ts} +2 -2
  49. package/dist/utils/index.d.ts +3 -3
  50. package/dist/utils/index.js +1 -1
  51. package/package.json +1 -1
  52. package/src/react/_internal/api/__mocks__/marketplace.msw.ts +4 -0
  53. package/src/react/_internal/api/marketplace.gen.ts +42 -7
  54. package/src/react/_internal/wallet/useWallet.ts +30 -46
  55. package/src/react/_internal/wallet/wallet.ts +52 -6
  56. package/src/react/hooks/index.ts +2 -0
  57. package/src/react/hooks/options/__mocks__/marketplaceConfig.msw.ts +10 -1
  58. package/src/react/hooks/useCollectionDetails.tsx +35 -0
  59. package/src/react/hooks/useCollectionDetailsPolling.tsx +60 -0
  60. package/src/react/ui/components/_internals/action-button/ActionButton.tsx +36 -118
  61. package/src/react/ui/components/_internals/action-button/components/ActionButtonBody.tsx +52 -0
  62. package/src/react/ui/components/_internals/action-button/components/NonOwnerActions.tsx +72 -0
  63. package/src/react/ui/components/_internals/action-button/components/OwnerActions.tsx +81 -0
  64. package/src/react/ui/components/_internals/action-button/hooks/useActionButtonLogic.ts +93 -0
  65. package/src/react/ui/components/_internals/action-button/store.ts +47 -0
  66. package/src/react/ui/components/_internals/action-button/styles.css.ts +8 -0
  67. package/src/react/ui/components/collectible-card/CollectibleCard.tsx +25 -6
  68. package/src/react/ui/components/collectible-card/Footer.tsx +5 -8
  69. package/src/react/ui/components/collectible-card/styles.css.ts +44 -31
  70. package/src/react/ui/icons/CartIcon.tsx +46 -0
  71. package/src/react/ui/modals/BuyModal/Modal.tsx +0 -2
  72. package/src/react/ui/modals/BuyModal/__tests__/Modal.test.tsx +253 -0
  73. package/src/react/ui/modals/BuyModal/__tests__/store.test.ts +100 -0
  74. package/src/react/ui/modals/BuyModal/hooks/__tests__/useBuyCollectable.test.tsx +402 -0
  75. package/src/react/ui/modals/BuyModal/hooks/__tests__/useCheckoutOptions.test.tsx +267 -0
  76. package/src/react/ui/modals/BuyModal/hooks/__tests__/useFees.test.tsx +166 -0
  77. package/src/react/ui/modals/BuyModal/hooks/__tests__/useLoadData.test.tsx +209 -0
  78. package/src/react/ui/modals/BuyModal/hooks/useCheckoutOptions.ts +19 -17
  79. package/src/react/ui/modals/BuyModal/hooks/useLoadData.ts +9 -7
  80. package/src/react/ui/modals/BuyModal/modals/Modal1155.tsx +36 -18
  81. package/src/react/ui/modals/BuyModal/modals/__tests__/CheckoutModal.test.tsx +162 -0
  82. package/src/react/ui/modals/BuyModal/modals/__tests__/Modal1155.test.tsx +243 -0
  83. package/src/react/ui/modals/BuyModal/store.ts +11 -10
  84. package/src/react/ui/modals/CreateListingModal/Modal.tsx +26 -3
  85. package/src/react/ui/modals/CreateListingModal/__tests__/Modal.test.tsx +141 -29
  86. package/src/react/ui/modals/_internal/components/actionModal/ActionModal.tsx +20 -11
  87. package/src/react/ui/modals/_internal/components/currencyOptionsSelect/__tests__/index.test.tsx +13 -58
  88. package/src/react/ui/modals/_internal/components/priceInput/__tests__/index.test.tsx +2 -0
  89. package/src/react/ui/modals/_internal/components/transactionStatusModal/__tests__/TransactionStatusModal.test.tsx +18 -19
  90. package/src/react/ui/modals/_internal/components/transactionStatusModal/__tests__/utils.test.ts +2 -0
  91. package/src/react/ui/modals/_internal/components/transactionStatusModal/hooks/useTransactionStatus.ts +62 -0
  92. package/src/react/ui/modals/_internal/components/transactionStatusModal/index.tsx +53 -100
  93. package/src/react/ui/modals/_internal/components/transactionStatusModal/store.ts +2 -10
  94. package/tsconfig.tsbuildinfo +1 -1
  95. package/dist/chunk-6R4G7J6Q.js.map +0 -1
  96. package/dist/chunk-7WCZP6FN.js.map +0 -1
  97. package/dist/chunk-AQT3BQ67.js.map +0 -1
  98. package/dist/chunk-FWN2MCLI.js +0 -425
  99. package/dist/chunk-FWN2MCLI.js.map +0 -1
  100. package/dist/chunk-JEOUQFT3.js.map +0 -1
  101. package/dist/chunk-WRMJ5FZM.js.map +0 -1
@@ -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
  }
@@ -13,6 +13,8 @@ import {
13
13
  useCollectible,
14
14
  useCollection,
15
15
  useBalanceOfCollectible,
16
+ useCurrencies,
17
+ useCurrencyOptions,
16
18
  } from '../../../../hooks';
17
19
  import { useCreateListing } from '../hooks/useCreateListing';
18
20
  import { useWallet } from '../../../../_internal/wallet/useWallet';
@@ -27,6 +29,11 @@ vi.mock('../../../../hooks', () => ({
27
29
  isLoading: false,
28
30
  isError: false,
29
31
  }),
32
+ useCurrencyOptions: vi.fn().mockReturnValue({
33
+ data: ['0x123'],
34
+ isLoading: false,
35
+ isError: false,
36
+ }),
30
37
  useMarketplaceConfig: vi.fn().mockReturnValue({
31
38
  data: {
32
39
  collections: [
@@ -107,39 +114,29 @@ describe('CreateListingModal', () => {
107
114
  expect(screen.queryByText('List item for sale')).toBeNull();
108
115
  });
109
116
 
110
- it('should render loading state', () => {
117
+ it('should render main form when data is loaded', () => {
118
+ // Mock successful states for all required hooks
111
119
  (useCollectible as any).mockReturnValue({
112
- isLoading: true,
120
+ data: { decimals: 18, name: 'Test NFT' },
121
+ isLoading: false,
122
+ isError: false,
113
123
  });
114
-
115
- createListingModal$.open({
116
- collectionAddress: '0x123',
117
- chainId: '1',
118
- collectibleId: '1',
124
+ (useCollection as any).mockReturnValue({
125
+ data: { type: 'ERC721', name: 'Test Collection' },
126
+ isLoading: false,
127
+ isError: false,
119
128
  });
120
-
121
- render(<CreateListingModal />);
122
- const loadingModal = screen.getByTestId('loading-modal');
123
- expect(loadingModal).toBeVisible();
124
- });
125
-
126
- it('should render error state', () => {
127
- (useCollectible as any).mockReturnValue({
128
- isError: true,
129
+ (useCurrencyOptions as any).mockReturnValue({
130
+ data: ['0x123'],
131
+ isLoading: false,
132
+ isError: false,
129
133
  });
130
-
131
- createListingModal$.open({
132
- collectionAddress: '0x123',
133
- chainId: '1',
134
- collectibleId: '1',
134
+ (useCurrencies as any).mockReturnValue({
135
+ data: [{ address: '0x123', symbol: 'TEST', decimals: 18 }],
136
+ isLoading: false,
137
+ isError: false,
135
138
  });
136
139
 
137
- render(<CreateListingModal />);
138
- const errorModal = screen.getByTestId('error-modal');
139
- expect(errorModal).toBeVisible();
140
- });
141
-
142
- it('should render main form when data is loaded', () => {
143
140
  createListingModal$.open({
144
141
  collectionAddress: '0x123',
145
142
  chainId: '1',
@@ -148,6 +145,9 @@ describe('CreateListingModal', () => {
148
145
 
149
146
  render(<CreateListingModal />);
150
147
 
148
+ // Check for the modal title using a more specific selector
149
+ expect(screen.getByRole('dialog')).toBeInTheDocument();
150
+ // Check for the collection name in the token preview
151
151
  expect(screen.getByText('Test Collection')).toBeInTheDocument();
152
152
  });
153
153
 
@@ -183,6 +183,28 @@ describe('CreateListingModal', () => {
183
183
  });
184
184
 
185
185
  it('should update state based on price input', async () => {
186
+ // Mock successful states for all required hooks
187
+ (useCollectible as any).mockReturnValue({
188
+ data: { decimals: 18, name: 'Test NFT' },
189
+ isLoading: false,
190
+ isError: false,
191
+ });
192
+ (useCollection as any).mockReturnValue({
193
+ data: { type: 'ERC721', name: 'Test Collection' },
194
+ isLoading: false,
195
+ isError: false,
196
+ });
197
+ (useCurrencyOptions as any).mockReturnValue({
198
+ data: ['0x123'],
199
+ isLoading: false,
200
+ isError: false,
201
+ });
202
+ (useCurrencies as any).mockReturnValue({
203
+ data: [{ address: '0x123', symbol: 'TEST', decimals: 18 }],
204
+ isLoading: false,
205
+ isError: false,
206
+ });
207
+
186
208
  createListingModal$.open({
187
209
  collectionAddress: '0x123',
188
210
  chainId: '1',
@@ -194,8 +216,8 @@ describe('CreateListingModal', () => {
194
216
  // Initial price should be 0
195
217
  expect(createListingModal$.listingPrice.amountRaw.get()).toBe('0');
196
218
 
197
- // Find and interact with price input
198
- const priceInput = screen.getByRole('textbox', { name: 'Enter price' });
219
+ // Find and interact with price input using id
220
+ const priceInput = screen.getByRole('textbox', { name: /enter price/i });
199
221
  expect(priceInput).toBeInTheDocument();
200
222
 
201
223
  fireEvent.change(priceInput, { target: { value: '1.5' } });
@@ -205,4 +227,94 @@ describe('CreateListingModal', () => {
205
227
  expect(createListingModal$.listingPrice.amountRaw.get()).not.toBe('0');
206
228
  });
207
229
  });
230
+
231
+ it('should show loading modal when data is being fetched', () => {
232
+ // Mock loading states for all required hooks
233
+ (useCollectible as any).mockReturnValue({
234
+ isLoading: true,
235
+ isError: false,
236
+ });
237
+ (useCollection as any).mockReturnValue({
238
+ isLoading: true,
239
+ isError: false,
240
+ });
241
+ (useCurrencyOptions as any).mockReturnValue({
242
+ isLoading: true,
243
+ isError: false,
244
+ });
245
+
246
+ createListingModal$.open({
247
+ collectionAddress: '0x123',
248
+ chainId: '1',
249
+ collectibleId: '1',
250
+ });
251
+
252
+ render(<CreateListingModal />);
253
+
254
+ expect(screen.getByTestId('loading-modal')).toBeInTheDocument();
255
+ });
256
+
257
+ it('should show error modal when there is an error fetching data', () => {
258
+ // Mock error states for required hooks
259
+ (useCollectible as any).mockReturnValue({
260
+ isLoading: false,
261
+ isError: true,
262
+ });
263
+ (useCollection as any).mockReturnValue({
264
+ isLoading: false,
265
+ isError: false,
266
+ });
267
+ (useCurrencyOptions as any).mockReturnValue({
268
+ isLoading: false,
269
+ isError: false,
270
+ });
271
+
272
+ createListingModal$.open({
273
+ collectionAddress: '0x123',
274
+ chainId: '1',
275
+ collectibleId: '1',
276
+ });
277
+
278
+ render(<CreateListingModal />);
279
+
280
+ expect(screen.getByText('Error loading item details')).toBeInTheDocument();
281
+ });
282
+
283
+ it('should show no ERC20 configured modal when currencies array is empty', () => {
284
+ // Reset all hooks to success state
285
+ (useCollectible as any).mockReturnValue({
286
+ data: { decimals: 18, name: 'Test NFT' },
287
+ isLoading: false,
288
+ isError: false,
289
+ });
290
+ (useCollection as any).mockReturnValue({
291
+ data: { type: 'ERC721', name: 'Test Collection' },
292
+ isLoading: false,
293
+ isError: false,
294
+ });
295
+ (useCurrencyOptions as any).mockReturnValue({
296
+ data: [],
297
+ isLoading: false,
298
+ isError: false,
299
+ });
300
+ (useCurrencies as any).mockReturnValue({
301
+ data: [],
302
+ isLoading: false,
303
+ isError: false,
304
+ });
305
+
306
+ createListingModal$.open({
307
+ collectionAddress: '0x123',
308
+ chainId: '1',
309
+ collectibleId: '1',
310
+ });
311
+
312
+ render(<CreateListingModal />);
313
+
314
+ expect(
315
+ screen.getByText(
316
+ 'No currencies are configured for the marketplace, contact the marketplace owners',
317
+ ),
318
+ ).toBeInTheDocument();
319
+ });
208
320
  });
@@ -12,7 +12,14 @@ import {
12
12
  Text,
13
13
  } from '@0xsequence/design-system';
14
14
  import { observer } from '@legendapp/state/react';
15
- import { Close, Content, Overlay, Portal, Root } from '@radix-ui/react-dialog';
15
+ import {
16
+ Close,
17
+ Content,
18
+ Overlay,
19
+ Portal,
20
+ Root,
21
+ Title,
22
+ } from '@radix-ui/react-dialog';
16
23
  import { getProviderEl } from '../../../../../_internal';
17
24
  import {
18
25
  closeButton,
@@ -77,16 +84,18 @@ export const ActionModal = observer(
77
84
  gap="4"
78
85
  position={'relative'}
79
86
  >
80
- <Text
81
- fontSize="medium"
82
- fontWeight="bold"
83
- textAlign="center"
84
- width="full"
85
- color="text100"
86
- fontFamily="body"
87
- >
88
- {title}
89
- </Text>
87
+ <Title asChild>
88
+ <Text
89
+ fontSize="medium"
90
+ fontWeight="bold"
91
+ textAlign="center"
92
+ width="full"
93
+ color="text100"
94
+ fontFamily="body"
95
+ >
96
+ {title}
97
+ </Text>
98
+ </Title>
90
99
 
91
100
  {children}
92
101