Skip to content

Commit f85db5b

Browse files
committed
feat(ui): update Card, LoadingSpinner, ProgressBar, and PokemonReferralCard components
1 parent b973224 commit f85db5b

4 files changed

Lines changed: 109 additions & 180 deletions

File tree

components/ui/common/Card.tsx

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

6868

6969

70+
71+
72+

components/ui/common/LoadingSpinner.tsx

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

5252

5353

54+
55+
56+

components/ui/common/ProgressBar.tsx

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

6363

6464

65+
66+
67+

components/ui/referral/PokemonReferralCard.tsx

Lines changed: 100 additions & 180 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { useGlobalWallet } from '@/contexts/wallet/WalletContext';
1010
import { useAuth } from '@/contexts/auth/AuthContext';
1111
import html2canvas from 'html2canvas';
1212
import Image from 'next/image';
13-
import GIF from 'gif.js';
1413

1514
interface PokemonReferralCardProps {
1615
account: Account | null;
@@ -26,8 +25,6 @@ export const PokemonReferralCard: React.FC<PokemonReferralCardProps> = ({
2625
const { user } = useAuth();
2726
const [isGeneratingPng, setIsGeneratingPng] = useState(false);
2827
const [isSharing, setIsSharing] = useState(false);
29-
const [isGeneratingGif, setIsGeneratingGif] = useState(false);
30-
const [gifProgress, setGifProgress] = useState(0);
3128
const [selectedPhase, setSelectedPhase] = useState(0); // 0: baby, 1: teen, 2: adult
3229
const [selectedBackground, setSelectedBackground] = useState(0); // 0: explorer, 1: mid-level, 2: expert
3330
const [selectedForm, setSelectedForm] = useState(0); // 0: initial, 1: mid, 2: final
@@ -54,23 +51,23 @@ export const PokemonReferralCard: React.FC<PokemonReferralCardProps> = ({
5451
const rankingThemes = {
5552
1: {
5653
// Gold
57-
// background: 'linear-gradient(135deg, #FFD700 0%, #FFA500 50%, #FF8C00 100%)',
54+
background: 'linear-gradient(135deg, #FFD700 0%, #FFA500 50%, #FF8C00 100%)',
5855
border: '#FFD700',
5956
text: '#000000',
6057
shadow: 'shadow-yellow-500/50',
6158
glow: 'from-yellow-400/50 to-orange-400/50',
6259
},
6360
2: {
6461
// Silver
65-
// background: 'linear-gradient(135deg, #C0C0C0 0%, #A8A8A8 50%, #808080 100%)',
62+
background: 'linear-gradient(135deg, #C0C0C0 0%, #A8A8A8 50%, #808080 100%)',
6663
border: '#C0C0C0',
6764
text: '#000000',
6865
shadow: 'shadow-gray-400/50',
6966
glow: 'from-gray-300/50 to-gray-500/50',
7067
},
7168
3: {
7269
// Bronze
73-
// background: 'linear-gradient(135deg, #CD7F32 0%, #B8860B 50%, #8B4513 100%)',
70+
background: 'linear-gradient(135deg, #CD7F32 0%, #B8860B 50%, #8B4513 100%)',
7471
border: '#CD7F32',
7572
text: '#FFFFFF',
7673
shadow: 'shadow-orange-600/50',
@@ -257,110 +254,6 @@ export const PokemonReferralCard: React.FC<PokemonReferralCardProps> = ({
257254
}
258255
};
259256

260-
const handleDownloadGif = async () => {
261-
if (!cardRef.current) return;
262-
263-
setIsGeneratingGif(true);
264-
setGifProgress(0);
265-
266-
try {
267-
const frames = [];
268-
const totalFrames = 12; // Number of frames for the GIF
269-
const delay = 100; // Delay between frames in ms
270-
271-
// Generate frames with slight variations for animation
272-
273-
for (let i = 0; i < totalFrames; i++) {
274-
// Create a temporary clone of the card with slight modifications
275-
const clonedCard = cardRef.current.cloneNode(true) as HTMLElement;
276-
277-
// Add slight rotation and glow effect for animation
278-
clonedCard.style.transform = `rotate(${Math.sin(i * 0.5) * 2}deg)`;
279-
clonedCard.style.boxShadow = `0 0 ${20 + Math.sin(i * 0.8) * 10}px rgba(255, 215, 0, 0.5)`;
280-
281-
// Temporarily add to DOM for capture
282-
clonedCard.style.cssText += `
283-
position: absolute;
284-
top: -9999px;
285-
left: -9999px;
286-
visibility: hidden;
287-
pointer-events: none;
288-
`;
289-
document.body.appendChild(clonedCard);
290-
291-
try {
292-
const canvas = await html2canvas(clonedCard, {
293-
width: 400,
294-
height: 520,
295-
background: 'transparent',
296-
useCORS: true,
297-
allowTaint: true,
298-
});
299-
300-
frames.push(canvas);
301-
const progress = ((i + 1) / totalFrames) * 100;
302-
setGifProgress(progress);
303-
304-
// Small delay to show progress
305-
await new Promise(resolve => setTimeout(resolve, 50));
306-
} finally {
307-
document.body.removeChild(clonedCard);
308-
}
309-
}
310-
311-
// Create GIF
312-
const gif = new GIF({
313-
workers: 2,
314-
quality: 10,
315-
width: 400,
316-
height: 520,
317-
workerScript: '/gif.worker.js',
318-
});
319-
320-
// Add frames to GIF
321-
frames.forEach(frame => {
322-
gif.addFrame(frame, { delay: delay });
323-
});
324-
325-
// Render GIF
326-
gif.on('finished', (blob: Blob) => {
327-
const url = URL.createObjectURL(blob);
328-
const link = document.createElement('a');
329-
link.href = url;
330-
link.download = `nexus-card-${account.displayName || 'anonymous'}-${Date.now()}.gif`;
331-
document.body.appendChild(link);
332-
link.click();
333-
document.body.removeChild(link);
334-
URL.revokeObjectURL(url);
335-
336-
setIsGeneratingGif(false);
337-
setGifProgress(0);
338-
addToast({
339-
type: 'success',
340-
title: '🎭 GIF Downloaded!',
341-
message: 'Your animated Nexus card is ready!',
342-
duration: 3000,
343-
});
344-
});
345-
346-
gif.on('progress', (progress: number) => {
347-
setGifProgress(progress * 100);
348-
});
349-
350-
gif.render();
351-
352-
} catch (error) {
353-
console.error('Error generating GIF:', error);
354-
setIsGeneratingGif(false);
355-
setGifProgress(0);
356-
addToast({
357-
type: 'error',
358-
title: 'GIF Generation Failed',
359-
message: 'Unable to create animated GIF. Please try again.',
360-
duration: 3000,
361-
});
362-
}
363-
};
364257

365258

366259

@@ -497,6 +390,8 @@ export const PokemonReferralCard: React.FC<PokemonReferralCardProps> = ({
497390
alt={`${currentBackground.name} Background`}
498391
className='w-full h-full object-cover'
499392
/>
393+
{/* Background Overlay for Better Text Readability */}
394+
<div className='absolute inset-0 bg-black/40'></div>
500395
</div>
501396

502397
<br/>
@@ -546,30 +441,84 @@ export const PokemonReferralCard: React.FC<PokemonReferralCardProps> = ({
546441
)}
547442

548443
{currentLayout.id === 1 && (
549-
/* Modern Layout - Centered Single Column */
550-
<div className='flex flex-col items-center text-center space-y-4'>
551-
<div className='text-sm font-bold' style={{ color: currentTheme.text }}>
552-
NEXUS CARD
444+
/* Modern Layout - Profile Card Style */
445+
<div className='flex flex-col h-full'>
446+
{/* Top Section - Card Type and Icon */}
447+
<div className='flex justify-between items-start mb-4'>
448+
<div className='text-sm font-bold text-white/90'>
449+
COMMON
450+
</div>
451+
<div className='w-6 h-6 bg-blue-400 rounded-full flex items-center justify-center'>
452+
<div className='w-3 h-3 bg-white rounded-full'></div>
453+
</div>
553454
</div>
554-
555-
{/* Avatar */}
556-
<div className='relative'>
557-
<div className='scale-125'>
558-
<UserAvatar size='lg' showStatus={false} />
455+
456+
{/* Center Section - Avatar and Name */}
457+
<div className='flex flex-col items-center mb-6'>
458+
{/* Avatar */}
459+
<div className='relative mb-3'>
460+
<div className='scale-125'>
461+
<UserAvatar size='lg' showStatus={false} />
462+
</div>
463+
<div className='absolute -bottom-1 -right-1 bg-orange-500 rounded-full w-8 h-8 flex items-center justify-center'>
464+
<div className='text-sm font-bold text-white'>
465+
{userLevel}
466+
</div>
467+
</div>
559468
</div>
560-
<div className='absolute -bottom-1 -right-1 bg-white rounded-full px-1.5 py-0.5'>
561-
<div className='text-xs font-bold' style={{ color: 'black' }}>
562-
Lv.{userLevel}
469+
470+
{/* Name and Title */}
471+
<div className='text-center'>
472+
<div className='text-lg font-bold text-white mb-1'>
473+
{account.displayName || 'Nexus Explorer'}
474+
</div>
475+
<div className='text-sm text-yellow-400 font-semibold'>
476+
TRUSTLESS WORKER
563477
</div>
564478
</div>
565479
</div>
566480

567-
<div className='text-lg font-bold' style={{ color: currentTheme.text }}>
568-
{account.displayName || 'Nexus Explorer'}
569-
</div>
570-
<div className='text-xs opacity-80' style={{ color: currentTheme.text }}>
571-
{account.totalPoints || 0} points
481+
{/* Stats Section */}
482+
<div className='flex-1 mb-4'>
483+
<div className='grid grid-cols-2 gap-2 text-sm'>
484+
<div className='flex justify-between items-center'>
485+
<span className='text-yellow-400 font-semibold'>XP</span>
486+
<span className='text-white font-bold'>{account.experience || 0}</span>
487+
</div>
488+
<div className='flex justify-between items-center'>
489+
<span className='text-yellow-400 font-semibold'>POINTS</span>
490+
<span className='text-white font-bold'>{account.totalPoints || 0}</span>
491+
</div>
492+
<div className='flex justify-between items-center'>
493+
<span className='text-yellow-400 font-semibold'>REFERRALS</span>
494+
<span className='text-white font-bold'>0</span>
495+
</div>
496+
<div className='flex justify-between items-center'>
497+
<span className='text-yellow-400 font-semibold'>BADGES</span>
498+
<span className='text-white font-bold'>{earnedBadges.length} / 13</span>
499+
</div>
500+
</div>
572501
</div>
502+
503+
{/* Badges Section */}
504+
{earnedBadges.length > 0 && (
505+
<div className='flex flex-wrap justify-center gap-2'>
506+
{earnedBadges.slice(0, 6).map((badgeId, index) => (
507+
<div key={index} className='w-6 h-6'>
508+
<BadgeEmblem
509+
id={badgeId as string}
510+
size='sm'
511+
className='w-full h-full'
512+
/>
513+
</div>
514+
))}
515+
{earnedBadges.length > 6 && (
516+
<div className='w-6 h-6 bg-blue-400 rounded-full flex items-center justify-center'>
517+
<span className='text-xs font-bold text-white'>+{earnedBadges.length - 6}</span>
518+
</div>
519+
)}
520+
</div>
521+
)}
573522
</div>
574523
)}
575524

@@ -605,8 +554,8 @@ export const PokemonReferralCard: React.FC<PokemonReferralCardProps> = ({
605554
<br/>
606555
<br/>
607556

608-
{/* Earned Badges */}
609-
{earnedBadges.length > 0 && (
557+
{/* Earned Badges - Only for Classic and Minimal layouts */}
558+
{currentLayout.id !== 1 && earnedBadges.length > 0 && (
610559
<div className='relative z-10 px-6 mb-4'>
611560
<div className='bg-black/30 backdrop-blur-sm rounded-lg p-3 border border-white/30'>
612561
{/* <div
@@ -638,29 +587,31 @@ export const PokemonReferralCard: React.FC<PokemonReferralCardProps> = ({
638587
</div>
639588
)}
640589

641-
{/* Stats and Referral Code */}
642-
<div className='relative z-10 px-6 mb-4 space-y-3'>
643-
{/* Stats */}
644-
<div className='bg-black/30 backdrop-blur-sm rounded-lg p-3 border border-white/30'>
645-
<div
646-
className='grid grid-cols-3 gap-2 text-sm'
647-
style={{ color: currentTheme.text }}
648-
>
649-
<div className='text-center'>
650-
<div className='font-bold text-lg'>{account.totalPoints || 0}</div>
651-
<div className='text-xs opacity-80'>Points</div>
652-
</div>
653-
<div className='text-center'>
654-
<div className='font-bold text-lg'>{account.experience || 0}</div>
655-
<div className='text-xs opacity-80'>Experience</div>
656-
</div>
657-
<div className='text-center'>
658-
<div className='font-bold text-lg'>{earnedBadges.length}</div>
659-
<div className='text-xs opacity-80'>Badges</div>
590+
{/* Stats and Referral Code - Only for Classic and Minimal layouts */}
591+
{currentLayout.id !== 1 && (
592+
<div className='relative z-10 px-6 mb-4 space-y-3'>
593+
{/* Stats */}
594+
<div className='bg-black/30 backdrop-blur-sm rounded-lg p-3 border border-white/30'>
595+
<div
596+
className='grid grid-cols-3 gap-2 text-sm'
597+
style={{ color: currentTheme.text }}
598+
>
599+
<div className='text-center'>
600+
<div className='font-bold text-lg'>{account.totalPoints || 0}</div>
601+
<div className='text-xs opacity-80'>Points</div>
602+
</div>
603+
<div className='text-center'>
604+
<div className='font-bold text-lg'>{account.experience || 0}</div>
605+
<div className='text-xs opacity-80'>Experience</div>
606+
</div>
607+
<div className='text-center'>
608+
<div className='font-bold text-lg'>{earnedBadges.length}</div>
609+
<div className='text-xs opacity-80'>Badges</div>
610+
</div>
660611
</div>
661612
</div>
662613
</div>
663-
</div>
614+
)}
664615

665616
<br/>
666617

@@ -904,37 +855,6 @@ export const PokemonReferralCard: React.FC<PokemonReferralCardProps> = ({
904855
)}
905856
</button>
906857

907-
{/* Download GIF Button */}
908-
<button
909-
onClick={handleDownloadGif}
910-
disabled={isGeneratingGif}
911-
className='bg-gradient-to-r from-purple-500 to-pink-500 hover:from-purple-600 hover:to-pink-600 text-white font-bold py-3 px-6 rounded-xl transition-all duration-300 transform hover:scale-105 shadow-lg hover:shadow-xl disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none flex items-center justify-center space-x-2 w-full'
912-
>
913-
{isGeneratingGif ? (
914-
<>
915-
<div className='animate-spin rounded-full h-4 w-4 border-b-2 border-white'></div>
916-
<span>Creating GIF...</span>
917-
<div className='animate-pulse text-xs ml-2'>
918-
({Math.round(gifProgress)}%)
919-
</div>
920-
</>
921-
) : (
922-
<>
923-
<span>🎭</span>
924-
<span>Download GIF</span>
925-
</>
926-
)}
927-
</button>
928-
929-
{/* GIF Progress Bar */}
930-
{isGeneratingGif && (
931-
<div className='w-full bg-gray-700 rounded-full h-2 overflow-hidden'>
932-
<div
933-
className='bg-gradient-to-r from-purple-500 to-pink-500 h-2 rounded-full transition-all duration-300 ease-out'
934-
style={{ width: `${gifProgress}%` }}
935-
/>
936-
</div>
937-
)}
938858
</div>
939859
</div>
940860
);

0 commit comments

Comments
 (0)