Skip to content

Commit b526b00

Browse files
authored
Merge pull request #101 from deeseeker/refactor/data-fetching-technique
refactor data fetching technique
2 parents ad9fd66 + d310836 commit b526b00

File tree

15 files changed

+384
-437
lines changed

15 files changed

+384
-437
lines changed

frontend/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
},
1212
"dependencies": {
1313
"@stellar/stellar-sdk": "^12.0.1",
14+
"@tanstack/react-query": "^5.90.21",
15+
"@tanstack/react-query-devtools": "^5.91.3",
1416
"clsx": "^2.0.0",
1517
"framer-motion": "^10.16.4",
1618
"lucide-react": "^0.263.1",

frontend/pnpm-lock.yaml

Lines changed: 76 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/src/components/Dashboard.tsx

Lines changed: 54 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState, useEffect } from 'react'
1+
import React, { useState } from 'react'
22
import { motion } from 'framer-motion'
33
import { PieChart, Pie, Cell, LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'
44
import { TrendingUp, AlertCircle, RefreshCw, ArrowLeft, ExternalLink } from 'lucide-react'
@@ -10,6 +10,12 @@ import PerformanceChart from './PerformanceChart'
1010
import NotificationPreferences from './NotificationPreferences'
1111
import { StellarWallet } from '../utils/stellar'
1212
import PriceTracker from './PriceTracker'
13+
import { API_CONFIG } from '../config/api'
14+
15+
// TanStack Query Hooks
16+
import { useUserPortfolios, usePortfolioDetails } from '../hooks/queries/usePortfolioQuery'
17+
import { usePrices } from '../hooks/queries/usePricesQuery'
18+
import { useExecuteRebalanceMutation } from '../hooks/mutations/usePortfolioMutations'
1319
import { api, ENDPOINTS } from '../config/api'
1420
import { logout as authLogout } from '../services/authService'
1521
import { browserPriceService } from '../services/browserPriceService'
@@ -24,142 +30,73 @@ interface DashboardProps {
2430
}
2531

2632
const Dashboard: React.FC<DashboardProps> = ({ onNavigate, publicKey }) => {
27-
const [portfolioData, setPortfolioData] = useState<any>(null)
28-
const [prices, setPrices] = useState<any>({})
29-
const [loading, setLoading] = useState(true)
30-
const [rebalancing, setRebalancing] = useState(false)
31-
const [priceSource, setPriceSource] = useState<string>('loading...')
3233
const [activeTab, setActiveTab] = useState<'overview' | 'analytics' | 'notifications' | 'test-notifications'>('overview')
3334
const { isDark } = useTheme()
3435

35-
useEffect(() => {
36-
if (publicKey) {
37-
fetchPortfolioData()
38-
fetchPrices()
39-
const interval = setInterval(() => {
40-
fetchPrices()
41-
}, 60000) // Reduce frequency to avoid rate limits
42-
return () => clearInterval(interval)
43-
} else {
44-
loadDemoData()
45-
}
46-
}, [publicKey])
47-
48-
const fetchPortfolioData = async () => {
49-
try {
50-
console.log('Fetching portfolio data for:', publicKey)
51-
if (!publicKey) {
52-
console.log('Portfolio list fetch failed, loading demo data')
53-
loadDemoData()
54-
return
55-
}
56-
57-
const userPortfolios = await api.get<{ portfolios: any[] }>(ENDPOINTS.USER_PORTFOLIOS(publicKey))
58-
const portfolios = userPortfolios.portfolios ?? []
59-
console.log('Found portfolios:', portfolios)
60-
61-
if (portfolios.length > 0) {
62-
// Use the most recent portfolio (last in array)
63-
const latestPortfolio = portfolios[portfolios.length - 1]
64-
console.log('Using portfolio:', latestPortfolio)
65-
66-
try {
67-
const detail = await api.get<{ portfolio: any }>(ENDPOINTS.PORTFOLIO_DETAIL(latestPortfolio.id))
68-
console.log('Portfolio data:', detail)
69-
setPortfolioData(detail.portfolio || latestPortfolio)
70-
} catch {
71-
console.log('Portfolio details fetch failed, using list data')
72-
setPortfolioData(latestPortfolio)
73-
}
74-
} else {
75-
console.log('No portfolios found, loading demo data')
76-
loadDemoData()
77-
}
78-
} catch (error) {
79-
console.error('Failed to fetch portfolio:', error)
80-
loadDemoData()
81-
} finally {
82-
setLoading(false)
83-
}
36+
// Query for user portfolios
37+
const { data: portfolios, isLoading: portfoliosLoading } = useUserPortfolios(publicKey)
38+
39+
// Determine the latest portfolio
40+
const latestPortfolioId = portfolios && portfolios.length > 0
41+
? portfolios[portfolios.length - 1].id
42+
: null
43+
44+
// Query for portfolio details
45+
const { data: portfolioDetails, isLoading: detailsLoading } = usePortfolioDetails(latestPortfolioId)
46+
47+
// Query for prices
48+
const { data: priceData, isLoading: pricesLoading } = usePrices()
49+
50+
// Mutation for rebalancing
51+
const executeRebalanceMutation = useExecuteRebalanceMutation(latestPortfolioId)
52+
53+
// Demo data fallback
54+
const demoData = {
55+
id: 'demo',
56+
totalValue: 10000,
57+
dayChange: 0.85,
58+
needsRebalance: false,
59+
lastRebalance: '2 hours ago',
60+
allocations: [
61+
{ asset: 'XLM', target: 40, current: 40.2, amount: 4020 },
62+
{ asset: 'USDC', target: 60, current: 59.8, amount: 5980 }
63+
]
8464
}
8565

86-
const fetchPrices = async () => {
87-
try {
88-
console.log('Fetching prices using browser service...')
89-
// Use browser price service directly
90-
const priceData = await browserPriceService.getCurrentPrices()
91-
console.log('Browser prices fetched:', priceData)
92-
93-
// Transform to expected format if needed
94-
const transformedPrices: any = {}
95-
Object.entries(priceData).forEach(([asset, data]) => {
96-
transformedPrices[asset] = {
97-
price: (data as any).price,
98-
change: (data as any).change || 0
99-
}
100-
})
101-
102-
setPrices(transformedPrices)
103-
setPriceSource('CoinGecko Browser API')
104-
} catch (error) {
105-
console.error('Failed to fetch prices from browser service:', error)
106-
setPriceSource('Fallback Data')
107-
108-
// Fallback to demo prices
109-
setPrices({
110-
XLM: { price: 0.354, change: -1.86 },
111-
USDC: { price: 1.0, change: -0.01 },
112-
BTC: { price: 110000, change: -1.19 },
113-
ETH: { price: 4200, change: -1.50 }
114-
})
115-
}
66+
const demoPrices = {
67+
XLM: { price: 0.354, change: -1.86 },
68+
USDC: { price: 1.0, change: -0.01 },
69+
BTC: { price: 110000, change: -1.19 },
70+
ETH: { price: 4200, change: -1.50 }
11671
}
11772

118-
const loadDemoData = () => {
119-
console.log('Loading demo data')
120-
setPortfolioData({
121-
id: 'demo',
122-
totalValue: 10000,
123-
dayChange: 0.85,
124-
needsRebalance: false,
125-
lastRebalance: '2 hours ago',
126-
allocations: [
127-
{ asset: 'XLM', target: 40, current: 40.2, amount: 4020 },
128-
{ asset: 'USDC', target: 60, current: 59.8, amount: 5980 }
129-
]
130-
})
131-
132-
// Load demo prices and try to get real prices
133-
fetchPrices()
134-
setLoading(false)
135-
}
73+
// Determine finalized data and loading state
74+
const portfolioData = publicKey ? (portfolioDetails || (portfolios && portfolios.length > 0 ? portfolios[portfolios.length - 1] : null)) : demoData
75+
const prices = priceData || demoPrices
76+
const loading = publicKey ? (portfoliosLoading || (latestPortfolioId ? detailsLoading : false) || (API_CONFIG.USE_BROWSER_PRICES ? false : pricesLoading)) : false
77+
const priceSource = priceData ? 'CoinGecko Browser API' : (publicKey ? 'Fallback Data' : 'Demo Data')
13678

13779
const executeRebalance = async () => {
13880
if (!portfolioData?.id || portfolioData.id === 'demo') {
13981
alert('Rebalancing not available in demo mode. Please create a real portfolio.')
14082
return
14183
}
14284

143-
setRebalancing(true)
144-
14585
try {
146-
const result = await api.post<{ result: any }>(ENDPOINTS.PORTFOLIO_REBALANCE(portfolioData.id))
86+
const result = await executeRebalanceMutation.mutateAsync()
14787
alert(`Rebalance executed successfully! Gas used: ${result.result?.gasUsed || 'N/A'}`)
148-
fetchPortfolioData()
149-
} catch (error) {
88+
} catch (error: any) {
15089
console.error('Rebalance failed:', error)
151-
const msg = error instanceof Error ? error.message : 'Rebalance failed. Please try again.'
152-
const isSlippage = msg.toLowerCase().includes('slippage') || msg.toLowerCase().includes('tolerance')
90+
const msg = error.message ?? 'Rebalance failed. Please try again.'
91+
const isSlippage = typeof msg === 'string' && (msg.toLowerCase().includes('slippage') || msg.toLowerCase().includes('tolerance'))
15392
alert(isSlippage ? `Slippage too high: ${msg}` : msg)
154-
} finally {
155-
setRebalancing(false)
15693
}
15794
}
15895

15996
const refreshData = async () => {
160-
setLoading(true)
161-
await Promise.all([fetchPortfolioData(), fetchPrices()])
162-
setLoading(false)
97+
// TanStack Query handles background refresh automatically, but we can force it
98+
// by invalidating queries if needed. For now, simple manual refresh is fine.
99+
window.location.reload()
163100
}
164101

165102
const disconnectWallet = async () => {
@@ -523,10 +460,10 @@ const Dashboard: React.FC<DashboardProps> = ({ onNavigate, publicKey }) => {
523460
)}
524461
<button
525462
onClick={executeRebalance}
526-
disabled={rebalancing || !publicKey || portfolioData?.id === 'demo'}
463+
disabled={executeRebalanceMutation.isPending || !publicKey || portfolioData?.id === 'demo'}
527464
className="w-full bg-orange-500 hover:bg-orange-600 disabled:bg-gray-300 dark:disabled:bg-gray-600 text-white py-2 px-4 rounded-lg font-medium transition-colors flex items-center justify-center"
528465
>
529-
{rebalancing ? (
466+
{executeRebalanceMutation.isPending ? (
530467
<>
531468
<RefreshCw className="w-4 h-4 mr-2 animate-spin" />
532469
Rebalancing...

0 commit comments

Comments
 (0)