Skip to content

Commit f107bd0

Browse files
committed
fix: refactor scruffy logic around whether there was a single kitty id (old approach) vs an array of kitty ids
1 parent fe05a0e commit f107bd0

File tree

2 files changed

+58
-142
lines changed

2 files changed

+58
-142
lines changed

src/components/CryptoKitties/CryptoKitties.test.tsx

Lines changed: 3 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ test('shows error alert when auction cancellation fails', async () => {
452452
})
453453

454454
test('shows alert when user enters non-numeric or out-of-range kitty ID', async () => {
455-
const { getByLabelText } = render(<CryptoKitties walletAddress={ETH_WALLET} dapperWalletAddress={DAPPERWALLET} invokeTx={mockInvokeTx} {...contracts} />)
455+
const { getByText, getByLabelText } = render(<CryptoKitties walletAddress={ETH_WALLET} dapperWalletAddress={DAPPERWALLET} invokeTx={mockInvokeTx} {...contracts} />)
456456

457457
// Wait for total supply to be set
458458
await waitFor(() => {
@@ -464,42 +464,14 @@ test('shows alert when user enters non-numeric or out-of-range kitty ID', async
464464
fireEvent.change(getByLabelText('Enter a CryptoKitty ID or multiple IDs separated by commas:'), { target: { value: '101' } })
465465
})
466466

467-
expect(window.alert).toHaveBeenCalledWith('Some kitty IDs were invalid and will be ignored')
467+
expect(getByText('Invalid kitty ID')).toBeTruthy()
468468

469469
await act(async () => {
470470
fireEvent.change(getByLabelText('Enter a CryptoKitty ID or multiple IDs separated by commas:'), { target: { value: '10a' } })
471471
})
472-
473-
expect(window.alert).toHaveBeenCalledWith('Some kitty IDs were invalid and will be ignored')
472+
expect(getByText('Invalid kitty ID')).toBeTruthy()
474473
})
475474

476-
// test('resets form state when reset button is clicked', async () => {
477-
// const { getByLabelText, getByText } = render(<CryptoKitties walletAddress={ETH_WALLET} dapperWalletAddress={DAPPERWALLET} invokeTx={mockInvokeTx} {...contracts} />)
478-
479-
// // Wait for total supply to be set
480-
// await waitFor(() => {
481-
// expect(contracts.core.methods.totalSupply().call).toHaveBeenCalled()
482-
// expect(contracts.core.methods.totalSupply().call).toHaveReturned()
483-
// })
484-
485-
// await act(async () => {
486-
// fireEvent.change(getByLabelText('Enter a CryptoKitty ID or multiple IDs separated by commas:'), { target: { value: '4' } })
487-
// })
488-
489-
// await waitFor(() => {
490-
// expect(getByText('Kitty #4')).toBeTruthy()
491-
// })
492-
493-
// // Click reset
494-
// await act(async () => {
495-
// fireEvent.click(getByText('Reset form'))
496-
// })
497-
498-
// // // Verify form is reset
499-
// const input = getByLabelText('Enter a CryptoKitty ID or multiple IDs separated by commas:') as HTMLInputElement
500-
// expect(input.value).toBe('')
501-
// })
502-
503475
test('shows error alert when kitty transfer fails', async () => {
504476
const { getByLabelText, getByText } = render(<CryptoKitties walletAddress={ETH_WALLET} dapperWalletAddress={DAPPERWALLET} invokeTx={mockInvokeTx} {...contracts} />)
505477

src/components/CryptoKitties/CryptoKitties.tsx

Lines changed: 55 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,14 @@ import { Contract } from 'web3-eth-contract'
44
import { AbiFragment } from 'web3'
55

66
/**
7-
* Interface for managing CryptoKitties transfer and auction form state
7+
* Interface for managing form input and loading state
88
* @interface FormDetails
99
* @property {string} kittyId - ID of the CryptoKitty or comma-separated IDs
10-
* @property {boolean} transferrable - Whether the kitty can be transferred
11-
* @property {boolean} forSale - Whether the kitty is in a sale auction
12-
* @property {boolean} forSire - Whether the kitty is in a sire auction
1310
* @property {boolean} loading - Loading state during operations
14-
* @property {boolean} auctionCancelled - Whether auction was successfully cancelled
15-
* @property {boolean} transferSuccess - Whether transfer was successful
16-
* @property {string} [error] - Error message if operation failed
1711
*/
1812
export interface FormDetails {
1913
kittyId: string,
20-
transferrable: boolean,
21-
forSale: boolean,
22-
forSire: boolean,
2314
loading: boolean,
24-
auctionCancelled: boolean,
25-
transferSuccess: boolean,
26-
error?: string,
2715
}
2816

2917
/**
@@ -75,44 +63,39 @@ const CryptoKitties: React.FC<{
7563

7664
const initFormState: FormDetails = {
7765
kittyId: '',
78-
transferrable: false,
79-
forSire: false,
80-
forSale: false,
8166
loading: false,
82-
auctionCancelled: false,
83-
transferSuccess: false,
84-
error: undefined,
8567
}
8668

8769
// Component state
8870
const [kittyStatuses, setKittyStatuses] = useState<KittyStatus[]>([]) // Status for multiple kitties
89-
const [total, setTotal] = useState<number>(0) // Total CryptoKitties supply
9071
const [balance, setBalance] = useState<number>(0) // User's CryptoKitties balance
72+
const [totalSupply, setTotalSupply] = useState<number>(0) // Total number of CryptoKitties
9173
const [formDetails, setFormDetails] = useState<FormDetails>(initFormState) // Form state
9274

9375
useEffect(() => {
94-
const getCryptoKittiesBalanceAndTotal = async () => {
76+
const init = async () => {
9577
const _balance = await core.methods.balanceOf(dapperWalletAddress).call()
96-
const _total = await core.methods.totalSupply().call()
97-
if (_balance !== undefined && _balance !== null && _total !== undefined && _total !== null) {
78+
const _totalSupply = await core.methods.totalSupply().call()
79+
if (_balance !== undefined && _balance !== null) {
9880
setBalance(parseInt(_balance.toString()))
99-
setTotal(parseInt(_total.toString()))
81+
}
82+
if (_totalSupply !== undefined && _totalSupply !== null) {
83+
setTotalSupply(parseInt(_totalSupply.toString()))
10084
}
10185
}
102-
getCryptoKittiesBalanceAndTotal()
103-
}, [])
86+
init()
87+
}, [core, dapperWalletAddress])
10488

105-
useEffect(() => {
106-
if (formDetails.transferrable || formDetails.forSire || formDetails.forSale || formDetails.error) {
107-
setFormDetails(prevState => ({
108-
...prevState,
109-
transferrable: false,
110-
forSale: false,
111-
forSire: false,
112-
error: undefined
113-
}))
114-
}
115-
}, [formDetails.kittyId])
89+
/**
90+
* Validates a kitty ID format and range
91+
* @param {string} id - ID to validate
92+
* @returns {boolean} Whether ID is valid
93+
*/
94+
const isValidKittyId = (id: string) => {
95+
if (!(/^\d+$/.test(id.trim()))) return false;
96+
const numId = parseInt(id.trim());
97+
return numId <= totalSupply;
98+
}
11699

117100
/**
118101
* Checks if a kitty is in an active auction
@@ -142,13 +125,14 @@ const CryptoKitties: React.FC<{
142125
const methodCall = contract.methods.cancelAuction(tokenId)
143126
try {
144127
await invokeTx(address, methodCall, '0')
145-
setFormDetails(prevState => ({ ...prevState, forSale: false, forSire: false, auctionCancelled: true }))
146128
setKittyStatuses(prev => prev.map(s =>
147129
s.id === tokenId ? { ...s, auctionCancelled: true } : s
148130
));
149131
} catch (e) {
132+
const errorMessage = 'Failed to cancel auction. Please try again.';
133+
alert(errorMessage);
150134
setKittyStatuses(prev => prev.map(s =>
151-
s.id === tokenId ? { ...s, error: 'Failed to cancel auction. Please try again.' } : s
135+
s.id === tokenId ? { ...s, error: errorMessage } : s
152136
));
153137
} finally {
154138
setFormDetails(prevState => ({ ...prevState, loading: false }))
@@ -168,15 +152,15 @@ const CryptoKitties: React.FC<{
168152
const methodCall = core.methods.transfer(walletAddress, kittyId)
169153
try {
170154
await invokeTx(address, methodCall, '0')
171-
if (kittyStatuses.length > 0) {
172-
setKittyStatuses(prev => prev.map(s =>
173-
s.id === kittyId ? { ...s, transferSuccess: true } : s
174-
))
175-
} else {
176-
setFormDetails(prevState => ({ ...prevState, transferrable: false, transferSuccess: true }))
177-
}
155+
setKittyStatuses(prev => prev.map(s =>
156+
s.id === kittyId ? { ...s, transferSuccess: true } : s
157+
))
178158
} catch (e) {
179-
setFormDetails(prev => ({ ...prev, error: 'Failed to transfer. Please try again.' }));
159+
const errorMessage = 'Failed to transfer. Please try again.';
160+
alert(errorMessage);
161+
setKittyStatuses(prev => prev.map(s =>
162+
s.id === kittyId ? { ...s, error: errorMessage } : s
163+
));
180164
} finally {
181165
setFormDetails(prevState => ({ ...prevState, loading: false }))
182166
}
@@ -196,24 +180,19 @@ const CryptoKitties: React.FC<{
196180
// Clear existing statuses when input changes
197181
setKittyStatuses([])
198182

199-
// If input contains commas, treat as multiple IDs
200-
const ids = value.split(',').map(id => id.trim()).filter(id => id !== '');
201-
if (ids.length > 0) {
202-
// Validate all IDs
203-
const validIds = ids.filter(id => /^\d+$/.test(id) && total && parseInt(id, 10) <= total);
204-
if (validIds.length !== ids.length) {
205-
alert('Some kitty IDs were invalid and will be ignored');
206-
}
183+
// Show all IDs immediately
184+
const ids = value.split(',')
185+
.map(id => id.trim())
186+
.filter(id => id !== '');
207187

208-
// Initialize statuses for valid IDs
209-
setKittyStatuses(validIds.map(id => ({
210-
id,
211-
transferrable: false,
212-
forSale: false,
213-
forSire: false,
214-
loading: false
215-
})));
216-
}
188+
setKittyStatuses(ids.map(id => ({
189+
id,
190+
transferrable: false,
191+
forSale: false,
192+
forSire: false,
193+
loading: false,
194+
error: !isValidKittyId(id) ? 'Invalid kitty ID' : undefined
195+
})));
217196
}
218197
setFormDetails(newState)
219198
}
@@ -224,7 +203,10 @@ const CryptoKitties: React.FC<{
224203
* @async
225204
*/
226205
const checkAllKitties = async () => {
227-
const ids = formDetails.kittyId.split(',').map(id => id.trim()).filter(id => /^\d+$/.test(id));
206+
// Get valid IDs
207+
const ids = kittyStatuses
208+
.filter(status => !status.error)
209+
.map(status => status.id);
228210
for (let i = 0; i < ids.length; i++) {
229211
const kittyId = ids[i];
230212
setKittyStatuses(prev => prev.map(status =>
@@ -259,8 +241,9 @@ const CryptoKitties: React.FC<{
259241
}
260242
}
261243
} catch (error) {
244+
const errorMessage = 'An error occurred while checking ownership.';
262245
setKittyStatuses(prev => prev.map(status =>
263-
status.id === kittyId ? { ...status, loading: false, error: 'An error occurred while checking ownership.' } : status
246+
status.id === kittyId ? { ...status, loading: false, error: errorMessage } : status
264247
));
265248
}
266249
}
@@ -274,11 +257,6 @@ const CryptoKitties: React.FC<{
274257
*/
275258
const formatBalance = (balance: number) => balance === 1 ? '1 CryptoKitty' : `${balance} CryptoKitties`
276259

277-
/**
278-
* Resets the form state to initial values
279-
*/
280-
// const resetForm = () => setFormDetails(initFormState)
281-
282260
return (
283261
<>
284262
<h2>{`CryptoKitties`}</h2>
@@ -299,7 +277,10 @@ const CryptoKitties: React.FC<{
299277
placeholder="Example: 123 or 123,456,789"
300278
/>
301279
</label>
302-
<button onClick={checkAllKitties} disabled={formDetails.loading || kittyStatuses.length === 0}>
280+
<button
281+
onClick={checkAllKitties}
282+
disabled={formDetails.loading}
283+
>
303284
{'Check Kitties'}
304285
</button>
305286

@@ -329,15 +310,7 @@ const CryptoKitties: React.FC<{
329310
)}
330311
{(status.forSale || status.forSire) && (
331312
<button
332-
onClick={async () => {
333-
setFormDetails(prev => ({
334-
...prev,
335-
kittyId: status.id,
336-
forSale: status.forSale,
337-
forSire: status.forSire,
338-
}));
339-
await handleCancelAuction(status.forSale, status.id);
340-
}}
313+
onClick={async () => await handleCancelAuction(status.forSale, status.id)}
341314
disabled={formDetails.loading}
342315
>
343316
{`Cancel ${status.forSale ? 'Sale' : 'Sire'} Auction`}
@@ -348,36 +321,7 @@ const CryptoKitties: React.FC<{
348321
</div>
349322
))}
350323
</div>
351-
) : formDetails.auctionCancelled || formDetails.transferSuccess || formDetails.error ? (
352-
<div>
353-
{formDetails.error ? (
354-
<p className="error">{formDetails.error}</p>
355-
) : formDetails.auctionCancelled ? (
356-
<p><span className={'success'}></span>{`Cancel auction method invoked for Kitty ID: #${formDetails.kittyId}`}</p>
357-
) : (
358-
<p><span className={'success'}></span>{`Transfer method invoked for Kitty ID: #${formDetails.kittyId}`}</p>
359-
)}
360-
</div>
361-
) : (
362-
<div style={{ marginTop: '10px' }}>
363-
{formDetails.transferrable && (
364-
<button
365-
onClick={async () => await handleTransfer(formDetails.kittyId)}
366-
disabled={formDetails.loading}
367-
>
368-
{`transfer kitty #${formDetails.kittyId}`}
369-
</button>
370-
)}
371-
{(formDetails.forSale || formDetails.forSire) && (
372-
<button
373-
onClick={async () => await handleCancelAuction(formDetails.forSale, formDetails.kittyId)}
374-
disabled={formDetails.loading}
375-
>
376-
{`cancel ${formDetails.forSale ? 'sale' : 'sire'} auction`}
377-
</button>
378-
)}
379-
</div>
380-
)}
324+
) : null}
381325
</>
382326
)
383327
}

0 commit comments

Comments
 (0)