Skip to content

Commit

Permalink
react animations, handle gecko 429 error, UI cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
cg-ghost committed Oct 9, 2024
1 parent 3290f79 commit 48fdb57
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 118 deletions.
26 changes: 26 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"clsx": "^2.0.0",
"embla-carousel-react": "8.0.0-rc17",
"events": "^3.3.0",
"framer-motion": "^11.11.4",
"lucide-react": "^0.299.0",
"react": "^18.3.0",
"react-dom": "^18.2.0",
Expand Down
10 changes: 5 additions & 5 deletions src/components/BottomNav.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useNavigate } from 'react-router-dom';
import { Cog6ToothIcon, CurrencyDollarIcon, DocumentTextIcon } from '@heroicons/react/24/outline';
import { Cog6ToothIcon, BanknotesIcon, DocumentTextIcon } from '@heroicons/react/24/outline';

export default function BottomNav() {
const navigate = useNavigate();
Expand All @@ -8,16 +8,16 @@ export default function BottomNav() {
<nav className="fixed bottom-0 left-0 w-full bg-bg-dark border-t border-muted p-2">
<div className="flex justify-around text-primarytext">
<button onClick={() => navigate('/wallet')} className="flex flex-col items-center">
<CurrencyDollarIcon className="h-6 w-6" />
<span className="text-sm">Assets</span>
<BanknotesIcon className="h-6 w-6" />
<span className="text-xs">Assets</span>
</button>
<button onClick={() => navigate('/transactions')} className="flex flex-col items-center">
<DocumentTextIcon className="h-6 w-6" />
<span className="text-sm">Transactions</span>
<span className="text-xs">Transactions</span>
</button>
<button onClick={() => navigate('/settings')} className="flex flex-col items-center">
<Cog6ToothIcon className="h-6 w-6" />
<span className="text-sm">Settings</span>
<span className="text-xs">Settings</span>
</button>
</div>
</nav>
Expand Down
41 changes: 31 additions & 10 deletions src/hooks/useCoingecko.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,38 @@
import { useEffect, useState } from 'react'
import { useEffect, useState } from 'react';

export default function useCoingecko(currency: string) {
const [price, setPrice] = useState(0)
const [price, setPrice] = useState(0);

useEffect(() => {
fetch(
`https://api.coingecko.com/api/v3/simple/price?ids=kaspa&vs_currencies=${currency}`,
).then(async (res) => {
const response = await res.json()
// Try to get the cached price from localStorage
const cachedPrice = localStorage.getItem(`price_${currency}`);
const cachedTimestamp = localStorage.getItem(`timestamp_${currency}`);
const currentTime = Date.now();

setPrice(response.kaspa[currency.toLowerCase()])
})
}, [currency])
if (cachedPrice && cachedTimestamp && currentTime - parseInt(cachedTimestamp) < 60000) {
// Use the cached price if it's still valid
setPrice(parseFloat(cachedPrice));
} else {
// Fetch from the API if the cache is expired or not available
fetch(`https://api.coingecko.com/api/v3/simple/price?ids=kaspa&vs_currencies=${currency}`)
.then(async (res) => {
if (res.ok) {
const response = await res.json();
const newPrice = response.kaspa[currency.toLowerCase()];
setPrice(newPrice);

return price
// Cache the new price and timestamp
localStorage.setItem(`price_${currency}`, newPrice.toString());
localStorage.setItem(`timestamp_${currency}`, currentTime.toString());
} else if (res.status === 429) {
console.error('Rate limit exceeded. Please try again later.');
}
})
.catch((error) => {
console.error('Error fetching price:', error);
});
}
}, [currency]);

return price;
}
87 changes: 47 additions & 40 deletions src/pages/Wallet.tsx
Original file line number Diff line number Diff line change
@@ -1,62 +1,69 @@
import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import useKaspa from '@/hooks/useKaspa';
import useSettings from '@/hooks/useSettings';
import useCoingecko from '@/hooks/useCoingecko';
import { Status } from '@/wallet/kaspa/wallet';
import BottomNav from '@/components/BottomNav';
import { useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import { motion } from 'framer-motion'
import useKaspa from '@/hooks/useKaspa'
import useSettings from '@/hooks/useSettings'
import useCoingecko from '@/hooks/useCoingecko'
import { Status } from '@/wallet/kaspa/wallet'
import BottomNav from '@/components/BottomNav'
import QRCode from 'react-qr-code'

export default function Wallet() {
const { kaspa, request } = useKaspa();
const { settings } = useSettings();
const price = useCoingecko(settings.currency);
const navigate = useNavigate();
const { kaspa, request } = useKaspa()
const { settings } = useSettings()
const price = useCoingecko(settings.currency)
const navigate = useNavigate()
console.log('kaspa addresses in Wallet', kaspa.addresses)

useEffect(() => {
if (!kaspa.connected) {
request('node:connect', [settings.nodes[settings.selectedNode].address]);
request('node:connect', [settings.nodes[settings.selectedNode].address])
}

if (kaspa.status !== Status.Unlocked) {
navigate('/');
navigate('/')
}
}, [kaspa.status]);
}, [kaspa.status])

return (
<main className="p-6 pb-20">
<div className="flex flex-col gap-1 mt-4">
<>
<motion.main
className="pt-10 px-6"
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
transition={{ duration: 0.15, ease: 'easeInOut' }}
>
<div className="flex flex-col items-center">
<p className="text-4xl font-rubik text-primarytext">
<p className="text-3xl font-rubik text-primarytext">
{kaspa.balance.toFixed(4)} KAS
</p>
<p className="text-xl font-rubik text-mutedtext">
{settings.currency} {(kaspa.balance * price).toFixed(2)}
<p className="text-xl font-rubik text-primarytext">
{settings.currency === 'USD'
? '$'
: settings.currency === 'EUR'
? '€'
: settings.currency}{' '}
{(kaspa.balance * price).toFixed(2)}
</p>
</div>


<div className="flex flex-col items-center">
<textarea
readOnly
value={kaspa.addresses[0][kaspa.addresses[0].length - 1]}
className="w-72 border-none resize-none text-mutedtext bg-transparent"
/>
</div>

<div className="flex flex-col items-center">
<textarea
readOnly
value={kaspa.addresses[0][kaspa.addresses[0].length - 1]}
className="w-72 border-none resize-none text-mutedtext bg-transparent"
/>
</div>

<div className="flex flex-col items-center mt-4">
<QRCode
style={{ height: 'auto', width: '35%' }}
value={kaspa.addresses[0][kaspa.addresses[0].length - 1]}
/>
<div className="flex flex-col items-center mt-4">
<QRCode
style={{ height: 'auto', width: '35%' }}
value={kaspa.addresses[0][kaspa.addresses[0].length - 1]}
/>
</div>
</div>



</div>
</motion.main>
<BottomNav />
</main>
);
</>
)
}
120 changes: 57 additions & 63 deletions src/pages/Wallet/Settings.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
import React from 'react'
import { motion } from 'framer-motion'
import BottomNav from '@/components/BottomNav'
import { currencies } from '@/contexts/Settings'
import useSettings from '@/hooks/useSettings'
import useKaspa from '@/hooks/useKaspa'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { Badge } from '@/components/ui/badge'

export default function Settings() {
const { settings, updateSetting } = useSettings()
Expand All @@ -23,66 +16,67 @@ export default function Settings() {
}

return (
<main className="pt-10 px-6">
<div className="flex flex-col">
<h1 className="text-primarytext text-3xl font-rubik text-center mb-4">
Settings
</h1>
<>
<motion.main
className="pt-10 px-6"
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
transition={{ duration: 0.15, ease: 'easeInOut' }}
>
<div className="flex flex-col">
<h1 className="text-primarytext text-3xl font-rubik text-center mb-4">
Settings
</h1>

<div className="flex items-center gap-2 mb-2">
<h2 className="text-primarytext text-base font-lato">Network</h2>
<span
className={`px-2 py-1 ${kaspa.connected ? 'text-success text-base font-lato' : 'text-mutedtext text-base font-lato'}`}
>
{kaspa.connected ? 'Connected' : 'Connecting...'}
</span>
</div>

<div className={'flex flex-col gap-2'}>
<div className={'flex gap-1 mx-1'}>
<Select
defaultValue={settings.selectedNode.toString()}
onValueChange={async (id) => {
await updateSetting('selectedNode', parseInt(id))
await request('node:connect', [
settings.nodes[parseInt(id)].address,
])
}}
<div className="flex items-center gap-2 mb-2">
<h2 className="text-primarytext text-base font-lato">Network</h2>
<span
className={`px-2 py-1 ${kaspa.connected ? 'text-success text-base font-lato' : 'text-mutedtext text-base font-lato'}`}
>
<SelectTrigger className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent className="h-64">
{settings.nodes.map((node, id) => {
return (
<SelectItem key={id} value={id.toString()}>
<p className="text-sm font-lato">{node.address}</p>
</SelectItem>
)
})}
</SelectContent>
</Select>
{kaspa.connected ? 'Connected' : 'Connecting...'}
</span>
</div>
</div>

<h2 className="text-primarytext text-base font-lato mb-2 mt-6">Currency</h2>
<div className="flex gap-1 mx-1 mb-4">
<select
value={settings.currency}
onChange={handleCurrencyChange}
className="w-full p-2 border rounded border-muted bg-bgdarker text-primarytext"
>
{Object.keys(currencies).map((currency) => (
<option key={currency} value={currency}>
{currency} ({currencies[currency as never]})
</option>
))}
</select>
</div>

</div>
<div className={'flex flex-col gap-2'}>
<div className={'flex gap-1 mx-1'}>
<select
value={settings.selectedNode.toString()}
onChange={async (event) => {
const id = parseInt(event.target.value)
await updateSetting('selectedNode', id)
await request('node:connect', [settings.nodes[id].address])
}}
className="w-full p-2 border rounded border-muted bg-bgdarker text-primarytext"
>
{settings.nodes.map((node, id) => (
<option key={id} value={id.toString()}>
{node.address}
</option>
))}
</select>
</div>
</div>

<h2 className="text-primarytext text-base font-lato mb-2 mt-6">
Currency
</h2>
<div className="flex gap-1 mx-1 mb-4">
<select
value={settings.currency}
onChange={handleCurrencyChange}
className="w-full p-2 border rounded border-muted bg-bgdarker text-primarytext"
>
{Object.keys(currencies).map((currency) => (
<option key={currency} value={currency}>
{currency} ({currencies[currency as never]})
</option>
))}
</select>
</div>
</div>
</motion.main>
<BottomNav />
</main>
</>
)
}

0 comments on commit 48fdb57

Please sign in to comment.