Skip to content

Commit f0786bf

Browse files
committed
feat: implement UX improvements for clap restrictions, loading states and navigation
- fix(header): resolve controller icon dropdown closing issue by preventing event bubbling - feat(clap): implement one clap per user per demo restriction with Firebase persistence - feat(navigation): add active tab styling to Stellar Nexus Experience in user dropdown - feat(ui): add comprehensive preloader for demo cards during account data loading - improve(ui): clarify transaction history as session-based with informational banners - enhance(firebase): add clap tracking services and better loading state coordination
1 parent 879a2d6 commit f0786bf

11 files changed

Lines changed: 346 additions & 130 deletions

File tree

app/home/page.tsx

Lines changed: 174 additions & 107 deletions
Large diffs are not rendered by default.

components/demos/MicroTaskMarketplaceDemo.tsx

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { useToast } from '@/contexts/ui/ToastContext';
66
import { useTransactionHistory } from '@/contexts/data/TransactionContext';
77
import { useFirebase } from '@/contexts/data/FirebaseContext';
88
import ConfettiAnimation from '@/components/ui/animations/ConfettiAnimation';
9+
import { useImmersiveProgress } from '@/components/ui/modals/ImmersiveDemoModal';
910
import Image from 'next/image';
1011
import {
1112
useFundEscrow,
@@ -49,6 +50,12 @@ export const MicroTaskMarketplaceDemo = ({
4950
const { addTransaction, updateTransaction } = useTransactionHistory();
5051
// Demo completion tracking is now handled by FirebaseContext
5152
const { completeDemo } = useFirebase();
53+
const { updateProgress } = useImmersiveProgress();
54+
55+
// Smart step tracking for micro-task marketplace
56+
const [tasksPosted, setTasksPosted] = useState(0);
57+
const [tasksAccepted, setTasksAccepted] = useState(0);
58+
const [tasksCompleted, setTasksCompleted] = useState(0);
5259
const [activeTab, setActiveTab] = useState<'browse' | 'my-tasks' | 'post-task'>('browse');
5360
const [selectedCategory, setSelectedCategory] = useState<string>('all');
5461
const [newTask, setNewTask] = useState({
@@ -269,6 +276,14 @@ export const MicroTaskMarketplaceDemo = ({
269276
asset: 'USDC',
270277
});
271278

279+
// Track progress milestone - task posting
280+
const newTaskCount = tasksPosted + 1;
281+
setTasksPosted(newTaskCount);
282+
283+
if (newTaskCount === 1) {
284+
updateProgress('task_posted');
285+
}
286+
272287
addToast({
273288
type: 'success',
274289
title: '✅ Task Posted!',
@@ -371,6 +386,18 @@ export const MicroTaskMarketplaceDemo = ({
371386
);
372387
setTasks(updatedTasks);
373388

389+
// Track progress milestone - accept task
390+
const newAcceptCount = tasksAccepted + 1;
391+
setTasksAccepted(newAcceptCount);
392+
393+
if (newAcceptCount === 1) {
394+
updateProgress('task_accepted');
395+
} else if (newAcceptCount === 2) {
396+
updateProgress('task_accepted_2');
397+
} else if (newAcceptCount === 3) {
398+
updateProgress('task_accepted_3');
399+
}
400+
374401
addToast({
375402
type: 'success',
376403
title: '✅ Task Accepted!',
@@ -473,6 +500,16 @@ export const MicroTaskMarketplaceDemo = ({
473500
asset: 'USDC',
474501
});
475502

503+
// Track progress milestone - complete work
504+
const newCompleteCount = tasksCompleted + 1;
505+
setTasksCompleted(newCompleteCount);
506+
507+
if (newCompleteCount === 1) {
508+
updateProgress('work_completed');
509+
} else if (newCompleteCount === 2) {
510+
updateProgress('work_completed_2');
511+
}
512+
476513
addToast({
477514
type: 'success',
478515
title: '✅ Deliverable Submitted!',
@@ -529,6 +566,8 @@ export const MicroTaskMarketplaceDemo = ({
529566
asset: 'USDC',
530567
});
531568

569+
// Note: Approval tracking removed since it's not required for demo completion
570+
532571
addToast({
533572
type: 'success',
534573
title: '✅ Task Approved!',
@@ -590,6 +629,8 @@ export const MicroTaskMarketplaceDemo = ({
590629
asset: 'USDC',
591630
});
592631

632+
// Note: Payment release tracking removed since it's not required for demo completion
633+
593634
addToast({
594635
type: 'success',
595636
title: '💰 Funds Released!',
@@ -722,8 +763,6 @@ export const MicroTaskMarketplaceDemo = ({
722763
setPostedTasks(new Set());
723764
setTaskDeliverables({});
724765
setDemoCompleted(false);
725-
setContractId('');
726-
setEscrowData(null);
727766

728767
addToast({
729768
type: 'warning',
@@ -977,7 +1016,7 @@ export const MicroTaskMarketplaceDemo = ({
9771016
task.status.replace('-', ' ').slice(1)}
9781017
</span>
9791018
<span className='text-white/50'>
980-
{task.client === walletData?.publicKey ? '👔 Client' : '👷 Worker'}
1019+
{task.client === walletData?.publicKey ? '👔 Client' : task.worker === walletData?.publicKey ? '👷 Worker' : '👤 Other'}
9811020
</span>
9821021
</div>
9831022
</div>

components/ui/RewardsSidebar.tsx

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,10 @@ export const RewardsSidebar: React.FC<RewardsDropdownProps> = ({ isOpen, onClose
375375
<div className='space-y-3'>
376376
<div
377377
className='flex items-center space-x-2 mb-3 cursor-pointer hover:bg-gray-800/30 rounded-lg p-2 transition-colors'
378-
onClick={() => setIsMainAchievementsCollapsed(!isMainAchievementsCollapsed)}
378+
onClick={(e) => {
379+
e.stopPropagation();
380+
setIsMainAchievementsCollapsed(!isMainAchievementsCollapsed);
381+
}}
379382
>
380383
<div className='w-2 h-2 bg-gradient-to-r from-blue-400 to-purple-500 rounded-full'></div>
381384
<h3 className='text-lg font-semibold text-white'>Demo Badges</h3>
@@ -439,24 +442,38 @@ export const RewardsSidebar: React.FC<RewardsDropdownProps> = ({ isOpen, onClose
439442
<div>
440443
<div className='text-lg font-semibold text-white'>Transaction History</div>
441444
<div className='text-sm text-gray-400'>
442-
{transactions.length} total transactions
445+
{transactions.length} session transactions
443446
</div>
444447
</div>
445448
<button
446-
onClick={refreshTransactions}
449+
onClick={(e) => {
450+
e.stopPropagation();
451+
refreshTransactions();
452+
}}
447453
disabled={isLoading}
448454
className='px-3 py-1 bg-blue-500/20 text-blue-400 rounded-lg text-sm hover:bg-blue-500/30 transition-colors disabled:opacity-50'
449455
>
450456
{isLoading ? 'Refreshing...' : 'Refresh'}
451457
</button>
452458
</div>
453459

460+
{/* Session Info Banner */}
461+
<div className='bg-blue-500/10 border border-blue-500/20 rounded-lg p-3'>
462+
<div className='flex items-center space-x-2 mb-1'>
463+
<span className='text-blue-400 text-sm'>ℹ️</span>
464+
<span className='text-blue-300 text-sm font-medium'>Live Session Data</span>
465+
</div>
466+
<p className='text-blue-200/80 text-xs'>
467+
Transactions shown here are from your current browsing session and reset when you refresh the page.
468+
</p>
469+
</div>
470+
454471
<div className='max-h-96 overflow-y-auto'>
455472
<TransactionList
456473
transactions={transactions}
457474
isLoading={isLoading}
458475
showFilters={true}
459-
emptyMessage="No transactions found. Complete some demos to see your transaction history!"
476+
emptyMessage="No transactions in this session yet. Your demo interactions will appear here."
460477
/>
461478
</div>
462479
</div>
@@ -484,6 +501,7 @@ export const RewardsSidebar: React.FC<RewardsDropdownProps> = ({ isOpen, onClose
484501
{isOpen && (
485502
<div
486503
ref={dropdownRef}
504+
onClick={(e) => e.stopPropagation()}
487505
className='absolute right-0 mt-2 w-80 bg-black/80 backdrop-blur-2xl border border-white/30 rounded-2xl shadow-2xl z-50 overflow-hidden max-h-[80vh]'
488506
>
489507
{/* Enhanced background blur overlay */}
@@ -493,7 +511,13 @@ export const RewardsSidebar: React.FC<RewardsDropdownProps> = ({ isOpen, onClose
493511
{/* Header */}
494512
<div className='relative z-10 flex items-center justify-between p-4 border-b border-white/10'>
495513
<h2 className='text-xl font-bold text-white'>Nexus Account</h2>
496-
<button onClick={onClose} className='text-gray-400 hover:text-white transition-colors'>
514+
<button
515+
onClick={(e) => {
516+
e.stopPropagation();
517+
onClose();
518+
}}
519+
className='text-gray-400 hover:text-white transition-colors'
520+
>
497521
498522
</button>
499523
</div>
@@ -503,7 +527,10 @@ export const RewardsSidebar: React.FC<RewardsDropdownProps> = ({ isOpen, onClose
503527
{tabs.map(tab => (
504528
<button
505529
key={tab.id}
506-
onClick={() => setActiveTab(tab.id as any)}
530+
onClick={(e) => {
531+
e.stopPropagation();
532+
setActiveTab(tab.id as any);
533+
}}
507534
className={`flex-1 px-3 py-2 text-sm font-medium transition-colors ${
508535
activeTab === tab.id
509536
? 'text-white bg-white/10 border-b-2 border-blue-500'

components/ui/navigation/UserDropdown.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use client';
22

33
import { useState, useRef, useEffect, useMemo } from 'react';
4+
import { usePathname } from 'next/navigation';
45
import { useGlobalWallet } from '@/contexts/wallet/WalletContext';
56
import { useAuth } from '@/contexts/auth/AuthContext';
67
import { useFirebase } from '@/contexts/data/FirebaseContext';
@@ -17,6 +18,7 @@ export const UserDropdown = () => {
1718
const { isAuthenticated, user, getUserStats, updateUser } = useAuth();
1819
const { account } = useFirebase();
1920
const { addToast } = useToast();
21+
const pathname = usePathname();
2022
const [isOpen, setIsOpen] = useState(false);
2123
const [isGenerating, setIsGenerating] = useState(false);
2224
const [isEditingName, setIsEditingName] = useState(false);
@@ -364,7 +366,11 @@ export const UserDropdown = () => {
364366
<>
365367
<a
366368
href='/'
367-
className='w-full flex items-center space-x-3 px-3 py-2 text-white/80 hover:text-white hover:bg-white/10 rounded-lg transition-colors duration-200 text-sm'
369+
className={`w-full flex items-center space-x-3 px-3 py-2 rounded-lg transition-colors duration-200 text-sm ${
370+
pathname === '/'
371+
? 'text-white bg-blue-500/20 border border-blue-500/30'
372+
: 'text-white/80 hover:text-white hover:bg-white/10'
373+
}`}
368374
>
369375
<span className='text-lg'>
370376
<Image
@@ -393,9 +399,11 @@ export const UserDropdown = () => {
393399
}
394400
}}
395401
className={`w-full flex items-center space-x-3 px-3 py-2 rounded-lg transition-colors duration-200 text-sm mb-2 ${
396-
miniGamesUnlocked
397-
? 'text-white/80 hover:text-white hover:bg-white/10'
398-
: 'text-white/40 cursor-not-allowed'
402+
!miniGamesUnlocked
403+
? 'text-white/40 cursor-not-allowed'
404+
: pathname === '/mini-games'
405+
? 'text-white bg-purple-500/20 border border-purple-500/30'
406+
: 'text-white/80 hover:text-white hover:bg-white/10'
399407
}`}
400408
>
401409
<span className='text-lg'>

components/ui/wallet/WalletSidebar.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -674,7 +674,7 @@ export const WalletSidebar = ({ isOpen, onToggle, showBanner = false }: WalletSi
674674
transactions={recentTransactions}
675675
compact={true}
676676
limit={5}
677-
emptyMessage="No recent transactions"
677+
emptyMessage="No recent session transactions"
678678
/>
679679
</div>
680680
)}
@@ -685,15 +685,15 @@ export const WalletSidebar = ({ isOpen, onToggle, showBanner = false }: WalletSi
685685
onClick={() => setShowTransactionHistory(true)}
686686
className='text-xs text-white/60 hover:text-white/80 transition-colors underline'
687687
>
688-
View {transactions.length} transaction{transactions.length !== 1 ? 's' : ''}
688+
View {transactions.length} session transaction{transactions.length !== 1 ? 's' : ''}
689689
</button>
690690
</div>
691691
)}
692692

693693
{transactions.length === 0 && (
694694
<div className='text-center py-4'>
695695
<div className='text-2xl mb-2'>📝</div>
696-
<p className='text-xs text-gray-400'>No transactions yet</p>
696+
<p className='text-xs text-gray-400'>No transactions in this session</p>
697697
</div>
698698
)}
699699
</div>
@@ -718,7 +718,7 @@ export const WalletSidebar = ({ isOpen, onToggle, showBanner = false }: WalletSi
718718
</div>
719719

720720
{/* Floating Wallet Control Buttons - Always show, different styling based on connection */}
721-
<div className='fixed top-20 right-4 z-30 flex flex-col space-y-3'>
721+
<div className='fixed top-36 right-4 z-30 flex flex-col space-y-3'>
722722
{/* Open Wallet Button */}
723723
{!isOpen && (
724724
<>

contexts/data/FirebaseContext.tsx

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ interface FirebaseContextType {
3636
completeDemo: (demoId: string, score?: number, completionTimeMinutes?: number) => Promise<void>;
3737
hasBadge: (badgeId: string) => Promise<boolean>;
3838
hasCompletedDemo: (demoId: string) => Promise<boolean>;
39+
hasClappedDemo: (demoId: string) => Promise<boolean>;
3940
refreshAccountData: () => Promise<void>;
4041
clapDemo: (demoId: string) => Promise<void>;
4142
}
@@ -374,16 +375,36 @@ export const FirebaseProvider: React.FC<FirebaseProviderProps> = ({ children })
374375
}
375376
};
376377

378+
// Check if account has clapped for demo
379+
const hasClappedDemo = async (demoId: string): Promise<boolean> => {
380+
if (!walletData?.publicKey) return false;
381+
382+
try {
383+
return await accountService.hasClappedDemo(walletData.publicKey, demoId);
384+
} catch (error) {
385+
return false;
386+
}
387+
};
388+
377389
// Refresh account data
378390
const refreshAccountData = async () => {
379391
await Promise.all([loadAccountData(), loadDemoStats()]);
380392
};
381393

382394
// Clap a demo
383395
const clapDemo = async (demoId: string) => {
396+
if (!walletData?.publicKey) {
397+
addToast({
398+
title: 'Error',
399+
message: 'Please connect your wallet to clap for demos.',
400+
type: 'error',
401+
});
402+
return;
403+
}
404+
384405
try {
385-
await demoStatsService.incrementClap(demoId);
386-
await loadDemoStats(); // Refresh demo stats
406+
await demoStatsService.incrementClap(demoId, walletData.publicKey);
407+
await Promise.all([loadAccountData(), loadDemoStats()]); // Refresh both account data and demo stats
387408

388409
addToast({
389410
title: '👏 Demo Clapped!',
@@ -393,9 +414,10 @@ export const FirebaseProvider: React.FC<FirebaseProviderProps> = ({ children })
393414
});
394415
} catch (error) {
395416
console.error('Failed to clap demo:', error);
417+
const errorMessage = error instanceof Error ? error.message : 'Failed to clap demo. Please try again.';
396418
addToast({
397419
title: 'Error',
398-
message: 'Failed to clap demo. Please try again.',
420+
message: errorMessage,
399421
type: 'error',
400422
});
401423
}
@@ -412,6 +434,7 @@ export const FirebaseProvider: React.FC<FirebaseProviderProps> = ({ children })
412434
completeDemo,
413435
hasBadge,
414436
hasCompletedDemo,
437+
hasClappedDemo,
415438
refreshAccountData,
416439
clapDemo,
417440
};

0 commit comments

Comments
 (0)