Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions app/components/WalletConnectionProgress.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import React from 'react';
import { ConnectionStep } from '@/app/hooks/useWalletConnection';
import { Loader2, CheckCircle, AlertCircle, Wallet } from 'lucide-react';

interface WalletConnectionProgressProps {
step: ConnectionStep;
walletName?: string;
className?: string;
}

const WalletConnectionProgress: React.FC<WalletConnectionProgressProps> = ({
step,
walletName,
className = ''
}) => {
const getStepIcon = () => {
switch (step) {
case 'connecting':
return <Loader2 className="w-5 h-5 text-[#A26DFF] animate-spin" />;
case 'success':
case 'connected':
return <CheckCircle className="w-5 h-5 text-green-400" />;
case 'failed':
return <AlertCircle className="w-5 h-5 text-red-400" />;
default:
return <Wallet className="w-5 h-5 text-gray-400" />;
}
};

const getStepMessage = () => {
switch (step) {
case 'connecting':
return `Connecting to ${walletName || 'wallet'}...`;
case 'success':
return `Successfully connected to ${walletName || 'wallet'}!`;
case 'connected':
return `Connected to ${walletName || 'wallet'}`;
case 'failed':
return `Failed to connect to ${walletName || 'wallet'}`;
default:
return 'Ready to connect';
}
};

const getStepColor = () => {
switch (step) {
case 'connecting':
return 'text-[#A26DFF]';
case 'success':
case 'connected':
return 'text-green-400';
case 'failed':
return 'text-red-400';
default:
return 'text-gray-400';
}
};

const getProgressWidth = () => {
switch (step) {
case 'idle':
return '0%';
case 'connecting':
return '50%';
case 'success':
case 'connected':
return '100%';
case 'failed':
return '30%';
default:
return '0%';
}
};

return (
<div className={`${className}`}>
{/* Progress Bar */}
<div className="w-full bg-gray-700 rounded-full h-1 mb-3 overflow-hidden">
<div
className={`
h-full transition-all duration-500 ease-out
${step === 'success' || step === 'connected'
? 'bg-green-400'
: step === 'failed'
? 'bg-red-400'
: 'bg-[#A26DFF]'
}
`}
style={{ width: getProgressWidth() }}
/>
</div>

{/* Status */}
<div className="flex items-center gap-3">
<div className="flex-shrink-0">
{getStepIcon()}
</div>
<div className="flex-1 min-w-0">
<p className={`text-sm font-medium ${getStepColor()}`}>
{getStepMessage()}
</p>
{step === 'connecting' && (
<p className="text-xs text-gray-500 mt-1">
Please check your wallet and approve the connection
</p>
)}
</div>
</div>
</div>
);
};

export default WalletConnectionProgress;
125 changes: 125 additions & 0 deletions app/components/WalletEmptyState.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import React from 'react';
import { Wallet, Download, ExternalLink } from 'lucide-react';
import { WalletInfo } from '@/app/utils/walletDetection';

interface WalletEmptyStateProps {
category: 'ethereum' | 'starknet';
availableWallets: WalletInfo[];
onRefresh: () => void;
className?: string;
}

const WalletEmptyState: React.FC<WalletEmptyStateProps> = ({
category,
availableWallets,
onRefresh,
className = ''
}) => {
const categoryName = category === 'ethereum' ? 'Ethereum' : 'Starknet';
const primaryWallet = availableWallets[0];

return (
<div className={`text-center py-8 px-4 ${className}`}>
{/* Icon */}
<div className="mb-4 flex justify-center">
<div className="w-16 h-16 rounded-full bg-[#291A43]/30 flex items-center justify-center">
<Wallet className="w-8 h-8 text-gray-400" />
</div>
</div>

{/* Title */}
<h3 className="text-white text-lg font-semibold mb-2">
No {categoryName} Wallets Found
</h3>

{/* Description */}
<p className="text-gray-400 text-sm mb-6 max-w-sm mx-auto">
To connect to {categoryName}, you'll need to install a compatible wallet extension.
</p>

{/* Primary Action - Install main wallet */}
{primaryWallet && (
<div className="mb-6">
<a
href={primaryWallet.downloadUrl}
target="_blank"
rel="noopener noreferrer"
className="
inline-flex items-center gap-3 px-6 py-3
bg-[#A26DFF] hover:bg-[#A26DFF]/90
text-white font-medium rounded-lg
transition-colors duration-200
"
>
<Download size={18} />
Install {primaryWallet.name}
</a>
<p className="text-xs text-gray-500 mt-2">
{primaryWallet.description}
</p>
</div>
)}

{/* Alternative Wallets */}
{availableWallets.length > 1 && (
<div className="mb-6">
<p className="text-sm text-gray-400 mb-3">Or choose an alternative:</p>
<div className="flex flex-wrap gap-2 justify-center">
{availableWallets.slice(1).map((wallet) => (
<a
key={wallet.id}
href={wallet.downloadUrl}
target="_blank"
rel="noopener noreferrer"
className="
inline-flex items-center gap-2 px-4 py-2
bg-[#291A43] hover:bg-[#342251]
text-white text-sm rounded-lg
transition-colors duration-200
"
>
<ExternalLink size={14} />
{wallet.name}
</a>
))}
</div>
</div>
)}

{/* Refresh Action */}
<div className="border-t border-gray-700 pt-4 mt-6">
<p className="text-xs text-gray-500 mb-3">
Already installed a wallet?
</p>
<button
onClick={onRefresh}
className="
inline-flex items-center gap-2 px-4 py-2
bg-[#291A43] hover:bg-[#342251]
text-white text-sm rounded-lg
transition-colors duration-200
border border-gray-600 hover:border-gray-500
"
>
<Wallet size={14} />
Check Again
</button>
</div>

{/* Help Text */}
<p className="text-xs text-gray-500 mt-4">
Need help? Check our{' '}
<a
href={category === 'ethereum' ? 'https://ethereum.org/wallets' : 'https://starknet.io/wallets'}
target="_blank"
rel="noopener noreferrer"
className="text-[#A26DFF] hover:text-[#A26DFF]/80 underline"
>
wallet setup guide
</a>
</p>
</div>
);
};

export default WalletEmptyState;
116 changes: 116 additions & 0 deletions app/components/WalletError.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import React from 'react';
import { AlertCircle, RefreshCw, ExternalLink, X } from 'lucide-react';
import { WalletError, getWalletInstallUrl } from '@/app/utils/walletErrors';

interface WalletErrorProps {
error: WalletError;
walletName?: string;
onRetry?: () => void;
onDismiss?: () => void;
className?: string;
}

const WalletErrorComponent: React.FC<WalletErrorProps> = ({
error,
walletName,
onRetry,
onDismiss,
className = ''
}) => {
const installUrl = walletName ? getWalletInstallUrl(walletName) : null;

return (
<div
role="alert"
aria-live="polite"
className={`
relative p-4 rounded-lg border border-red-500/20 bg-red-500/5
backdrop-blur-sm transition-all duration-200 ${className}
`}
>
{/* Dismiss button */}
{onDismiss && (
<button
onClick={onDismiss}
className="absolute top-2 right-2 text-gray-400 hover:text-white transition-colors"
aria-label="Dismiss error"
>
<X size={16} />
</button>
)}

<div className="flex items-start gap-3">
{/* Error icon */}
<div className="flex-shrink-0 mt-0.5">
<AlertCircle className="w-5 h-5 text-red-400" />
</div>

<div className="flex-1 min-w-0">
{/* Error message */}
<h4 className="text-white font-medium text-sm mb-1">
{(() => {
switch (error.type) {
case 'CONNECTION_REJECTED': return 'Request Rejected';
case 'UNSUPPORTED_NETWORK': return 'Unsupported Network';
case 'NETWORK_MISMATCH': return 'Network Mismatch';
case 'WALLET_NOT_INSTALLED': return 'Wallet Not Installed';
case 'CONNECTION_FAILED': return 'Connection Failed';
default: return 'Something Went Wrong';
}
})()}
</h4>
<p className="text-gray-300 text-sm mb-2">
{error.message}
</p>

{/* Suggested action */}
{error.suggestedAction && (
<p className="text-gray-400 text-xs mb-3">
{error.suggestedAction}
</p>
)}

{/* Action buttons */}
<div className="flex items-center gap-2 flex-wrap">
{/* Retry button */}
{error.retryable && onRetry && (
<button
onClick={onRetry}
className="
inline-flex items-center gap-2 px-3 py-1.5
bg-[#291A43] hover:bg-[#342251]
text-white text-xs rounded-lg
transition-colors duration-200
border border-gray-600 hover:border-gray-500
"
>
<RefreshCw size={12} />
Try Again
</button>
)}

{/* Install wallet button - only show for WALLET_NOT_INSTALLED error */}
{error.type === 'WALLET_NOT_INSTALLED' && installUrl && installUrl !== '#' && (
<a
href={installUrl}
target="_blank"
rel="noopener noreferrer"
className="
inline-flex items-center gap-2 px-3 py-1.5
bg-[#A26DFF] hover:bg-[#A26DFF]/90
text-white text-xs rounded-lg
transition-colors duration-200
"
>
<ExternalLink size={12} />
Install {walletName}
</a>
)}
</div>
</div>
</div>
</div>
);
};

export default WalletErrorComponent;
Loading