Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
3 changes: 3 additions & 0 deletions src/components/ToolTipGuide.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.force-opacity-1 {
opacity: 1 !important;
}
61 changes: 39 additions & 22 deletions src/components/TooltipGuide.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useState, useEffect, type ReactNode } from 'react'
import { Tooltip } from 'react-tooltip'
import './ToolTipGuide.css'

export interface TooltipStep {
id: string
Expand Down Expand Up @@ -43,13 +44,24 @@ const TooltipGuide = ({
}
}

const handleSkip = () => {
setShowTooltips(false)
onComplete?.()
}

const renderTooltipContent = (content: string) => {
return (
<div className="flex flex-col gap-3">
<div className="text-base leading-relaxed">
{content}
</div>
<div className="flex justify-end">
<div className="flex justify-between items-center">
<button
onClick={handleSkip}
className="bg-transparent hover:bg-white/10 text-white/70 hover:text-white px-3 py-1.5 rounded-md text-sm transition-all duration-200 font-medium cursor-pointer border border-white/20"
>
Skip
</button>
<button
onClick={handleNextStep}
className="bg-white/20 hover:bg-white/30 text-white px-3 py-1.5 rounded-md text-sm transition-all duration-200 font-medium cursor-pointer"
Expand All @@ -64,27 +76,32 @@ const TooltipGuide = ({
return (
<>
{children(showTooltips)}
{showTooltips && steps.map((step, index) => (
<Tooltip
key={step.id}
id={step.id}
place={step.placement || 'top'}
isOpen={showTooltips && currentStep === index}
clickable={true}
style={{
backgroundColor: '#9400FF',
color: 'white',
borderRadius: '12px',
padding: '16px',
fontSize: '20px',
maxWidth: '350px',
zIndex: showTooltips && currentStep === index ? 10000 : 9999,
boxShadow: '0 10px 25px -5px rgba(0, 0, 0, 0.25), 0 8px 10px -6px rgba(0, 0, 0, 0.1)'
}}
>
{renderTooltipContent(step.content)}
</Tooltip>
))}
{showTooltips && steps.map((step, index) => {
const isCurrentStep = currentStep === index
return (
<Tooltip
key={step.id}
id={step.id}
place={step.placement || 'top'}
isOpen={isCurrentStep}
clickable={true}
className="force-opacity-1"
style={{
backgroundColor: '#9400FF',
color: 'white',
borderRadius: '12px',
padding: '16px',
fontSize: '20px',
maxWidth: '350px',
zIndex: isCurrentStep ? 10000 : -1,
boxShadow: '0 10px 25px -5px rgba(0, 0, 0, 0.25), 0 8px 10px -6px rgba(0, 0, 0, 0.1)',
opacity: 1,
}}
>
{renderTooltipContent(step.content)}
</Tooltip>
)
})}
</>
)
}
Expand Down
197 changes: 167 additions & 30 deletions src/components/WalletConnection.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { ConnectWalletList } from '@cardano-foundation/cardano-connect-with-wallet'
import { NetworkType } from '@cardano-foundation/cardano-connect-with-wallet-core'
import { useWalletStore } from '../stores/walletStore'
import { useNavigate } from 'react-router'
import { useState, useRef, useEffect } from 'react'

interface WalletConnectionProps {
variant?: 'default' | 'white'
Expand All @@ -14,30 +17,53 @@ const WalletConnection = ({
}: WalletConnectionProps) => {
const { isConnected, connect, disconnect } = useWalletStore()
const navigate = useNavigate()
const [showWalletList, setShowWalletList] = useState(false)
const [connectionError, setConnectionError] = useState<string | null>(null)
const [isConnecting, setIsConnecting] = useState(false)
const dropdownRef = useRef<HTMLDivElement>(null)

const handleConnect = async () => {
const onConnectWallet = async (walletName: string) => {
setIsConnecting(true)
setConnectionError(null)

try {
const walletNames = ['nami', 'eternl', 'flint', 'yoroi', 'gerowallet']
let connected = false
const success = await connect(walletName)

for (const walletName of walletNames) {
const success = await connect(walletName)
if (success) {
console.log(`Connected to ${walletName}`)
connected = true
navigate('/account')
break
}
}

if (!connected) {
console.warn('No compatible wallets found. Please install a supported Cardano wallet.')
if (success) {
setShowWalletList(false)
navigate('/account')
} else {
console.error(`Failed to connect to ${walletName} - connect function returned false`)
setConnectionError(`Failed to connect to ${walletName}. Please try again.`)
}
} catch (error) {
console.error('Failed to connect wallet:', error)
console.error(`Error connecting to ${walletName}:`, error)
setConnectionError(`Error connecting to ${walletName}: ${error instanceof Error ? error.message : 'Unknown error'}`)
} finally {
setIsConnecting(false)
}
}

// Add error handler for ConnectWalletList
const onConnectError = (walletName: string, error: Error) => {
console.error(`ConnectWalletList error for ${walletName}:`, error)
setConnectionError(`Error with ${walletName}: ${error.message}`)
}

useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
setShowWalletList(false)
setConnectionError(null);
}
}

document.addEventListener('mousedown', handleClickOutside)
return () => {
document.removeEventListener('mousedown', handleClickOutside)
}
}, [])

if (isConnected) {
return (
<div className="flex items-center space-x-4">
Expand All @@ -53,14 +79,13 @@ const WalletConnection = ({

const buttonClasses = variant === 'white'
? "flex py-3 px-8 justify-center items-center gap-2.5 rounded-md bg-white text-black font-medium cursor-pointer text-lg md:text-base"
: "flex py-2.5 px-6 justify-center items-center gap-2.5 self-stretch rounded-md border border-white/20 backdrop-blur-sm text-white font-medium z-40 cursor-pointer"
: "flex py-2.5 px-10 justify-center items-center gap-2.5 self-stretch rounded-md border border-white/20 backdrop-blur-sm text-white font-medium z-40 cursor-pointer"

if (showTitle || showDescription) {
return (
<div className="flex flex-col items-center justify-center w-full max-w-4xl mx-auto">
<div className="border-2 border-white rounded-3xl p-8 md:p-12 w-full">
<div className="flex items-start gap-4 mb-6">
{/* Logomark Icon */}
<div className="flex-shrink-0">
<img
src="/wallet-icon-white.svg"
Expand All @@ -84,24 +109,136 @@ const WalletConnection = ({
</p>
)}

<button
onClick={handleConnect}
className="bg-white text-black font-medium py-3 px-8 rounded-lg text-lg cursor-pointer"
>
Connect
</button>
<div className="relative" ref={dropdownRef}>
<button
onClick={() => setShowWalletList(!showWalletList)}
className="bg-white text-black font-medium py-3 px-8 rounded-lg text-lg cursor-pointer"
disabled={isConnecting}
>
{isConnecting ? 'Connecting...' : 'Connect'}
</button>

{showWalletList && (
<div className="absolute top-full left-0 mt-2 backdrop-blur-sm p-4 z-50 min-w-[200px]">
{connectionError && (
<div className="mb-3 p-2 bg-red-100 border border-red-300 rounded text-red-700 text-sm">
{connectionError}
</div>
)}
<ConnectWalletList
borderRadius={15}
gap={12}
primaryColor="#000000"
onConnect={onConnectWallet}
onConnectError={onConnectError}
supportedWallets={['nami', 'eternl', 'flint', 'yoroi', 'gerowallet']}
showUnavailableWallets={0}
peerConnectEnabled={false}
limitNetwork={NetworkType.TESTNET}
customCSS={`
font-family: Helvetica Light, sans-serif;
font-size: 1rem;
font-weight: 700;
width: 100%;
& > span {
padding: 10px 24px;
color: #ffffff;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 6px;
margin-bottom: 12px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
background: transparent;
backdrop-filter: blur(10px);
transition: all 0.2s ease;
cursor: pointer;
}
& > span:hover {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.3);
}
`}
/>
</div>
)}
</div>
</div>
</div>
)
}

return (
<button
onClick={handleConnect}
className={buttonClasses}
>
Connect Wallet
</button>
<div className="relative" ref={dropdownRef}>
<button
onClick={() => setShowWalletList(!showWalletList)}
className={buttonClasses}
disabled={isConnecting}
>
{isConnecting ? 'Connecting...' : 'Connect Wallet'}
</button>

{showWalletList && (
<div className="absolute top-full left-0 right-0 pt-3 z-50 animate-in slide-in-from-top-2 duration-300">
<ConnectWalletList
borderRadius={15}
gap={12}
primaryColor="#000000"
onConnect={onConnectWallet}
onConnectError={onConnectError}
supportedWallets={['nami', 'eternl', 'flint', 'yoroi']}
showUnavailableWallets={0}
peerConnectEnabled={false}
limitNetwork={NetworkType.TESTNET} // MUST BE CHANGED TO MAINNET to work on mainnet
customCSS={`
font-family: Helvetica Light, sans-serif;
font-size: 0.875rem;
font-weight: 700;
width: 100%;
& > span {
padding: 10px 12px;
color: #ffffff;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 6px;
margin-bottom: 10px;
display: flex;
align-items: center;
justify-content: start;
gap: 8px;
background: transparent;
backdrop-filter: blur(10px);
transition: all 0.2s ease;
cursor: pointer;
opacity: 0;
transform: translateY(-10px);
animation: cascadeIn 0.4s ease-out forwards;
}
& > span:nth-child(1) { animation-delay: 0.02s; }
& > span:nth-child(2) { animation-delay: 0.08s; }
& > span:nth-child(3) { animation-delay: 0.12s; }
& > span:nth-child(4) { animation-delay: 0.17s; }
& > span:nth-child(5) { animation-delay: 0.22s; }
& > span:hover {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.3);
transform: translateY(-2px);
}
@keyframes cascadeIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
`}
/>
</div>
)}
</div>
)
}

Expand Down
39 changes: 36 additions & 3 deletions src/stores/walletStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,36 @@ export const useWalletStore = create<WalletState>()(

const walletApi = await window.cardano[walletName].enable()

const stakeAddresses = await walletApi.getRewardAddresses()
await new Promise(resolve => setTimeout(resolve, 100))

let stakeAddresses: string[] = []
let retries = 3

while (retries > 0) {
try {
stakeAddresses = await walletApi.getRewardAddresses()
break
} catch (error: unknown) {
retries--

if (error instanceof Error && (error.message.includes('account changed') || error.message.includes('Account changed'))) {
console.warn(`Account changed error for ${walletName}, retrying... (${retries} attempts left)`)

if (retries > 0) {
await new Promise(resolve => setTimeout(resolve, 500))
continue
} else {
const newWalletApi = await window.cardano[walletName].enable()
await new Promise(resolve => setTimeout(resolve, 200))
stakeAddresses = await newWalletApi.getRewardAddresses()
break
}
} else {
throw error
}
}
}

const stakeAddress = stakeAddresses?.[0] || null

set({
Expand All @@ -82,8 +111,12 @@ export const useWalletStore = create<WalletState>()(
walletApi,
})

const { getBalance, getWalletAddress } = get()
await Promise.all([getBalance(), getWalletAddress()])
try {
const { getBalance, getWalletAddress } = get()
await Promise.all([getBalance(), getWalletAddress()])
} catch (error) {
console.warn('Failed to get balance or address, but wallet is connected:', error)
}

return true
} catch (error) {
Expand Down