1- import React , { useState , useEffect } from 'react'
1+ import React , { useState } from 'react'
22import { motion } from 'framer-motion'
33import { PieChart , Pie , Cell , LineChart , Line , XAxis , YAxis , CartesianGrid , Tooltip , ResponsiveContainer } from 'recharts'
44import { TrendingUp , AlertCircle , RefreshCw , ArrowLeft , ExternalLink } from 'lucide-react'
@@ -10,6 +10,12 @@ import PerformanceChart from './PerformanceChart'
1010import NotificationPreferences from './NotificationPreferences'
1111import { StellarWallet } from '../utils/stellar'
1212import 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'
1319import { api , ENDPOINTS } from '../config/api'
1420import { logout as authLogout } from '../services/authService'
1521import { browserPriceService } from '../services/browserPriceService'
@@ -24,142 +30,73 @@ interface DashboardProps {
2430}
2531
2632const 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