Skip to content

Commit d7e9694

Browse files
committed
fix: resolve TypeScript errors and improve NexusPrime typewriter animation
- Fixed toast duration property errors in app/home/page.tsx - Removed invalid OnboardingOverlayProps currentDemo property - Fixed duplicate property names in DisputeResolutionDemo.tsx - Resolved undefined variable errors in DisputeResolutionDemo.tsx - Fixed variable declaration order in HelloMilestoneDemo.tsx - Corrected missing 'name' property in UserDashboard.tsx - Completely rewrote NexusPrime typewriter animation to prevent freezing - Simplified typewriter logic with useCallback and proper cleanup - Removed complex message queue system for better reliability - Enhanced performance with memoized functions and proper dependencies
1 parent c418545 commit d7e9694

5 files changed

Lines changed: 101 additions & 148 deletions

File tree

app/home/page.tsx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -826,7 +826,6 @@ const DemoSelector = ({
826826
title: '👑 Nexus Master Unlocked!',
827827
message:
828828
'You have mastered all trustless work demos! Earned 200 XP and the legendary Nexus Master badge!',
829-
duration: 6000,
830829
});
831830

832831
// Refresh account data to update UI
@@ -836,7 +835,6 @@ const DemoSelector = ({
836835
type: 'error',
837836
title: '❌ Claim Failed',
838837
message: 'Failed to claim Nexus Master badge. Please try again.',
839-
duration: 4000,
840838
});
841839
} finally {
842840
setIsClaimingNexusMaster(false);
@@ -1005,10 +1003,10 @@ export default function HomePageContent() {
10051003
isLoading: firebaseLoading,
10061004
isInitialized,
10071005
} = useFirebase();
1006+
const { addToast: addToastHook } = useToast();
10081007
const [activeDemo, setActiveDemo] = useState('hello-milestone');
10091008
// Note: submitFeedback removed from simplified Firebase context
10101009
// Removed old AccountService usage
1011-
const { addToast } = useToast();
10121010

10131011
// Check if user has unlocked mini-games access (earned all badges including Nexus Master)
10141012
const miniGamesUnlocked = useMemo(() => {
@@ -1194,7 +1192,7 @@ export default function HomePageContent() {
11941192
feedbackDemoData.completionTime
11951193
);
11961194

1197-
addToast({
1195+
addToastHook({
11981196
title: '🎉 Demo Completed!',
11991197
message: `Great job completing ${feedbackDemoData.demoName}!`,
12001198
type: 'success',
@@ -1203,7 +1201,7 @@ export default function HomePageContent() {
12031201
}
12041202
} catch (error) {
12051203
// Failed to complete demo - error is shown in toast
1206-
addToast({
1204+
addToastHook({
12071205
title: 'Error',
12081206
message: 'Failed to complete demo. Please try again.',
12091207
type: 'error',
@@ -1502,7 +1500,7 @@ export default function HomePageContent() {
15021500
setActiveDemo={setActiveDemo}
15031501
setShowImmersiveDemo={setShowImmersiveDemo}
15041502
isConnected={isConnected}
1505-
addToast={addToast}
1503+
addToast={(toast) => addToastHook({ ...toast, duration: 5000 })}
15061504
account={account}
15071505
demos={demos}
15081506
demoStats={demoStats}
@@ -1724,7 +1722,6 @@ export default function HomePageContent() {
17241722
setShowOnboarding(false);
17251723
setHasSeenOnboarding(true);
17261724
}}
1727-
currentDemo={activeDemo}
17281725
/>
17291726

17301727
{/* Immersive Demo Modal */}

components/demos/DisputeResolutionDemo.tsx

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -616,10 +616,6 @@ export const DisputeResolutionDemo = () => {
616616
message: `Resolving dispute for "${milestone?.title}" (${resolution})...`,
617617
type: 'dispute',
618618
demoId: 'dispute-resolution',
619-
amount: milestone?.amount,
620-
asset: 'USDC',
621-
type: 'dispute',
622-
demoId: 'dispute-resolution',
623619
amount: milestone?.amount ? (parseInt(milestone.amount) / 100000).toFixed(1) : '0',
624620
asset: 'USDC',
625621
});
@@ -725,8 +721,6 @@ export const DisputeResolutionDemo = () => {
725721
demoId: 'dispute-resolution',
726722
amount: milestone?.amount,
727723
asset: 'USDC',
728-
type: 'dispute',
729-
demoId: 'dispute-resolution',
730724
});
731725

732726
addToast({
@@ -779,10 +773,6 @@ export const DisputeResolutionDemo = () => {
779773
message: 'Releasing all funds...',
780774
type: 'release',
781775
demoId: 'dispute-resolution',
782-
amount: '10',
783-
asset: 'USDC',
784-
type: 'release',
785-
demoId: 'dispute-resolution',
786776
amount: milestones.reduce((total, m) => total + parseInt(m.amount) / 100000, 0).toFixed(1),
787777
asset: 'USDC',
788778
});
@@ -821,8 +811,6 @@ export const DisputeResolutionDemo = () => {
821811
demoId: 'dispute-resolution',
822812
amount: '10',
823813
asset: 'USDC',
824-
type: 'release',
825-
demoId: 'dispute-resolution',
826814
});
827815

828816
addToast({
@@ -848,8 +836,6 @@ export const DisputeResolutionDemo = () => {
848836
setEscrowData(null);
849837
setCurrentRole('client');
850838
setDisputes([]);
851-
setNewDisputeReason('');
852-
setResolutionReason('');
853839

854840
// Reset milestone statuses
855841
const resetMilestones = milestones.map(m => ({ ...m, status: 'pending' as const }));

components/demos/HelloMilestoneDemo.tsx

Lines changed: 51 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,57 @@ export const HelloMilestoneDemo = ({
189189
};
190190
}, [transactionTimeouts]);
191191

192+
// Helper function to update transaction status and check for step completion
193+
const updateTransactionStatusAndCheckCompletion = (
194+
txHash: string,
195+
status: 'pending' | 'success' | 'failed',
196+
message: string
197+
) => {
198+
// Only update transaction with success/failed status (updateTransaction expects these)
199+
if (status === 'success' || status === 'failed') {
200+
updateTransaction(txHash, status, message);
201+
}
202+
setTransactionStatuses(prev => ({ ...prev, [txHash]: status }));
203+
204+
if (status === 'success') {
205+
// Clear any pending timeout for this transaction
206+
const timeout = transactionTimeouts[txHash];
207+
if (timeout) {
208+
clearTimeout(timeout);
209+
setTransactionTimeouts(prev => {
210+
const newTimeouts = { ...prev };
211+
delete newTimeouts[txHash];
212+
return newTimeouts;
213+
});
214+
}
215+
216+
// Find which step this transaction belongs to
217+
const stepId = Object.keys(pendingTransactions).find(
218+
key => pendingTransactions[key] === txHash
219+
);
220+
if (stepId) {
221+
// Remove from pending
222+
setPendingTransactions(prev => {
223+
const newPending = { ...prev };
224+
delete newPending[stepId];
225+
return newPending;
226+
});
227+
228+
// Allow progression to next step
229+
const stepOrder = ['initialize', 'fund', 'complete', 'approve', 'release'];
230+
const currentIndex = stepOrder.indexOf(stepId);
231+
if (currentIndex !== -1 && currentIndex + 1 <= stepOrder.length) {
232+
setCurrentStep(currentIndex + 1);
233+
234+
// Show success
235+
setTimeout(() => {
236+
setShowProcessExplanation(false);
237+
}, 1000);
238+
}
239+
}
240+
}
241+
};
242+
192243
// Auto-completion countdown effect for better UX
193244
useEffect(() => {
194245
const intervals: Record<string, NodeJS.Timeout> = {};
@@ -260,57 +311,6 @@ export const HelloMilestoneDemo = ({
260311
return status === 'success';
261312
};
262313

263-
// Helper function to update transaction status and check for step completion
264-
const updateTransactionStatusAndCheckCompletion = (
265-
txHash: string,
266-
status: 'pending' | 'success' | 'failed',
267-
message: string
268-
) => {
269-
// Only update transaction with success/failed status (updateTransaction expects these)
270-
if (status === 'success' || status === 'failed') {
271-
updateTransaction(txHash, status, message);
272-
}
273-
setTransactionStatuses(prev => ({ ...prev, [txHash]: status }));
274-
275-
if (status === 'success') {
276-
// Clear any pending timeout for this transaction
277-
const timeout = transactionTimeouts[txHash];
278-
if (timeout) {
279-
clearTimeout(timeout);
280-
setTransactionTimeouts(prev => {
281-
const newTimeouts = { ...prev };
282-
delete newTimeouts[txHash];
283-
return newTimeouts;
284-
});
285-
}
286-
287-
// Find which step this transaction belongs to
288-
const stepId = Object.keys(pendingTransactions).find(
289-
key => pendingTransactions[key] === txHash
290-
);
291-
if (stepId) {
292-
// Remove from pending
293-
setPendingTransactions(prev => {
294-
const newPending = { ...prev };
295-
delete newPending[stepId];
296-
return newPending;
297-
});
298-
299-
// Allow progression to next step
300-
const stepOrder = ['initialize', 'fund', 'complete', 'approve', 'release'];
301-
const currentIndex = stepOrder.indexOf(stepId);
302-
if (currentIndex !== -1 && currentIndex + 1 <= stepOrder.length) {
303-
setCurrentStep(currentIndex + 1);
304-
305-
// Show success
306-
setTimeout(() => {
307-
setShowProcessExplanation(false);
308-
}, 1000);
309-
}
310-
}
311-
}
312-
};
313-
314314
const getStepStatus = (
315315
stepIndex: number,
316316
stepId: string

components/layout/NexusPrime.tsx

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

3-
import React, { useState, useEffect } from 'react';
3+
import React, { useState, useEffect, useRef, useCallback } from 'react';
44
import Image from 'next/image';
55

66
interface NexusPrimeProps {
@@ -16,12 +16,13 @@ export const NexusPrime: React.FC<NexusPrimeProps> = ({
1616
}) => {
1717
const [isExpanded, setIsExpanded] = useState(false);
1818
const [currentMessage, setCurrentMessage] = useState('');
19-
const [messageQueue, setMessageQueue] = useState<string[]>([]);
2019
const [isTyping, setIsTyping] = useState(false);
2120
const [showTutorial, setShowTutorial] = useState(false);
2221
const [tutorialStep, setTutorialStep] = useState(0);
2322
const [isSpeaking, setIsSpeaking] = useState(false);
2423
const [ttsEnabled, setTtsEnabled] = useState(true);
24+
const lastProcessedMessageRef = useRef('');
25+
const typewriterTimeoutRef = useRef<NodeJS.Timeout | null>(null);
2526

2627
// Tutorial steps for interactive guidance
2728
const tutorialSteps = [
@@ -131,7 +132,7 @@ export const NexusPrime: React.FC<NexusPrimeProps> = ({
131132
};
132133

133134
// Get appropriate message based on current context
134-
const getContextMessage = () => {
135+
const getContextMessage = useCallback(() => {
135136
let message = '';
136137

137138
if (currentPage === 'home') {
@@ -160,41 +161,54 @@ export const NexusPrime: React.FC<NexusPrimeProps> = ({
160161
return String(message)
161162
.replace(/undefined/g, '')
162163
.trim();
163-
};
164+
}, [currentPage, currentDemo, walletConnected]);
165+
166+
// Simple typewriter effect for messages
167+
const startTypewriter = useCallback((message: string) => {
168+
// Clear any existing timeout
169+
if (typewriterTimeoutRef.current) {
170+
clearTimeout(typewriterTimeoutRef.current);
171+
}
164172

165-
// Typewriter effect for messages
173+
setCurrentMessage('');
174+
setIsTyping(true);
175+
176+
let index = 0;
177+
const typeNextChar = () => {
178+
if (index < message.length) {
179+
const char = message[index];
180+
if (char && char !== 'undefined') {
181+
setCurrentMessage(prev => prev + char);
182+
}
183+
index++;
184+
typewriterTimeoutRef.current = setTimeout(typeNextChar, 50);
185+
} else {
186+
setIsTyping(false);
187+
typewriterTimeoutRef.current = null;
188+
}
189+
};
190+
191+
// Start typing after a small delay
192+
typewriterTimeoutRef.current = setTimeout(typeNextChar, 100);
193+
}, []);
194+
195+
// Effect to trigger typewriter when context changes
166196
useEffect(() => {
167197
const message = getContextMessage();
168-
if (message && message !== currentMessage) {
169-
setMessageQueue(prev => [...(prev || []), message]);
198+
if (message && message !== lastProcessedMessageRef.current) {
199+
lastProcessedMessageRef.current = message;
200+
startTypewriter(message);
170201
}
171-
}, [currentPage, currentDemo, walletConnected, currentMessage]);
202+
}, [getContextMessage, startTypewriter]);
172203

204+
// Cleanup timeout on unmount
173205
useEffect(() => {
174-
if (messageQueue && messageQueue.length > 0 && !isTyping) {
175-
const nextMessage = messageQueue[0];
176-
if (nextMessage) {
177-
setMessageQueue(prev => prev.slice(1));
178-
setCurrentMessage('');
179-
setIsTyping(true);
180-
181-
let index = 0;
182-
const typeInterval = setInterval(() => {
183-
if (index < nextMessage.length) {
184-
const char = nextMessage[index];
185-
// Only add valid characters, skip undefined or null
186-
if (char && char !== 'undefined') {
187-
setCurrentMessage(prev => prev + char);
188-
}
189-
index++;
190-
} else {
191-
setIsTyping(false);
192-
clearInterval(typeInterval);
193-
}
194-
}, 50);
206+
return () => {
207+
if (typewriterTimeoutRef.current) {
208+
clearTimeout(typewriterTimeoutRef.current);
195209
}
196-
}
197-
}, [messageQueue, isTyping]);
210+
};
211+
}, []);
198212

199213
// Removed problematic auto-hide mechanism that was causing button to disappear
200214

@@ -417,50 +431,6 @@ export const NexusPrime: React.FC<NexusPrimeProps> = ({
417431
)}
418432
</p>
419433

420-
{/* TTS Controls for Regular Chat */}
421-
<div className='mt-2 flex items-center justify-between'>
422-
<span className='text-xs text-white/60'>Voice Assistant:</span>
423-
<div className='flex items-center space-x-2'>
424-
{/* Play Button */}
425-
<button
426-
onClick={() => {
427-
if (ttsEnabled && currentMessage) {
428-
speakMessage(currentMessage);
429-
}
430-
}}
431-
disabled={!ttsEnabled || !currentMessage || isSpeaking}
432-
className={`px-3 py-1 rounded-lg transition-all duration-200 hover:scale-105 text-xs ${
433-
!ttsEnabled || !currentMessage || isSpeaking
434-
? 'bg-gray-500/20 text-gray-400 border border-gray-400/30 cursor-not-allowed'
435-
: 'bg-gradient-to-r from-blue-500/20 to-indigo-600/20 border border-blue-400/50 text-blue-300 hover:bg-gradient-to-r hover:from-blue-500/30 hover:to-indigo-600/30'
436-
}`}
437-
title={
438-
!ttsEnabled
439-
? 'Voice is disabled'
440-
: !currentMessage
441-
? 'No message to play'
442-
: isSpeaking
443-
? 'Already speaking'
444-
: 'Play message with voice'
445-
}
446-
>
447-
{isSpeaking ? '⏸️' : '▶️'}
448-
</button>
449-
450-
{/* TTS Toggle */}
451-
<button
452-
onClick={toggleTts}
453-
className={`px-3 py-1 rounded-lg transition-all duration-200 hover:scale-105 text-xs ${
454-
ttsEnabled
455-
? 'bg-gradient-to-r from-green-500/20 to-emerald-600/20 border border-green-400/50 text-green-300'
456-
: 'bg-gradient-to-r from-red-500/20 to-pink-600/20 border border-red-400/50 text-red-300'
457-
}`}
458-
title={ttsEnabled ? 'Disable Voice' : 'Enable Voice'}
459-
>
460-
{ttsEnabled ? '🔊 ON' : '🔇 OFF'}
461-
</button>
462-
</div>
463-
</div>
464434
</div>
465435
)}
466436
</div>

0 commit comments

Comments
 (0)