Skip to content

Commit 8845117

Browse files
Improve AI chat UI UX
Implements loading state, responsive AI chat, and official AI Tor avatar - Replace brain icon with Ai_Tor avatar for chat button - Fullscreen chat on mobile with improved overlay and responsive sizing - Add loading overlay with avatar, spinner, and typing indicators - Update chat header to AI Assistant with AI Tor label - Add proactive and typing indicators, and manage iframe load/error states - Integrate NFTGallery, PromoBanner, NewsletterSubscription, AdBanner scaffolding - Update Contact page social links and DAODashboard visuals - General UI tweaks for readability and responsiveness X-Lovable-Edit-ID: edt-9bd0bb65-f148-482b-b194-1ae16896154d
2 parents 0f1a5c9 + 7453ab2 commit 8845117

9 files changed

Lines changed: 615 additions & 33 deletions

File tree

src/components/AIChatbot.tsx

Lines changed: 95 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,64 @@
1-
import React from 'react';
1+
import React, { useEffect, useCallback } from 'react';
22
import { motion, AnimatePresence } from 'framer-motion';
33
import { X, Loader2 } from 'lucide-react';
44
import { toast } from 'sonner';
55
import aiTorAvatar from '@/assets/ai-tor-avatar.jpg';
66

7+
const PROACTIVE_MESSAGES = [
8+
"¿Puedo ayudarte a encontrar algo?",
9+
"¿Tienes alguna pregunta sobre AlienFlowSpace?",
10+
"¿Necesitas ayuda con los NFTs o las DAOs?",
11+
"¿Te gustaría conocer más sobre nuestro ecosistema?",
12+
"¿En qué puedo asistirte hoy?"
13+
];
14+
715
const AIChatbot = () => {
816
const [isOpen, setIsOpen] = React.useState(false);
917
const [isLoading, setIsLoading] = React.useState(true);
18+
const [showProactive, setShowProactive] = React.useState(false);
19+
const [proactiveMessage, setProactiveMessage] = React.useState('');
20+
const [hasInteracted, setHasInteracted] = React.useState(false);
21+
22+
// Proactive engagement - show after 45 seconds of inactivity
23+
useEffect(() => {
24+
if (hasInteracted || isOpen) return;
25+
26+
const timer = setTimeout(() => {
27+
const randomMessage = PROACTIVE_MESSAGES[Math.floor(Math.random() * PROACTIVE_MESSAGES.length)];
28+
setProactiveMessage(randomMessage);
29+
setShowProactive(true);
30+
31+
// Auto-hide after 8 seconds
32+
setTimeout(() => setShowProactive(false), 8000);
33+
}, 45000);
34+
35+
return () => clearTimeout(timer);
36+
}, [hasInteracted, isOpen]);
1037

11-
const handleOpen = () => {
38+
// Track user interaction
39+
useEffect(() => {
40+
const handleInteraction = () => setHasInteracted(true);
41+
window.addEventListener('click', handleInteraction, { once: true });
42+
window.addEventListener('scroll', handleInteraction, { once: true });
43+
return () => {
44+
window.removeEventListener('click', handleInteraction);
45+
window.removeEventListener('scroll', handleInteraction);
46+
};
47+
}, []);
48+
49+
const handleOpen = useCallback(() => {
1250
setIsOpen(true);
1351
setIsLoading(true);
52+
setShowProactive(false);
53+
setHasInteracted(true);
1454
toast.info('AI Assistant Loading', {
1555
description: 'Opening AI Tor Assistant...'
1656
});
17-
};
57+
}, []);
1858

19-
const handleIframeLoad = () => {
59+
const handleIframeLoad = useCallback(() => {
2060
setIsLoading(false);
21-
};
61+
}, []);
2262

2363
return (
2464
<>
@@ -35,6 +75,35 @@ const AIChatbot = () => {
3575
)}
3676
</AnimatePresence>
3777

78+
{/* Proactive Engagement Bubble */}
79+
<AnimatePresence>
80+
{showProactive && !isOpen && (
81+
<motion.div
82+
initial={{ opacity: 0, x: 20, scale: 0.8 }}
83+
animate={{ opacity: 1, x: 0, scale: 1 }}
84+
exit={{ opacity: 0, x: 20, scale: 0.8 }}
85+
className="fixed bottom-36 right-4 sm:bottom-40 sm:right-8 z-40
86+
max-w-[250px] p-3 rounded-xl rounded-br-sm
87+
bg-gradient-to-br from-alien-gold/90 to-alien-gold-dark/90
88+
backdrop-blur-md border border-alien-gold-light/50
89+
shadow-lg shadow-alien-gold/20 cursor-pointer"
90+
onClick={handleOpen}
91+
>
92+
<p className="text-alien-space-dark font-exo text-sm font-medium">
93+
{proactiveMessage}
94+
</p>
95+
<button
96+
onClick={(e) => { e.stopPropagation(); setShowProactive(false); }}
97+
className="absolute -top-2 -right-2 w-5 h-5 bg-alien-space-dark rounded-full
98+
flex items-center justify-center border border-alien-gold/50
99+
hover:bg-alien-gold/20 transition-colors"
100+
>
101+
<X className="w-3 h-3 text-alien-gold" />
102+
</button>
103+
</motion.div>
104+
)}
105+
</AnimatePresence>
106+
38107
{/* Floating Button */}
39108
<motion.button
40109
initial={{ opacity: 0, scale: 0.5 }}
@@ -56,7 +125,7 @@ const AIChatbot = () => {
56125
/>
57126
</motion.button>
58127

59-
{/* Chat Window - Fullscreen on mobile */}
128+
{/* Chat Window - Fullscreen on mobile, optimized size on desktop */}
60129
<AnimatePresence>
61130
{isOpen && (
62131
<motion.div
@@ -67,8 +136,8 @@ const AIChatbot = () => {
67136
className="fixed z-50
68137
inset-0 sm:inset-auto
69138
sm:bottom-24 sm:right-8
70-
sm:w-[380px] md:w-[420px]
71-
sm:h-[550px] md:h-[600px]
139+
sm:w-[360px] md:w-[380px]
140+
sm:h-[480px] md:h-[520px]
72141
sm:max-h-[calc(100vh-8rem)]
73142
bg-alien-space-dark/98 backdrop-blur-xl
74143
sm:border-2 border-alien-gold/40 sm:rounded-2xl
@@ -91,10 +160,20 @@ const AIChatbot = () => {
91160
</h3>
92161
<p className="text-xs text-alien-green font-exo">AI Tor</p>
93162
</div>
94-
{/* Status indicator */}
163+
{/* Status indicator with typing animation when loading */}
95164
<div className="flex items-center gap-1.5 ml-2">
96-
<span className="w-2 h-2 bg-alien-green rounded-full animate-pulse" />
97-
<span className="text-xs text-muted-foreground hidden sm:inline">Online</span>
165+
{isLoading ? (
166+
<div className="flex items-center gap-1">
167+
<span className="typing-dot" />
168+
<span className="typing-dot" style={{ animationDelay: '0.2s' }} />
169+
<span className="typing-dot" style={{ animationDelay: '0.4s' }} />
170+
</div>
171+
) : (
172+
<>
173+
<span className="w-2 h-2 bg-alien-green rounded-full animate-pulse" />
174+
<span className="text-xs text-muted-foreground hidden sm:inline">Online</span>
175+
</>
176+
)}
98177
</div>
99178
</div>
100179
<button
@@ -127,10 +206,11 @@ const AIChatbot = () => {
127206
<Loader2 className="absolute -bottom-1 -right-1 h-8 w-8 text-alien-gold animate-spin" />
128207
</div>
129208
<p className="text-alien-gold font-nasalization text-sm mt-4">Loading AI Tor...</p>
130-
<div className="flex gap-1 mt-3">
131-
<span className="w-2 h-2 bg-alien-gold rounded-full animate-bounce" style={{ animationDelay: '0ms' }} />
132-
<span className="w-2 h-2 bg-alien-gold rounded-full animate-bounce" style={{ animationDelay: '150ms' }} />
133-
<span className="w-2 h-2 bg-alien-gold rounded-full animate-bounce" style={{ animationDelay: '300ms' }} />
209+
{/* Typing indicator animation */}
210+
<div className="flex gap-1.5 mt-3">
211+
<span className="typing-dot-lg" />
212+
<span className="typing-dot-lg" style={{ animationDelay: '0.15s' }} />
213+
<span className="typing-dot-lg" style={{ animationDelay: '0.3s' }} />
134214
</div>
135215
</motion.div>
136216
)}

src/components/AdBanner.tsx

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import React from 'react';
2+
import { motion } from 'framer-motion';
3+
4+
interface AdBannerProps {
5+
position: 'horizontal' | 'vertical' | 'square';
6+
className?: string;
7+
adSlotId?: string;
8+
}
9+
10+
const AD_SIZES = {
11+
horizontal: { width: 728, height: 90, label: '728x90' },
12+
vertical: { width: 160, height: 600, label: '160x600' },
13+
square: { width: 300, height: 250, label: '300x250' }
14+
};
15+
16+
const AdBanner: React.FC<AdBannerProps> = ({
17+
position = 'horizontal',
18+
className = '',
19+
adSlotId
20+
}) => {
21+
const size = AD_SIZES[position];
22+
23+
// Placeholder for Google AdSense or other ad network
24+
// Replace with actual ad code when ad account is set up
25+
return (
26+
<motion.div
27+
initial={{ opacity: 0 }}
28+
whileInView={{ opacity: 1 }}
29+
viewport={{ once: true }}
30+
className={`flex items-center justify-center ${className}`}
31+
>
32+
<div
33+
className="bg-alien-space-dark/50 border border-alien-gold/20 rounded-lg
34+
flex items-center justify-center text-muted-foreground/50 font-exo text-xs
35+
hover:border-alien-gold/40 transition-colors"
36+
style={{
37+
width: position === 'horizontal' ? '100%' : size.width,
38+
maxWidth: size.width,
39+
height: size.height,
40+
minHeight: size.height
41+
}}
42+
>
43+
{adSlotId ? (
44+
// Placeholder for actual ad script
45+
<div
46+
id={adSlotId}
47+
className="w-full h-full flex items-center justify-center"
48+
>
49+
{/* Google AdSense script would go here */}
50+
<span className="text-center">
51+
Ad Space<br/>
52+
<span className="text-alien-gold/50">{size.label}</span>
53+
</span>
54+
</div>
55+
) : (
56+
<span className="text-center">
57+
Ad Space<br/>
58+
<span className="text-alien-gold/50">{size.label}</span>
59+
</span>
60+
)}
61+
</div>
62+
</motion.div>
63+
);
64+
};
65+
66+
export default AdBanner;

src/components/DAODashboard.tsx

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { useState, useEffect } from 'react';
22
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
33
import { Button } from '@/components/ui/button';
4-
import { Users, TrendingUp, FileText, Clock, CheckCircle, XCircle, RefreshCw } from 'lucide-react';
4+
import { Users, TrendingUp, FileText, Clock, CheckCircle, XCircle, RefreshCw, ExternalLink } from 'lucide-react';
55
import { PieChart, Pie, Cell, ResponsiveContainer, BarChart, Bar, XAxis, YAxis, Tooltip, Legend } from 'recharts';
66

77
type Proposal = {
@@ -16,6 +16,22 @@ type Proposal = {
1616
const DAODashboard: React.FC = () => {
1717
const [lastUpdate, setLastUpdate] = useState(new Date());
1818

19+
// AlienFlowSpace DAOs on Polygon
20+
const DAOS = [
21+
{
22+
name: 'AlienFlowSpace DAO',
23+
address: '0xCA497d631DB260ebFFF4bA71AEAc3201ae972a77',
24+
url: 'https://app.aragon.org/dao/polygon-mainnet/0xCA497d631DB260ebFFF4bA71AEAc3201ae972a77/dashboard',
25+
token: '$AFS'
26+
},
27+
{
28+
name: 'Alien69Flow DAO',
29+
address: '0x2A1F32A807b3f8a43F9473C1FA7d11881A579b16',
30+
url: 'https://app.aragon.org/dao/polygon-mainnet/0x2A1F32A807b3f8a43F9473C1FA7d11881A579b16/dashboard',
31+
token: '$A69F'
32+
}
33+
];
34+
1935
// Mock data - In production, fetch from blockchain
2036
const [stats] = useState({
2137
activeVoters: 1618033,
@@ -25,10 +41,10 @@ const DAODashboard: React.FC = () => {
2541
});
2642

2743
const [treasury] = useState([
28-
{ name: 'BTC', value: 123456789, change: 3.3, color: '#F7931A' },
29-
{ name: 'ETH', value: 33456789, change: -1.2, color: '#627EEA' },
30-
{ name: 'USDC', value: 850000, change: 0, color: '#2775CA' },
31-
{ name: 'Other', value: 95000, change: 2.3, color: '#22C55E' }
44+
{ name: 'MATIC', value: 50000, change: 2.5, color: '#8247E5' },
45+
{ name: '$AFS', value: 1000000, change: 5.2, color: '#22C55E' },
46+
{ name: '$A69F', value: 500000, change: 3.8, color: '#F0D882' },
47+
{ name: 'USDC', value: 25000, change: 0, color: '#2775CA' }
3248
]);
3349

3450
const [proposals] = useState<Proposal[]>([
@@ -82,13 +98,27 @@ const DAODashboard: React.FC = () => {
8298
Last updated: {lastUpdate.toLocaleTimeString()}
8399
</p>
84100
</div>
85-
<Button
86-
onClick={handleRefresh}
87-
className="bg-alien-gold/20 hover:bg-alien-gold/30 text-alien-gold border border-alien-gold/50"
88-
>
89-
<RefreshCw className="h-4 w-4 mr-2" />
90-
Refresh Data
91-
</Button>
101+
<div className="flex gap-2 flex-wrap">
102+
{DAOS.map((dao) => (
103+
<Button
104+
key={dao.address}
105+
onClick={() => window.open(dao.url, '_blank')}
106+
className="bg-alien-gold/20 hover:bg-alien-gold/30 text-alien-gold border border-alien-gold/50 text-xs"
107+
size="sm"
108+
>
109+
<ExternalLink className="h-3 w-3 mr-1" />
110+
{dao.name}
111+
</Button>
112+
))}
113+
<Button
114+
onClick={handleRefresh}
115+
className="bg-alien-green/20 hover:bg-alien-green/30 text-alien-green border border-alien-green/50"
116+
size="sm"
117+
>
118+
<RefreshCw className="h-4 w-4 mr-2" />
119+
Refresh
120+
</Button>
121+
</div>
92122
</div>
93123

94124
{/* Stats Grid */}

0 commit comments

Comments
 (0)