Skip to content

Commit c418545

Browse files
committed
feat(ui): enhance components and Firebase integration
- Enhanced home page functionality - Improved preloader screen and user dashboard - Updated common UI components (Card, LoadingSpinner, ProgressBar) - Refined Firebase context and service integration - Updated demo stats initialization
1 parent c53deae commit c418545

9 files changed

Lines changed: 221 additions & 38 deletions

File tree

app/home/page.tsx

Lines changed: 49 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ const DemoSelector = ({
117117
type: 'success' | 'error' | 'info' | 'warning';
118118
}) => void;
119119
account: Account | null;
120-
demos: typeof PREDEFINED_DEMOS;
120+
demos: DemoCard[];
121121
demoStats: DemoStats[];
122122
completeDemo: (demoId: string, score?: number, completionTimeMinutes?: number) => Promise<void>;
123123
hasBadge: (badgeId: string) => Promise<boolean>;
@@ -204,7 +204,7 @@ const DemoSelector = ({
204204
// Removed unused hasEarnedBadge function
205205

206206
const getClapStats = (demoId: string) => {
207-
const demo = demos.find((d: DemoCard) => d.id === demoId);
207+
const demo = demos.find((d) => d.id === demoId);
208208

209209
if (!demo) {
210210
return {
@@ -1044,6 +1044,7 @@ export default function HomePageContent() {
10441044
} | null>(null);
10451045
const [isLoading, setIsLoading] = useState(true);
10461046
const [loadingProgress, setLoadingProgress] = useState(0);
1047+
const [loadingStep, setLoadingStep] = useState(0);
10471048
const [showImmersiveDemo, setShowImmersiveDemo] = useState(false);
10481049
const [showTechTree, setShowTechTree] = useState(false);
10491050

@@ -1067,36 +1068,54 @@ export default function HomePageContent() {
10671068
}
10681069
}, []);
10691070

1070-
// Preloader effect - only on first load
1071+
// Preloader effect - track actual loading progress
10711072
useEffect(() => {
10721073
if (!isLoading) return; // Skip if already loaded
10731074

10741075
const loadingSteps = [
1075-
{ progress: 20, message: 'Initializing Demo Suite...' },
1076-
{ progress: 40, message: 'Loading Smart Contracts...' },
1077-
{ progress: 60, message: 'Preparing Interactive Demos...' },
1078-
{ progress: 80, message: 'Setting up Wallet Integration...' },
1076+
{ progress: 10, message: 'Initializing STELLAR NEXUS...' },
1077+
{ progress: 25, message: 'Connecting to Stellar Network...' },
1078+
{ progress: 40, message: 'Loading Demo Suite...' },
1079+
{ progress: 60, message: 'Fetching Demo Statistics...' },
1080+
{ progress: 80, message: 'Preparing Smart Contracts...' },
1081+
{ progress: 95, message: 'Finalizing Experience...' },
10791082
{ progress: 100, message: 'Ready to Launch!' },
10801083
];
10811084

10821085
let currentStep = 0;
10831086
const interval = setInterval(() => {
10841087
if (currentStep < loadingSteps.length) {
1088+
setLoadingStep(currentStep);
10851089
setLoadingProgress(loadingSteps[currentStep].progress);
10861090
currentStep++;
10871091
} else {
1088-
clearInterval(interval);
1089-
setTimeout(() => {
1090-
setIsLoading(false);
1091-
// Mark that the page has been loaded
1092-
if (typeof window !== 'undefined') {
1093-
localStorage.setItem('homePageLoaded', 'true');
1094-
}
1095-
}, 500);
1092+
// Wait for Firebase initialization and demo stats to complete
1093+
if (isInitialized && demoStats.length >= 0) {
1094+
clearInterval(interval);
1095+
setTimeout(() => {
1096+
setIsLoading(false);
1097+
// Mark that the page has been loaded
1098+
if (typeof window !== 'undefined') {
1099+
localStorage.setItem('homePageLoaded', 'true');
1100+
}
1101+
}, 500);
1102+
}
10961103
}
1097-
}, 800);
1104+
}, 1000);
10981105

10991106
return () => clearInterval(interval);
1107+
}, [isLoading, isInitialized, demoStats]);
1108+
1109+
// Fallback timeout to ensure preloader doesn't get stuck
1110+
useEffect(() => {
1111+
if (isLoading) {
1112+
const timeout = setTimeout(() => {
1113+
console.log('Preloader timeout - forcing completion');
1114+
setIsLoading(false);
1115+
}, 15000); // 15 second timeout
1116+
1117+
return () => clearTimeout(timeout);
1118+
}
11001119
}, [isLoading]);
11011120

11021121
// Listen for wallet sidebar state changes
@@ -1219,7 +1238,20 @@ export default function HomePageContent() {
12191238
} ${!walletSidebarOpen ? 'pb-32' : 'pb-8'}`}
12201239
>
12211240
{/* Preloader Screen */}
1222-
<PreloaderScreen isLoading={isLoading} loadingProgress={loadingProgress} />
1241+
<PreloaderScreen
1242+
isLoading={isLoading}
1243+
loadingProgress={loadingProgress}
1244+
loadingSteps={[
1245+
'Initializing STELLAR NEXUS...',
1246+
'Connecting to Stellar Network...',
1247+
'Loading Demo Suite...',
1248+
'Fetching Demo Statistics...',
1249+
'Preparing Smart Contracts...',
1250+
'Finalizing Experience...',
1251+
'Ready to Launch!',
1252+
]}
1253+
currentStep={loadingStep}
1254+
/>
12231255

12241256
{/* Main Content - Only show when not loading */}
12251257
{!isLoading && (

components/ui/PreloaderScreen.tsx

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ interface PreloaderScreenProps {
1010
loadingSteps?: string[];
1111
logoPath?: string;
1212
logoAlt?: string;
13+
currentStep?: number;
1314
}
1415

1516
export const PreloaderScreen: React.FC<PreloaderScreenProps> = ({
@@ -25,6 +26,7 @@ export const PreloaderScreen: React.FC<PreloaderScreenProps> = ({
2526
],
2627
logoPath = '/images/logo/logoicon.png',
2728
logoAlt = 'STELLAR NEXUS',
29+
currentStep = 0,
2830
}) => {
2931
if (!isLoading) return null;
3032

@@ -73,24 +75,49 @@ export const PreloaderScreen: React.FC<PreloaderScreenProps> = ({
7375
{/* Subtitle */}
7476
<p className='text-xl text-brand-300 mb-8 animate-pulse'>{subtitle}</p>
7577

76-
{/* Loading Bar */}
77-
<div className='w-80 h-3 bg-white/10 rounded-full overflow-hidden mx-auto mb-8'>
78+
{/* Enhanced Loading Bar */}
79+
<div className='w-96 h-4 bg-white/10 rounded-full overflow-hidden mx-auto mb-8 relative'>
7880
<div
79-
className='h-full bg-gradient-to-r from-brand-500 via-brand-600 to-accent-600 rounded-full transition-all duration-500 ease-out'
81+
className='h-full bg-gradient-to-r from-brand-500 via-brand-600 to-accent-600 rounded-full transition-all duration-700 ease-out relative'
82+
style={{ width: `${loadingProgress}%` }}
83+
>
84+
{/* Shimmer effect */}
85+
<div className='absolute inset-0 bg-gradient-to-r from-transparent via-white/30 to-transparent animate-pulse'></div>
86+
</div>
87+
{/* Progress bar glow */}
88+
<div
89+
className='absolute top-0 h-full bg-gradient-to-r from-brand-400/50 to-accent-400/50 rounded-full blur-sm transition-all duration-700 ease-out'
8090
style={{ width: `${loadingProgress}%` }}
8191
></div>
8292
</div>
8393

84-
{/* Loading Steps */}
85-
<div className='space-y-2 text-white/80'>
94+
{/* Current Loading Step */}
95+
<div className='mb-6'>
96+
<p className='text-lg text-brand-300 font-medium animate-pulse'>
97+
{loadingSteps[currentStep] || loadingSteps[loadingSteps.length - 1]}
98+
</p>
99+
</div>
100+
101+
{/* Loading Steps Progress */}
102+
<div className='space-y-1 text-white/60 max-w-md mx-auto'>
86103
{loadingSteps.map((step, index) => (
87-
<p
104+
<div
88105
key={index}
89-
className='animate-fadeInUp'
90-
style={{ animationDelay: `${0.5 + index * 0.5}s` }}
106+
className={`flex items-center space-x-2 transition-all duration-300 ${
107+
index <= currentStep ? 'text-brand-300' : 'text-white/40'
108+
}`}
91109
>
92-
{step}
93-
</p>
110+
<div
111+
className={`w-2 h-2 rounded-full transition-all duration-300 ${
112+
index < currentStep
113+
? 'bg-brand-400'
114+
: index === currentStep
115+
? 'bg-brand-400 animate-pulse'
116+
: 'bg-white/20'
117+
}`}
118+
></div>
119+
<span className='text-sm'>{step}</span>
120+
</div>
94121
))}
95122
</div>
96123

components/ui/UserDashboard.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ interface UserDashboardProps {
1212
}
1313

1414
export const UserDashboard = ({ isOpen, onClose }: UserDashboardProps) => {
15-
const { account, demos } = useFirebase();
15+
const { account, demos, demoStats } = useFirebase();
1616
const { walletData, isConnected } = useGlobalWallet();
1717
// Leaderboard functionality removed
1818
const [showBadges, setShowBadges] = useState(false);
@@ -29,6 +29,15 @@ export const UserDashboard = ({ isOpen, onClose }: UserDashboardProps) => {
2929
return xp.toString();
3030
};
3131

32+
// Helper function to get demo stats
33+
const getDemoStats = (demoId: string) => {
34+
const stats = demoStats.find(stat => stat.demoId === demoId);
35+
return {
36+
totalClaps: stats?.totalClaps || 0,
37+
totalCompletions: stats?.totalCompletions || 0,
38+
};
39+
};
40+
3241
const formatTime = (minutes: number) => {
3342
if (minutes < 60) {
3443
return `${minutes}m`;
@@ -246,7 +255,11 @@ export const UserDashboard = ({ isOpen, onClose }: UserDashboardProps) => {
246255
</div>
247256
<div className='flex justify-between text-sm text-white/70'>
248257
<span>Total Claps</span>
249-
<span>0</span>
258+
<span>{getDemoStats(demo.id).totalClaps}</span>
259+
</div>
260+
<div className='flex justify-between text-sm text-white/70'>
261+
<span>Total Completions</span>
262+
<span>{getDemoStats(demo.id).totalCompletions}</span>
250263
</div>
251264
</div>
252265
</div>

components/ui/common/Card.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,4 @@ export const Card: React.FC<CardProps> = ({
6161
</div>
6262
);
6363
};
64+

components/ui/common/LoadingSpinner.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,4 @@ export const LoadingSpinner: React.FC<LoadingSpinnerProps> = ({
4545
</div>
4646
);
4747
};
48+

components/ui/common/ProgressBar.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,4 @@ export const ProgressBar: React.FC<ProgressBarProps> = ({
5656
</div>
5757
);
5858
};
59+

contexts/data/FirebaseContext.tsx

Lines changed: 96 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,18 @@ import {
1414
PREDEFINED_BADGES,
1515
getBadgeById
1616
} from '../../lib/firebase/firebase-types';
17+
18+
// DemoCard interface
19+
interface DemoCard {
20+
id: string;
21+
title: string;
22+
subtitle: string;
23+
description: string;
24+
icon: string;
25+
color: string;
26+
isReady: boolean;
27+
multiStakeholderRequired: boolean;
28+
}
1729
import { useBadgeAnimation } from '../ui/BadgeAnimationContext';
1830
import { useToast } from '../ui/ToastContext';
1931
import { useTransactionHistory } from './TransactionContext';
@@ -23,7 +35,7 @@ interface FirebaseContextType {
2335
account: Account | null;
2436

2537
// Static data
26-
demos: typeof PREDEFINED_DEMOS;
38+
demos: DemoCard[];
2739
badges: typeof PREDEFINED_BADGES;
2840
demoStats: DemoStats[];
2941

@@ -71,7 +83,17 @@ export const FirebaseProvider: React.FC<FirebaseProviderProps> = ({ children })
7183
// Initialize account data when wallet connects
7284
useEffect(() => {
7385
const initializeFirebase = async () => {
74-
if (!walletData?.publicKey) return;
86+
// Always load demo stats (public data)
87+
try {
88+
await loadDemoStats();
89+
} catch (error) {
90+
console.error('Error loading demo stats:', error);
91+
}
92+
93+
if (!walletData?.publicKey) {
94+
setIsInitialized(true);
95+
return;
96+
}
7597

7698
setIsLoading(true);
7799
try {
@@ -110,8 +132,8 @@ export const FirebaseProvider: React.FC<FirebaseProviderProps> = ({ children })
110132
});
111133
}
112134

113-
// Load account data and demo stats
114-
await Promise.all([loadAccountData(), loadDemoStats()]);
135+
// Load account data (demo stats already loaded above)
136+
await loadAccountData();
115137
setIsInitialized(true);
116138
} catch (error) {
117139
addToast({
@@ -147,7 +169,31 @@ export const FirebaseProvider: React.FC<FirebaseProviderProps> = ({ children })
147169
// Load demo stats
148170
const loadDemoStats = async () => {
149171
try {
150-
const stats = await demoStatsService.getAllDemoStats();
172+
let stats = await demoStatsService.getAllDemoStats();
173+
174+
// If no stats exist, initialize them for all demos
175+
if (stats.length === 0) {
176+
console.log('No demo stats found, initializing...');
177+
const demos = [
178+
{ id: 'hello-milestone', name: 'Baby Steps to Riches' },
179+
{ id: 'dispute-resolution', name: 'Drama Queen Escrow' },
180+
{ id: 'micro-marketplace', name: 'Gig Economy Madness' },
181+
{ id: 'nexus-master', name: 'Nexus Master Achievement' },
182+
];
183+
184+
for (const demo of demos) {
185+
try {
186+
await demoStatsService.initializeDemoStats(demo.id, demo.name);
187+
console.log(`✅ Initialized stats for ${demo.name} (${demo.id})`);
188+
} catch (error) {
189+
console.error(`❌ Failed to initialize stats for ${demo.name} (${demo.id}):`, error);
190+
}
191+
}
192+
193+
// Reload stats after initialization
194+
stats = await demoStatsService.getAllDemoStats();
195+
}
196+
151197
setDemoStats(stats);
152198
} catch (error) {
153199
console.error('Error loading demo stats:', error);
@@ -261,7 +307,6 @@ export const FirebaseProvider: React.FC<FirebaseProviderProps> = ({ children })
261307

262308
// Update demo stats for global tracking
263309
try {
264-
console.log('FirebaseContext: Updating demo stats for:', demoId);
265310
await demoStatsService.incrementCompletion(demoId, completionTimeMinutes, finalScore);
266311
} catch (statsError) {
267312
console.error('FirebaseContext: Failed to update demo stats:', statsError);
@@ -430,9 +475,53 @@ export const FirebaseProvider: React.FC<FirebaseProviderProps> = ({ children })
430475
}
431476
};
432477

478+
// Convert PREDEFINED_DEMOS to DemoCard format
479+
const demos: DemoCard[] = [
480+
{
481+
id: 'hello-milestone',
482+
title: '1. Baby Steps to Riches',
483+
subtitle: 'Basic Escrow Flow Demo',
484+
description: PREDEFINED_DEMOS[0].description,
485+
icon: '🎮',
486+
color: 'from-brand-500 to-brand-400',
487+
isReady: true,
488+
multiStakeholderRequired: false,
489+
},
490+
{
491+
id: 'dispute-resolution',
492+
title: '2. Drama Queen Escrow',
493+
subtitle: 'Dispute Resolution & Arbitration',
494+
description: PREDEFINED_DEMOS[1].description,
495+
icon: '🎮',
496+
color: 'from-warning-500 to-warning-400',
497+
isReady: true,
498+
multiStakeholderRequired: false,
499+
},
500+
{
501+
id: 'micro-marketplace',
502+
title: '3. Gig Economy Madness',
503+
subtitle: 'Micro-Task Marketplace',
504+
description: PREDEFINED_DEMOS[2].description,
505+
icon: '🎮',
506+
color: 'from-accent-500 to-accent-400',
507+
isReady: true,
508+
multiStakeholderRequired: false,
509+
},
510+
{
511+
id: 'nexus-master',
512+
title: 'Nexus Master Achievement',
513+
subtitle: 'Complete All Main Badges',
514+
description: 'The ultimate achievement! Complete all three main demos to unlock the legendary Nexus Master badge and claim your place among the elite.',
515+
icon: '/images/demos/economy.png',
516+
color: 'from-gray-500 to-gray-400',
517+
isReady: false,
518+
multiStakeholderRequired: false,
519+
},
520+
];
521+
433522
const value: FirebaseContextType = {
434523
account,
435-
demos: PREDEFINED_DEMOS,
524+
demos,
436525
badges: PREDEFINED_BADGES,
437526
demoStats,
438527
isLoading,

0 commit comments

Comments
 (0)