From 1951a8f46192d422e071e38edf1b41586d67014c Mon Sep 17 00:00:00 2001 From: 0age <37939117+0age@users.noreply.github.com> Date: Fri, 6 Dec 2024 14:25:27 -0800 Subject: [PATCH] fix frontend linting + type issues --- frontend/src/components/BalanceDisplay.tsx | 31 ++- frontend/src/components/CreateAllocation.tsx | 252 +++++++++++------- frontend/src/components/DepositForm.tsx | 238 +++++++++-------- .../src/components/ForcedWithdrawalDialog.tsx | 2 +- .../InitiateForcedWithdrawalDialog.tsx | 87 +++--- frontend/src/components/Transfer.tsx | 2 +- frontend/src/context/NotificationContext.tsx | 34 +-- frontend/src/hooks/useCompact.ts | 2 +- frontend/src/hooks/useNotification.ts | 12 + frontend/src/hooks/useSessionPoller.ts | 83 +++--- 10 files changed, 432 insertions(+), 311 deletions(-) create mode 100644 frontend/src/hooks/useNotification.ts diff --git a/frontend/src/components/BalanceDisplay.tsx b/frontend/src/components/BalanceDisplay.tsx index a170e7c..04e63c5 100644 --- a/frontend/src/components/BalanceDisplay.tsx +++ b/frontend/src/components/BalanceDisplay.tsx @@ -7,6 +7,35 @@ import { Transfer } from './Transfer'; import { InitiateForcedWithdrawalDialog } from './InitiateForcedWithdrawalDialog'; import { ForcedWithdrawalDialog } from './ForcedWithdrawalDialog'; +interface Token { + tokenAddress: string; + name: string; + symbol: string; + decimals: number; +} + +interface ResourceLock { + resetPeriod: number; + isMultichain: boolean; +} + +interface Balance { + chainId: string; + lockId: string; + allocatableBalance: string; + allocatedBalance: string; + balanceAvailableToAllocate: string; + withdrawalStatus: number; + withdrawableAt: string; + balance: string; + tokenName: string; + token?: Token; + resourceLock?: ResourceLock; + formattedAllocatableBalance?: string; + decimals: number; + symbol: string; +} + // Utility function to format reset period const formatResetPeriod = (seconds: number): string => { if (seconds < 60) return `${seconds} seconds`; @@ -23,7 +52,7 @@ export function BalanceDisplay(): JSX.Element | null { const [isWithdrawalDialogOpen, setIsWithdrawalDialogOpen] = useState(false); const [isExecuteDialogOpen, setIsExecuteDialogOpen] = useState(false); const [selectedLockId, setSelectedLockId] = useState(''); - const [selectedLock, setSelectedLock] = useState(null); + const [selectedLock, setSelectedLock] = useState(null); if (!isConnected) return null; diff --git a/frontend/src/components/CreateAllocation.tsx b/frontend/src/components/CreateAllocation.tsx index feabd7a..b645601 100644 --- a/frontend/src/components/CreateAllocation.tsx +++ b/frontend/src/components/CreateAllocation.tsx @@ -1,26 +1,55 @@ -import { useState, useEffect } from 'react' -import { useAccount } from 'wagmi' -import { useBalances } from '../hooks/useBalances' -import { useNotification } from '../context/NotificationContext' -import { useAllocatorAPI } from '../hooks/useAllocatorAPI' -import { parseUnits, formatUnits } from 'viem' +import { useState, useEffect, useCallback } from 'react'; +import { useAccount } from 'wagmi'; +import { useBalances } from '../hooks/useBalances'; +import { useNotification } from '../hooks/useNotification'; +import { useAllocatorAPI } from '../hooks/useAllocatorAPI'; +import { parseUnits, formatUnits } from 'viem'; + +interface Token { + tokenAddress: string; + name: string; + symbol: string; + decimals: number; +} + +interface ResourceLock { + resetPeriod: number; + isMultichain: boolean; +} + +interface Balance { + chainId: string; + lockId: string; + allocatableBalance: string; + allocatedBalance: string; + balanceAvailableToAllocate: string; + withdrawalStatus: number; + withdrawableAt: string; + balance: string; + tokenName: string; + token?: Token; + resourceLock?: ResourceLock; + formattedAllocatableBalance?: string; + decimals: number; + symbol: string; +} const EXPIRY_OPTIONS = [ { label: '1 minute', value: '1min', seconds: 60 }, { label: '10 minutes', value: '10min', seconds: 600 }, { label: '1 hour', value: '1hour', seconds: 3600 }, - { label: 'Custom', value: 'custom', seconds: 0 } -] + { label: 'Custom', value: 'custom', seconds: 0 }, +]; interface CreateAllocationProps { - sessionToken: string + sessionToken: string; } export function CreateAllocation({ sessionToken }: CreateAllocationProps) { - const { address, isConnected } = useAccount() - const { balances, isLoading: isLoadingBalances } = useBalances() - const { showNotification } = useNotification() - const { createAllocation, getResourceLockDecimals } = useAllocatorAPI() + const { address, isConnected } = useAccount(); + const { balances, isLoading: isLoadingBalances } = useBalances(); + const { showNotification } = useNotification(); + const { createAllocation, getResourceLockDecimals } = useAllocatorAPI(); const [formData, setFormData] = useState({ lockId: '', @@ -30,7 +59,7 @@ export function CreateAllocation({ sessionToken }: CreateAllocationProps) { expiration: '', witnessHash: '', witnessTypestring: '', - }) + }); const [errors, setErrors] = useState({ lockId: '', @@ -38,83 +67,93 @@ export function CreateAllocation({ sessionToken }: CreateAllocationProps) { arbiterAddress: '', nonce: '', expiration: '', - }) + }); - const [showWitnessFields, setShowWitnessFields] = useState(false) - const [selectedLock, setSelectedLock] = useState(null) - const [lockDecimals, setLockDecimals] = useState(18) - const [expiryOption, setExpiryOption] = useState('10min') - const [customExpiry, setCustomExpiry] = useState(false) - const [isSubmitting, setIsSubmitting] = useState(false) + const [showWitnessFields, setShowWitnessFields] = useState(false); + const [selectedLock, setSelectedLock] = useState(null); + const [lockDecimals, setLockDecimals] = useState(18); + const [expiryOption, setExpiryOption] = useState('10min'); + const [customExpiry, setCustomExpiry] = useState(false); + const [isSubmitting, setIsSubmitting] = useState(false); + + const generateNewNonce = useCallback(() => { + if (address) { + const addressBytes = address.slice(2); + const randomBytes = Array.from({ length: 24 }, () => + Math.floor(Math.random() * 16).toString(16) + ).join(''); + const nonce = `0x${addressBytes}${randomBytes}`; + setFormData((prev) => ({ ...prev, nonce })); + } + }, [address]); // Generate random nonce on mount useEffect(() => { if (address) { - generateNewNonce() + generateNewNonce(); } - }, [address]) + }, [address, generateNewNonce]); // Set default expiration (10 minutes from now) useEffect(() => { - const tenMinutesFromNow = Math.floor(Date.now() / 1000) + 600 - setFormData(prev => ({ + const tenMinutesFromNow = Math.floor(Date.now() / 1000) + 600; + setFormData((prev) => ({ ...prev, - expiration: tenMinutesFromNow.toString() - })) - }, []) + expiration: tenMinutesFromNow.toString(), + })); + }, []); // Fetch decimals when lock changes useEffect(() => { if (selectedLock) { getResourceLockDecimals(selectedLock.chainId, selectedLock.lockId) - .then(decimals => setLockDecimals(decimals)) - .catch(console.error) + .then((decimals) => setLockDecimals(decimals)) + .catch(console.error); } - }, [selectedLock]) + }, [selectedLock, getResourceLockDecimals]); - const handleInputChange = (e: React.ChangeEvent) => { - const { name, value } = e.target - setFormData(prev => ({ ...prev, [name]: value })) - setErrors(prev => ({ ...prev, [name]: '' })) + const handleInputChange = ( + e: React.ChangeEvent + ) => { + const { name, value } = e.target; + setFormData((prev) => ({ ...prev, [name]: value })); + setErrors((prev) => ({ ...prev, [name]: '' })); if (name === 'lockId') { - const lock = balances.find(b => b.lockId === value) - setSelectedLock(lock) - } - } - - const generateNewNonce = () => { - if (address) { - const addressBytes = address.slice(2) - const randomBytes = Array.from({ length: 24 }, () => - Math.floor(Math.random() * 16).toString(16) - ).join('') - setFormData(prev => ({ - ...prev, - nonce: '0x' + addressBytes + randomBytes - })) + const lock = balances.find((b) => b.lockId === value); + if (lock) { + setSelectedLock({ + ...lock, + balance: lock.allocatableBalance, + tokenName: lock.token?.name || '', + decimals: lock.token?.decimals || 18, + symbol: lock.token?.symbol || '', + }); + } else { + setSelectedLock(null); + } } - } + }; const handleExpiryChange = (value: string) => { - setExpiryOption(value) - const now = Math.floor(Date.now() / 1000) + setExpiryOption(value); + const now = Math.floor(Date.now() / 1000); if (value === 'custom') { - setCustomExpiry(true) - return + setCustomExpiry(true); + return; } - setCustomExpiry(false) - const option = EXPIRY_OPTIONS.find(opt => opt.value === value) + setCustomExpiry(false); + const option = EXPIRY_OPTIONS.find((opt) => opt.value === value); if (option) { - setFormData(prev => ({ + setFormData((prev) => ({ ...prev, - expiration: (now + option.seconds).toString() - })) - setErrors(prev => ({ ...prev, expiration: '' })) + expiration: (now + option.seconds).toString(), + })); + setErrors((prev) => ({ ...prev, expiration: '' })); } - } + }; const validateForm = () => { const newErrors = { @@ -123,56 +162,56 @@ export function CreateAllocation({ sessionToken }: CreateAllocationProps) { arbiterAddress: '', nonce: '', expiration: '', - } + }; if (!formData.lockId) { - newErrors.lockId = 'Resource lock is required' + newErrors.lockId = 'Resource lock is required'; } if (!formData.amount) { - newErrors.amount = 'Amount is required' + newErrors.amount = 'Amount is required'; } else if (selectedLock) { try { - const amountBigInt = parseUnits(formData.amount, lockDecimals) - const availableBigInt = BigInt(selectedLock.balanceAvailableToAllocate) + const amountBigInt = parseUnits(formData.amount, lockDecimals); + const availableBigInt = BigInt(selectedLock.balanceAvailableToAllocate); if (amountBigInt > availableBigInt) { - newErrors.amount = 'Amount exceeds available balance' + newErrors.amount = 'Amount exceeds available balance'; } - } catch (err) { - newErrors.amount = 'Invalid amount' + } catch (_err) { + newErrors.amount = 'Invalid amount'; } } if (!formData.arbiterAddress) { - newErrors.arbiterAddress = 'Arbiter address is required' + newErrors.arbiterAddress = 'Arbiter address is required'; } else if (!/^0x[a-fA-F0-9]{40}$/.test(formData.arbiterAddress)) { - newErrors.arbiterAddress = 'Invalid address format' + newErrors.arbiterAddress = 'Invalid address format'; } if (!formData.nonce) { - newErrors.nonce = 'Nonce is required' + newErrors.nonce = 'Nonce is required'; } if (!formData.expiration) { - newErrors.expiration = 'Expiration is required' + newErrors.expiration = 'Expiration is required'; } else { - const expirationTime = parseInt(formData.expiration) - const now = Math.floor(Date.now() / 1000) + const expirationTime = parseInt(formData.expiration); + const now = Math.floor(Date.now() / 1000); if (isNaN(expirationTime) || expirationTime <= now) { - newErrors.expiration = 'Expiration must be in the future' + newErrors.expiration = 'Expiration must be in the future'; } } - setErrors(newErrors) - return Object.values(newErrors).every(error => !error) - } + setErrors(newErrors); + return Object.values(newErrors).every((error) => !error); + }; const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault() - if (!validateForm() || !selectedLock || !address) return + e.preventDefault(); + if (!validateForm() || !selectedLock || !address) return; try { - setIsSubmitting(true) + setIsSubmitting(true); const request = { chainId: selectedLock.chainId.toString(), @@ -185,18 +224,18 @@ export function CreateAllocation({ sessionToken }: CreateAllocationProps) { amount: parseUnits(formData.amount, lockDecimals).toString(), ...(showWitnessFields && { witnessTypeString: formData.witnessTypestring, - witnessHash: formData.witnessHash - }) - } - } + witnessHash: formData.witnessHash, + }), + }, + }; - const result = await createAllocation(sessionToken, request) + const result = await createAllocation(sessionToken, request); showNotification({ type: 'success', title: 'Allocation Created', message: `Successfully created allocation with hash: ${result.hash}`, - }) + }); // Reset form setFormData({ @@ -207,35 +246,39 @@ export function CreateAllocation({ sessionToken }: CreateAllocationProps) { expiration: '', witnessHash: '', witnessTypestring: '', - }) - setShowWitnessFields(false) - generateNewNonce() - + }); + setShowWitnessFields(false); + generateNewNonce(); } catch (error) { showNotification({ type: 'error', title: 'Error', - message: error instanceof Error ? error.message : 'Failed to create allocation', - }) + message: + error instanceof Error + ? error.message + : 'Failed to create allocation', + }); } finally { - setIsSubmitting(false) + setIsSubmitting(false); } - } + }; - if (!isConnected) return null + if (!isConnected) return null; if (isLoadingBalances) { return (
- ) + ); } return (
-

Create Allocation

+

+ Create Allocation +

Create a new allocation from your available resource locks.

@@ -340,7 +383,7 @@ export function CreateAllocation({ sessionToken }: CreateAllocationProps) { onChange={(e) => handleExpiryChange(e.target.value)} className="w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-gray-300 focus:outline-none focus:border-[#00ff00] transition-colors" > - {EXPIRY_OPTIONS.map(option => ( + {EXPIRY_OPTIONS.map((option) => ( @@ -371,7 +414,10 @@ export function CreateAllocation({ sessionToken }: CreateAllocationProps) { onChange={(e) => setShowWitnessFields(e.target.checked)} className="w-4 h-4 bg-gray-800 border-gray-700 rounded focus:ring-[#00ff00]" /> -
@@ -423,5 +469,5 @@ export function CreateAllocation({ sessionToken }: CreateAllocationProps) {
- ) + ); } diff --git a/frontend/src/components/DepositForm.tsx b/frontend/src/components/DepositForm.tsx index 0fb5b37..027e0c6 100644 --- a/frontend/src/components/DepositForm.tsx +++ b/frontend/src/components/DepositForm.tsx @@ -1,96 +1,105 @@ -import { useState } from 'react' -import { useAccount, useBalance } from 'wagmi' -import { formatEther, parseEther, parseUnits } from 'viem' -import { useCompact } from '../hooks/useCompact' -import { useNotification } from '../context/NotificationContext' -import { useERC20 } from '../hooks/useERC20' -import { useAllocatorAPI } from '../hooks/useAllocatorAPI' +import { useState } from 'react'; +import { useAccount, useBalance } from 'wagmi'; +import { formatEther, parseEther, parseUnits } from 'viem'; +import { useCompact } from '../hooks/useCompact'; +import { useNotification } from '../hooks/useNotification'; +import { useERC20 } from '../hooks/useERC20'; +import { useAllocatorAPI } from '../hooks/useAllocatorAPI'; -type TokenType = 'native' | 'erc20' +type TokenType = 'native' | 'erc20'; export function DepositForm() { - const { address, isConnected } = useAccount() - const { data: ethBalance } = useBalance({ address }) - const [amount, setAmount] = useState('') - const [tokenType, setTokenType] = useState('native') - const [tokenAddress, setTokenAddress] = useState('') - const [isLoading, setIsLoading] = useState(false) - const [isApproving, setIsApproving] = useState(false) - const { deposit } = useCompact() - const { showNotification } = useNotification() - const { allocatorAddress } = useAllocatorAPI() - const { - balance, - allowance, - decimals, - rawBalance, - rawAllowance, + const { address, isConnected } = useAccount(); + const { data: ethBalance } = useBalance({ address }); + const [amount, setAmount] = useState(''); + const [tokenType, setTokenType] = useState('native'); + const [tokenAddress, setTokenAddress] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const [isApproving, setIsApproving] = useState(false); + const { deposit } = useCompact(); + const { showNotification } = useNotification(); + const { allocatorAddress } = useAllocatorAPI(); + const { + balance, + allowance, + decimals, + rawBalance, + rawAllowance, approve, name, symbol, isValid, - isLoading: isLoadingToken + isLoading: isLoadingToken, } = useERC20( - tokenType === 'erc20' && tokenAddress ? tokenAddress as `0x${string}` : undefined - ) + tokenType === 'erc20' && tokenAddress + ? (tokenAddress as `0x${string}`) + : undefined + ); const validateAmount = () => { - if (!amount) return null + if (!amount) return null; // Check if amount is zero or negative for both token types try { - const numAmount = parseFloat(amount) + const numAmount = parseFloat(amount); if (numAmount <= 0) { - return { type: 'error', message: 'Amount must be greater than zero' } + return { type: 'error', message: 'Amount must be greater than zero' }; } - } catch (e) { - return { type: 'error', message: 'Invalid amount format' } + } catch (_e) { + return { type: 'error', message: 'Invalid amount format' }; } // For ERC20 tokens if (tokenType === 'erc20') { - if (!tokenAddress || decimals === undefined) return null - + if (!tokenAddress || decimals === undefined) return null; + // Check decimal places - const decimalParts = amount.split('.') + const decimalParts = amount.split('.'); if (decimalParts.length > 1 && decimalParts[1].length > decimals) { - return { type: 'error', message: `Invalid amount (greater than ${decimals} decimals)` } + return { + type: 'error', + message: `Invalid amount (greater than ${decimals} decimals)`, + }; } try { - const parsedAmount = parseUnits(amount, decimals) - const allowanceBigInt = rawAllowance ? BigInt(rawAllowance.toString()) : BigInt(0) - const balanceBigInt = rawBalance ? BigInt(rawBalance.toString()) : BigInt(0) + const parsedAmount = parseUnits(amount, decimals); + const allowanceBigInt = rawAllowance + ? BigInt(rawAllowance.toString()) + : BigInt(0); + const balanceBigInt = rawBalance + ? BigInt(rawBalance.toString()) + : BigInt(0); if (rawBalance && parsedAmount > balanceBigInt) { - return { type: 'error', message: 'Insufficient Balance' } + return { type: 'error', message: 'Insufficient Balance' }; } if (parsedAmount > allowanceBigInt) { - return { type: 'warning', message: 'Insufficient Allowance' } + return { type: 'warning', message: 'Insufficient Allowance' }; } - return null - } catch (e) { - return { type: 'error', message: 'Invalid amount format' } + return null; + } catch (_e) { + return { type: 'error', message: 'Invalid amount format' }; } } - + // For native ETH if (tokenType === 'native' && ethBalance) { try { - const parsedAmount = parseEther(amount) + const parsedAmount = parseEther(amount); if (parsedAmount > ethBalance.value) { - return { type: 'error', message: 'Insufficient ETH Balance' } + return { type: 'error', message: 'Insufficient ETH Balance' }; } - return null - } catch (e) { - return { type: 'error', message: 'Invalid amount format' } + return null; + } catch (_e) { + return { type: 'error', message: 'Invalid amount format' }; } } - - return null - } - const amountValidation = validateAmount() + return null; + }; + + const amountValidation = validateAmount(); const handleDeposit = async () => { if (!amount || isNaN(Number(amount))) { @@ -98,8 +107,8 @@ export function DepositForm() { type: 'error', title: 'Invalid Amount', message: 'Please enter a valid amount', - }) - return + }); + return; } if (!allocatorAddress) { @@ -107,8 +116,8 @@ export function DepositForm() { type: 'error', title: 'No Allocator Available', message: 'Unable to get allocator address', - }) - return + }); + return; } if (!address) { @@ -116,94 +125,97 @@ export function DepositForm() { type: 'error', title: 'Wallet Not Connected', message: 'Please connect your wallet', - }) - return + }); + return; } - const sessionId = localStorage.getItem(`session-${address}`) + const sessionId = localStorage.getItem(`session-${address}`); if (!sessionId) { showNotification({ type: 'error', title: 'Not Signed In', message: 'Please sign in with your Ethereum account', - }) - return + }); + return; } try { - setIsLoading(true) - const parsedAmount = tokenType === 'native' - ? parseEther(amount) - : parseUnits(amount, decimals!) - - const hexAllocatorAddress = allocatorAddress as `0x${string}` - + setIsLoading(true); + const parsedAmount = + tokenType === 'native' + ? parseEther(amount) + : parseUnits(amount, decimals!); + + const hexAllocatorAddress = allocatorAddress as `0x${string}`; + await deposit( - tokenType === 'native' + tokenType === 'native' ? { allocator: hexAllocatorAddress, value: parsedAmount, - isNative: true + isNative: true, } : { token: tokenAddress as `0x${string}`, allocator: hexAllocatorAddress, amount: parsedAmount, - isNative: false + isNative: false, } - ) + ); showNotification({ type: 'success', title: 'Deposit Submitted', message: `Successfully deposited ${amount} ${tokenType === 'native' ? 'ETH' : symbol || 'tokens'}`, - }) + }); // Reset form - setAmount('') + setAmount(''); if (tokenType === 'erc20') { - setTokenAddress('') + setTokenAddress(''); } } catch (error) { - console.error('Error depositing:', error) + console.error('Error depositing:', error); showNotification({ type: 'error', title: 'Deposit Failed', message: error instanceof Error ? error.message : 'Failed to deposit', - }) + }); } finally { - setIsLoading(false) + setIsLoading(false); } - } + }; const handleApprove = async () => { - if (!tokenAddress) return - + if (!tokenAddress) return; + try { - setIsApproving(true) - await approve() - + setIsApproving(true); + await approve(); + showNotification({ type: 'success', title: 'Approval Submitted', - message: 'Please wait while the approval transaction is being confirmed...', - }) + message: + 'Please wait while the approval transaction is being confirmed...', + }); } catch (error) { - console.error('Error approving:', error) + console.error('Error approving:', error); showNotification({ type: 'error', title: 'Approval Failed', - message: error instanceof Error - ? `Approval failed: ${error.message}` - : 'Failed to approve token', - }) + message: + error instanceof Error + ? `Approval failed: ${error.message}` + : 'Failed to approve token', + }); } finally { - setIsApproving(false) + setIsApproving(false); } - } + }; if (!isConnected || !address) { - return null + return null; } return ( @@ -245,7 +257,7 @@ export function DepositForm() { {/* Amount Input */}
- ) + ); } diff --git a/frontend/src/components/ForcedWithdrawalDialog.tsx b/frontend/src/components/ForcedWithdrawalDialog.tsx index e0c6922..44b188e 100644 --- a/frontend/src/components/ForcedWithdrawalDialog.tsx +++ b/frontend/src/components/ForcedWithdrawalDialog.tsx @@ -3,7 +3,7 @@ import { formatUnits, parseUnits, isAddress } from 'viem'; import { useAccount, useChainId } from 'wagmi'; import { switchNetwork } from '@wagmi/core'; import { useCompact } from '../hooks/useCompact'; -import { useNotification } from '../context/NotificationContext'; +import { useNotification } from '../hooks/useNotification'; import { config } from '../config/wagmi'; interface ForcedWithdrawalDialogProps { diff --git a/frontend/src/components/InitiateForcedWithdrawalDialog.tsx b/frontend/src/components/InitiateForcedWithdrawalDialog.tsx index bbff3d4..8950910 100644 --- a/frontend/src/components/InitiateForcedWithdrawalDialog.tsx +++ b/frontend/src/components/InitiateForcedWithdrawalDialog.tsx @@ -1,54 +1,57 @@ -import { useState } from 'react' -import { useCompact } from '../hooks/useCompact' -import { useNotification } from '../context/NotificationContext' +import { useState } from 'react'; +import { useCompact } from '../hooks/useCompact'; +import { useNotification } from '../hooks/useNotification'; interface InitiateForcedWithdrawalDialogProps { - isOpen: boolean - onClose: () => void - lockId: string - resetPeriod: number + isOpen: boolean; + onClose: () => void; + lockId: string; + resetPeriod: number; } export function InitiateForcedWithdrawalDialog({ isOpen, onClose, lockId, - resetPeriod + resetPeriod, }: InitiateForcedWithdrawalDialogProps) { - const [isLoading, setIsLoading] = useState(false) - const { enableForcedWithdrawal } = useCompact() - const { showNotification } = useNotification() + const [isLoading, setIsLoading] = useState(false); + const { enableForcedWithdrawal } = useCompact(); + const { showNotification } = useNotification(); const handleInitiateWithdrawal = async () => { - if (isLoading) return + if (isLoading) return; try { - setIsLoading(true) + setIsLoading(true); await enableForcedWithdrawal({ - args: [BigInt(lockId)] - }) + args: [BigInt(lockId)], + }); showNotification({ type: 'success', title: 'Forced Withdrawal Initiated', - message: 'The timelock period has started' - }) + message: 'The timelock period has started', + }); - onClose() - } catch (error: any) { - console.error('Error initiating forced withdrawal:', error) + onClose(); + } catch (error: unknown) { + console.error('Error initiating forced withdrawal:', error); showNotification({ type: 'error', title: 'Transaction Failed', - message: error.message || 'Failed to initiate forced withdrawal' - }) + message: + error instanceof Error + ? error.message + : 'Failed to initiate forced withdrawal', + }); } finally { - setIsLoading(false) + setIsLoading(false); } - } + }; - if (!isOpen) return null + if (!isOpen) return null; // Format reset period const formatResetPeriod = (seconds: number): string => { @@ -61,24 +64,42 @@ export function InitiateForcedWithdrawalDialog({ return (
-

Initiate Forced Withdrawal

+

+ Initiate Forced Withdrawal +

- Are you sure you want to initiate a forced withdrawal? This will start a timelock period. + Are you sure you want to initiate a forced withdrawal? This will start + a timelock period.

- - + +
-

Warning: Timelock Period

+

+ Warning: Timelock Period +

- Initiating a forced withdrawal from this resource lock will start a timelock period lasting {formatResetPeriod(resetPeriod)}. You will need to wait for this period to end, - then submit another transaction to perform the forced withdrawal from this resource lock. To begin using this resource lock again, you must submit another transaction to disable forced withdrawals. + Initiating a forced withdrawal from this resource lock will + start a timelock period lasting{' '} + {formatResetPeriod(resetPeriod)}. You will need to wait for + this period to end, then submit another transaction to perform + the forced withdrawal from this resource lock. To begin using + this resource lock again, you must submit another transaction + to disable forced withdrawals.

@@ -103,5 +124,5 @@ export function InitiateForcedWithdrawalDialog({
- ) + ); } diff --git a/frontend/src/components/Transfer.tsx b/frontend/src/components/Transfer.tsx index 4e77204..b9dbb68 100644 --- a/frontend/src/components/Transfer.tsx +++ b/frontend/src/components/Transfer.tsx @@ -1,6 +1,6 @@ import { useAccount, useChainId } from 'wagmi'; import { switchNetwork } from '@wagmi/core'; -import { useNotification } from '../context/NotificationContext'; +import { useNotification } from '../hooks/useNotification'; import { config } from '../config/wagmi'; interface TransferProps { diff --git a/frontend/src/context/NotificationContext.tsx b/frontend/src/context/NotificationContext.tsx index 1729427..cc9f18a 100644 --- a/frontend/src/context/NotificationContext.tsx +++ b/frontend/src/context/NotificationContext.tsx @@ -1,37 +1,31 @@ -import { createContext, useContext, ReactNode } from 'react' +import { createContext, ReactNode } from 'react'; interface NotificationContextType { showNotification: (notification: { - type: 'success' | 'error' | 'warning' | 'info' - title: string - message: string - }) => void + type: 'success' | 'error' | 'warning' | 'info'; + title: string; + message: string; + }) => void; } -const NotificationContext = createContext(undefined) +export const NotificationContext = createContext< + NotificationContextType | undefined +>(undefined); export function NotificationProvider({ children }: { children: ReactNode }) { const showNotification = (notification: { - type: 'success' | 'error' | 'warning' | 'info' - title: string - message: string + type: 'success' | 'error' | 'warning' | 'info'; + title: string; + message: string; }) => { // For now, just console.log the notification // You can implement a proper notification system later - console.log('Notification:', notification) - } + console.log('Notification:', notification); + }; return ( {children} - ) -} - -export function useNotification() { - const context = useContext(NotificationContext) - if (context === undefined) { - throw new Error('useNotification must be used within a NotificationProvider') - } - return context + ); } diff --git a/frontend/src/hooks/useCompact.ts b/frontend/src/hooks/useCompact.ts index bfcd562..861a5ac 100644 --- a/frontend/src/hooks/useCompact.ts +++ b/frontend/src/hooks/useCompact.ts @@ -5,7 +5,7 @@ import { COMPACT_ADDRESS, isSupportedChain, } from '../../src/constants/contracts'; -import { useNotification } from '../context/NotificationContext'; +import { useNotification } from './useNotification'; import { mainnet, optimism, diff --git a/frontend/src/hooks/useNotification.ts b/frontend/src/hooks/useNotification.ts new file mode 100644 index 0000000..e7edcc2 --- /dev/null +++ b/frontend/src/hooks/useNotification.ts @@ -0,0 +1,12 @@ +import { useContext } from 'react'; +import { NotificationContext } from '../context/NotificationContext'; + +export function useNotification() { + const context = useContext(NotificationContext); + if (context === undefined) { + throw new Error( + 'useNotification must be used within a NotificationProvider' + ); + } + return context; +} diff --git a/frontend/src/hooks/useSessionPoller.ts b/frontend/src/hooks/useSessionPoller.ts index ba1f78a..b9b3506 100644 --- a/frontend/src/hooks/useSessionPoller.ts +++ b/frontend/src/hooks/useSessionPoller.ts @@ -1,95 +1,96 @@ -import { useEffect } from 'react' -import { useAccount } from 'wagmi' +import { useEffect } from 'react'; +import { useAccount } from 'wagmi'; -const POLLING_INTERVAL = 5000 // 5 seconds +const POLLING_INTERVAL = 5000; // 5 seconds interface SessionResponse { session?: { - id: string - address: string - expiresAt: string - } - error?: string + id: string; + address: string; + expiresAt: string; + }; + error?: string; } -export function useSessionPoller(onSessionUpdate: (sessionId: string | null) => void) { - const { address } = useAccount() +export function useSessionPoller( + onSessionUpdate: (sessionId: string | null) => void +) { + const { address } = useAccount(); useEffect(() => { // Clear session if no address if (!address) { - localStorage.removeItem(`session-${address}`) - onSessionUpdate(null) - return + localStorage.removeItem(`session-${address}`); + onSessionUpdate(null); + return; } // Get session for current address - const sessionId = localStorage.getItem(`session-${address}`) + const sessionId = localStorage.getItem(`session-${address}`); if (!sessionId) { - onSessionUpdate(null) - return + onSessionUpdate(null); + return; } // Set initial session - onSessionUpdate(sessionId) + onSessionUpdate(sessionId); // Start polling const intervalId = setInterval(async () => { try { const response = await fetch('/session', { headers: { - 'x-session-id': sessionId - } - }) + 'x-session-id': sessionId, + }, + }); if (!response.ok) { - throw new Error('Session invalid') + throw new Error('Session invalid'); } - const data: SessionResponse = await response.json() + const data: SessionResponse = await response.json(); // Verify session belongs to current address if (data.session?.address.toLowerCase() !== address.toLowerCase()) { - throw new Error('Session address mismatch') + throw new Error('Session address mismatch'); } // Check if session has expired - const expiryTime = new Date(data.session.expiresAt).getTime() + const expiryTime = new Date(data.session.expiresAt).getTime(); if (expiryTime < Date.now()) { - throw new Error('Session expired') + throw new Error('Session expired'); } - - } catch (error) { + } catch (_error) { // On any error, clear the session - localStorage.removeItem(`session-${address}`) - onSessionUpdate(null) + localStorage.removeItem(`session-${address}`); + onSessionUpdate(null); } - }, POLLING_INTERVAL) + }, POLLING_INTERVAL); // Cleanup on unmount or address change return () => { - clearInterval(intervalId) - } - }, [address, onSessionUpdate]) + clearInterval(intervalId); + }; + }, [address, onSessionUpdate]); // Helper function to store new session const storeSession = (sessionId: string) => { if (address) { - localStorage.setItem(`session-${address}`, sessionId) - onSessionUpdate(sessionId) + localStorage.setItem(`session-${address}`, sessionId); + onSessionUpdate(sessionId); } - } + }; // Helper function to clear session const clearSession = () => { if (address) { - localStorage.removeItem(`session-${address}`) - onSessionUpdate(null) + localStorage.removeItem(`session-${address}`); + onSessionUpdate(null); } - } + }; return { storeSession, - clearSession - } + clearSession, + }; }