Skip to content
Merged
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
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
198 changes: 168 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,137 @@ 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={['eternl', 'yoroi', 'gerowallet', 'begin', 'nufi', 'lace', 'vespr']}
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={['eternl', 'yoroi', 'begin', 'nufi', 'lace', 'vespr']}
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:nth-child(6) { animation-delay: 0.27s; }
& > 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