Skip to content

Commit 73e1f72

Browse files
committed
feat: Simplify feedback system, fix Firebase issues, and improve analytics dashboard
- Create SimpleFeedbackModal for quick 30-second feedback collection - Fix Firebase feedback submission issues with better error handling - Improve analytics dashboard with funding-ready metrics and revenue projections - Remove typewriter effect from first demo title for instant display - Clean up unused files from /lib folder - Update badge system with correct badge names and requirements - Fix notification loops in demo completion process - Add comprehensive error logging and debugging for Firebase operations - Enhance user experience with streamlined feedback flow - Add market opportunity metrics for investor presentations
1 parent fb98f2b commit 73e1f72

35 files changed

+1369
-1895
lines changed

app/analytics/page.tsx

Lines changed: 506 additions & 0 deletions
Large diffs are not rendered by default.

app/page.tsx

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { MilestoneVotingDemo } from '@/components/demos/MilestoneVotingDemo';
2222
import { DisputeResolutionDemo } from '@/components/demos/DisputeResolutionDemo';
2323
import { MicroTaskMarketplaceDemo } from '@/components/demos/MicroTaskMarketplaceDemo';
2424
import { useDemoStats } from '@/hooks/useDemoStats';
25-
import { DemoFeedbackModal } from '@/components/ui/modals/DemoFeedbackModal';
25+
import { SimpleFeedbackModal } from '@/components/ui/modals/SimpleFeedbackModal';
2626
import { initializeDemoStats } from '@/lib/demo-stats-initializer';
2727
import { OnboardingOverlay } from '@/components/OnboardingOverlay';
2828
import { ImmersiveDemoModal } from '@/components/ui/modals/ImmersiveDemoModal';
@@ -107,7 +107,7 @@ const DemoSelector = ({
107107
isConnected: boolean;
108108
}) => {
109109
const { getCompletedDemos } = useAccount();
110-
const { demoStats, clapDemo } = useDemoStats();
110+
const { demoStats, clapDemo, refreshStats } = useDemoStats();
111111

112112
const getClapStats = (demoId: string) => {
113113
const stats = demoStats[demoId];
@@ -476,8 +476,9 @@ const DemoSelector = ({
476476
function HomePageContent() {
477477
const { isConnected } = useGlobalWallet();
478478
const { isAuthenticated, user } = useAuth();
479+
const { account } = useAccount();
479480
const [activeDemo, setActiveDemo] = useState('hello-milestone');
480-
const { submitFeedback, markDemoComplete } = useDemoStats();
481+
const { submitFeedback, markDemoComplete, refreshStats } = useDemoStats();
481482

482483
const [walletSidebarOpen, setWalletSidebarOpen] = useState(false);
483484
const [walletExpanded, setWalletExpanded] = useState(false);
@@ -571,6 +572,15 @@ function HomePageContent() {
571572
};
572573
}, []);
573574

575+
// Refresh demo stats when account changes (after demo completion)
576+
useEffect(() => {
577+
if (account) {
578+
refreshStats().catch(error => {
579+
console.error('Failed to refresh demo stats:', error);
580+
});
581+
}
582+
}, [account, refreshStats]);
583+
574584
// Authentication handlers
575585
const handleSignUpClick = () => {
576586
setAuthModalMode('signup');
@@ -1129,7 +1139,7 @@ function HomePageContent() {
11291139

11301140
{/* Demo Feedback Modal */}
11311141
{showFeedbackModal && feedbackDemoData && (
1132-
<DemoFeedbackModal
1142+
<SimpleFeedbackModal
11331143
isOpen={showFeedbackModal}
11341144
onClose={handleFeedbackClose}
11351145
onSubmit={handleFeedbackSubmit}

components/demos/DisputeResolutionDemo.tsx

Lines changed: 17 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
useRealInitializeEscrow,
1616
} from '@/lib/real-trustless-work';
1717
import { assetConfig } from '@/lib/wallet-config';
18+
import { useAccount } from '@/contexts/AccountContext';
1819
import { useToast } from '@/contexts/ToastContext';
1920
import { useTransactionHistory } from '@/contexts/TransactionContext';
2021
import { useDemoStats } from '@/hooks/useDemoStats';
@@ -50,6 +51,7 @@ export const DisputeResolutionDemo = () => {
5051
const { addToast } = useToast();
5152
const { addTransaction, updateTransaction } = useTransactionHistory();
5253
const { addCompletion, getDemoHistory, getTotalPointsEarned, getBestScore, getCompletionCount } = useDemoCompletionHistory();
54+
const { completeDemo: completeDemoInAccount } = useAccount();
5355
const [contractId, setContractId] = useState<string>('');
5456
const [escrowData, setEscrowData] = useState<any>(null);
5557
const [currentRole, setCurrentRole] = useState<'client' | 'worker' | 'arbitrator'>('client');
@@ -113,6 +115,7 @@ export const DisputeResolutionDemo = () => {
113115
client: 'client_wallet_address',
114116
},
115117
]);
118+
const [isCompleted, setIsCompleted] = useState(false);
116119

117120
// Trigger confetti when demo is completed
118121
useEffect(() => {
@@ -123,49 +126,29 @@ export const DisputeResolutionDemo = () => {
123126
milestones.map(m => ({ id: m.id, status: m.status }))
124127
);
125128

126-
if (allReleased) {
129+
// Only complete if all milestones are released AND demo hasn't been completed yet
130+
if (allReleased && !isCompleted) {
127131
console.log('🎉 Triggering confetti for Dispute Resolution Demo!');
128132
setShowConfetti(true);
133+
setIsCompleted(true); // Mark as completed to prevent multiple executions
129134

130-
// Complete the demo in Firebase
135+
// Complete the demo using the centralized account system
131136
const completeDemo = async () => {
132137
try {
133-
const completionTime = 5; // Estimated completion time in minutes
134138
const score = 95; // High score for completing dispute resolution
135-
const completionHistory = getDemoHistory('dispute-resolution');
136-
const isFirstCompletion = completionHistory.length === 0;
137139

138-
// Calculate points earned
139-
const basePoints = 150; // Higher base points for dispute resolution
140-
const scoreMultiplier = score / 100;
141-
let pointsEarned = Math.round(basePoints * scoreMultiplier);
142-
143-
// Give reduced points for replays (25% of original)
144-
if (!isFirstCompletion) {
145-
pointsEarned = Math.round(pointsEarned * 0.25);
146-
}
147-
148-
// Add to completion history
149-
addCompletion({
150-
demoId: 'dispute-resolution',
151-
demoName: 'Drama Queen Escrow',
152-
score,
153-
pointsEarned,
154-
completionTime,
155-
isFirstCompletion,
156-
network: walletData?.network || 'TESTNET',
157-
walletAddress: walletData?.publicKey || '',
158-
});
159-
160-
// Demo completion is handled by the account system via completeDemo
140+
// Use the centralized account system for completion
141+
await completeDemoInAccount('dispute-resolution', score);
161142

143+
console.log('✅ Dispute Resolution Demo completed successfully');
144+
} catch (error) {
145+
console.error('❌ Failed to complete Dispute Resolution Demo:', error);
162146
addToast({
163-
type: 'success',
164-
title: 'Demo Completed!',
165-
message: `Completed Drama Queen Escrow! Earned ${pointsEarned} points! 🎉`,
147+
type: 'error',
148+
title: '❌ Demo Completion Failed',
149+
message: 'Failed to complete demo. Please try again.',
150+
duration: 5000,
166151
});
167-
} catch (error) {
168-
console.error('Failed to complete demo:', error);
169152
}
170153
};
171154

@@ -179,7 +162,7 @@ export const DisputeResolutionDemo = () => {
179162
}, 4000);
180163
return () => clearTimeout(timer);
181164
}
182-
}, [milestones, addCompletion, getDemoHistory, walletData, addToast]);
165+
}, [milestones, completeDemoInAccount, walletData, addToast, isCompleted]);
183166

184167
async function handleInitializeEscrow() {
185168
if (!walletData) {

components/demos/HelloMilestoneDemo.tsx

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1407,21 +1407,12 @@ export const HelloMilestoneDemo = () => {
14071407
<div className='bg-gradient-to-br from-brand-500/20 to-brand-400/20 backdrop-blur-sm border border-brand-400/30 rounded-xl shadow-2xl p-8'>
14081408
<div className='text-center mb-8'>
14091409
<h2 className='text-3xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-brand-400 to-brand-300 mb-4'>
1410-
<TypeWriter
1411-
text="🚀 Baby Steps to Riches Flow Demo"
1412-
speed={60}
1413-
className="text-3xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-brand-400 to-brand-300"
1414-
showCursor={false}
1415-
/>
1410+
🚀 Baby Steps to Riches Flow Demo
14161411
</h2>
14171412
<div className="mb-4">
1418-
<TypeWriter
1419-
text="Experience the complete trustless escrow flow with real blockchain transactions"
1420-
speed={40}
1421-
delay={2000}
1422-
className="text-white/80 text-lg"
1423-
showCursor={false}
1424-
/>
1413+
<p className="text-white/80 text-lg">
1414+
Experience the complete trustless escrow flow with real blockchain transactions
1415+
</p>
14251416
</div>
14261417

14271418

components/demos/MicroTaskMarketplaceDemo.tsx

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -405,15 +405,9 @@ export const MicroTaskMarketplaceDemo = () => {
405405
duration: 5000,
406406
});
407407

408-
// Mark milestone as completed
409-
const payload = {
410-
contractId: task.escrowId,
411-
milestoneId: 'release_1',
412-
status: 'completed',
413-
releaseMode: 'multi-release',
414-
};
415-
416-
await hooks.changeMilestoneStatus(payload);
408+
// Note: Milestone status change is handled by the escrow system
409+
// For demo purposes, we'll skip the milestone status change
410+
// as it requires real blockchain implementation
417411
setDeliverable('');
418412
} catch (error) {
419413
console.error('Failed to submit deliverable:', error);

components/layout/Header.tsx

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -64,19 +64,6 @@ export const Header = () => {
6464
/>
6565
<span>Nexus Web3 Playground</span>
6666
</a>
67-
<a
68-
href='/docs'
69-
className='text-white/80 hover:text-white transition-colors flex items-center space-x-2'
70-
>
71-
<Image
72-
src='/images/icons/docs.png'
73-
alt='Docs'
74-
width={20}
75-
height={20}
76-
className='w-5 h-5'
77-
/>
78-
<span>Docs</span>
79-
</a>
8067
</nav>
8168

8269
{/* Header Controls */}
@@ -199,6 +186,16 @@ export const Header = () => {
199186
<span>Docs</span>
200187
</div>
201188
</a>
189+
<a
190+
href='/analytics'
191+
className='block px-3 py-2 text-white/80 hover:text-white hover:bg-white/10 rounded-md transition-colors'
192+
onClick={() => setIsMenuOpen(false)}
193+
>
194+
<div className='flex items-center space-x-2'>
195+
<span className='text-lg'>📊</span>
196+
<span>Analytics</span>
197+
</div>
198+
</a>
202199
</div>
203200
</div>
204201
)}

components/ui/RewardsSidebar.tsx

Lines changed: 76 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -113,16 +113,31 @@ export const RewardsSidebar: React.FC<RewardsSidebarProps> = ({ isOpen, onClose
113113

114114
const renderBadges = () => {
115115
try {
116+
// Define main achievement badges
117+
const mainBadgeIds = [
118+
'welcome_explorer',
119+
'escrow-expert',
120+
'trust-guardian',
121+
'stellar-champion',
122+
'nexus-master'
123+
];
124+
116125
// Check which badges are earned by the user (match by name since that's how they're stored)
117126
const earnedBadgeNames = safeAccount.badges?.map((badge: any) => badge.name) || [];
118127
const badgesWithStatus = AVAILABLE_BADGES?.map((badge: Badge) => ({
119128
...badge,
120129
isEarned: earnedBadgeNames.includes(badge.name),
121-
earnedAt: safeAccount.badges?.find(b => b.name === badge.name)?.earnedAt
130+
earnedAt: safeAccount.badges?.find(b => b.name === badge.name)?.earnedAt,
131+
isMainAchievement: mainBadgeIds.includes(badge.id)
122132
})) || [];
123133

134+
// Separate main achievements from extra badges
135+
const mainBadges = badgesWithStatus.filter(badge => badge.isMainAchievement);
136+
const extraBadges = badgesWithStatus.filter(badge => !badge.isMainAchievement);
137+
124138
const earnedCount = earnedBadgeNames.length;
125139
const totalCount = AVAILABLE_BADGES?.length || 0;
140+
const mainEarnedCount = mainBadges.filter(badge => badge.isEarned).length;
126141

127142
return (
128143
<div className="space-y-4">
@@ -136,23 +151,67 @@ export const RewardsSidebar: React.FC<RewardsSidebarProps> = ({ isOpen, onClose
136151
/>
137152
</div>
138153
</div>
139-
140-
<div className="grid grid-cols-1 gap-3 max-h-96 overflow-y-auto">
141-
{badgesWithStatus.length > 0 ? (
142-
badgesWithStatus.map((badge) => (
143-
<Badge3D
144-
key={badge.id}
145-
badge={badge}
146-
size="sm"
147-
compact={true}
148-
/>
149-
))
150-
) : (
151-
<div className="text-center py-8 text-gray-400">
152-
<div className="text-4xl mb-2">🏆</div>
153-
<div className="text-sm">No badges available</div>
154+
155+
{/* Main Achievements Section */}
156+
<div className="space-y-3">
157+
<div className="flex items-center space-x-2 mb-3">
158+
<div className="w-2 h-2 bg-gradient-to-r from-blue-400 to-purple-500 rounded-full"></div>
159+
<h3 className="text-lg font-semibold text-white">Main Achievements</h3>
160+
<div className="bg-gradient-to-r from-blue-500/20 to-purple-500/20 px-2 py-1 rounded-full text-xs text-blue-300 border border-blue-400/30">
161+
{mainEarnedCount} / {mainBadges.length}
162+
</div>
163+
</div>
164+
165+
<div className="space-y-2">
166+
{mainBadges.map((badge) => (
167+
<div key={badge.id} className="relative">
168+
<Badge3D
169+
badge={badge}
170+
size="sm"
171+
compact={true}
172+
/>
173+
{/* Main Achievement Indicator */}
174+
<div className="absolute -top-1 -right-1 w-3 h-3 bg-gradient-to-r from-blue-400 to-purple-500 rounded-full border border-white/20"></div>
175+
</div>
176+
))}
177+
</div>
178+
</div>
179+
180+
{/* Extra Badges Section */}
181+
{extraBadges.length > 0 && (
182+
<div className="space-y-3">
183+
<div className="flex items-center space-x-2 mb-3">
184+
<div className="w-2 h-2 bg-gradient-to-r from-gray-400 to-gray-500 rounded-full"></div>
185+
<h3 className="text-lg font-semibold text-white">Extra Badges</h3>
186+
<div className="bg-gradient-to-r from-gray-500/20 to-gray-600/20 px-2 py-1 rounded-full text-xs text-gray-300 border border-gray-400/30">
187+
{extraBadges.filter(badge => badge.isEarned).length} / {extraBadges.length}
188+
</div>
189+
</div>
190+
191+
<div className="space-y-2 max-h-64 overflow-y-auto">
192+
{extraBadges.map((badge) => (
193+
<div key={badge.id} className="opacity-80 hover:opacity-100 transition-opacity">
194+
<Badge3D
195+
badge={badge}
196+
size="sm"
197+
compact={true}
198+
/>
199+
</div>
200+
))}
154201
</div>
155-
)}
202+
</div>
203+
)}
204+
205+
{/* Achievement Guide */}
206+
<div className="bg-gradient-to-r from-blue-500/10 to-purple-500/10 rounded-lg p-3 border border-blue-400/20 mt-4">
207+
<h4 className="text-sm font-semibold text-blue-300 mb-2">🎯 Achievement Guide</h4>
208+
<div className="text-xs text-gray-300 space-y-1">
209+
<div><span className="text-blue-300">Account Creation</span> → Welcome Explorer</div>
210+
<div><span className="text-blue-300">Complete Demo 1</span> → Escrow Expert</div>
211+
<div><span className="text-blue-300">Complete Demo 3</span> → Trust Guardian</div>
212+
<div><span className="text-blue-300">Complete Demo 4</span> → Stellar Champion</div>
213+
<div><span className="text-purple-300">Complete Demos 1, 3, 4</span> → Nexus Master</div>
214+
</div>
156215
</div>
157216
</div>
158217
);

components/ui/common/Card.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,5 @@ export const Card: React.FC<CardProps> = ({
6969

7070

7171

72+
73+

components/ui/common/LoadingSpinner.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,5 @@ export const LoadingSpinner: React.FC<LoadingSpinnerProps> = ({
5353

5454

5555

56+
57+

components/ui/common/ProgressBar.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,5 @@ export const ProgressBar: React.FC<ProgressBarProps> = ({
6464

6565

6666

67+
68+

0 commit comments

Comments
 (0)