Skip to content

Commit 163d4b1

Browse files
UFO menu and contact full
Refactor UFO navigation with CompactPriceTicker, enhance DesktopNav, overhaul AI chatbot promo, restore contact form with validation, and add edge function for sending emails. Also remove PromoBanner and integrate promo into AI. X-Lovable-Edit-ID: edt-95bd8b3a-7b6c-49b1-a815-db855bdb6243
2 parents ddeca66 + ec52160 commit 163d4b1

7 files changed

Lines changed: 903 additions & 390 deletions

File tree

src/components/AIChatbot.tsx

Lines changed: 68 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import React, { useEffect, useCallback, useState, useMemo } from 'react';
22
import { motion, AnimatePresence } from 'framer-motion';
3-
import { X, Loader2, Shield, Zap, HeadphonesIcon, Bot } from 'lucide-react';
3+
import { X, Loader2, Shield, Zap, HeadphonesIcon, Bot, Sparkles, Rocket } from 'lucide-react';
44
import { toast } from 'sonner';
55
import aiTorAvatar from '@/assets/ai-tor-avatar.jpg';
66

77
// AI Personalities with unique messages and slight visual variations
8+
// Now includes promotional messages integrated into personalities
89
const AI_PERSONALITIES = [
910
{
1011
id: 'support',
@@ -61,6 +62,34 @@ const AI_PERSONALITIES = [
6162
"¿Buscas algo específico en el ecosistema?",
6263
"Déjame mostrarte lo que puedes hacer aquí.",
6364
]
65+
},
66+
{
67+
id: 'promo',
68+
name: 'AI Tor',
69+
role: 'Promociones',
70+
icon: Sparkles,
71+
color: 'from-pink-500 to-rose-500',
72+
borderColor: 'border-pink-500/50',
73+
messages: [
74+
"🚀 ¡Únete a AlienFlowSpace DAO y obtén NFTs exclusivos!",
75+
"🎁 Descubre los beneficios de ser miembro de la DAO",
76+
"💎 Colecciona NFTs únicos en nuestro ecosistema",
77+
"🌟 ¡Nuevas propuestas en la DAO! Tu voto cuenta",
78+
"🔥 Explora Academy, Clubs y CoNetWorKing - ¡Todo te espera!",
79+
]
80+
},
81+
{
82+
id: 'welcome',
83+
name: 'AI Tor',
84+
role: 'Bienvenida',
85+
icon: Rocket,
86+
color: 'from-orange-500 to-amber-500',
87+
borderColor: 'border-orange-500/50',
88+
messages: [
89+
"👽 ¡Bienvenido viajero! ¿Primera vez aquí? Te ayudo a explorar",
90+
"🌌 El universo AlienFlowSpace te espera. ¿Qué quieres descubrir?",
91+
"✨ ¿Listo para tu viaje cósmico? Pregúntame lo que quieras",
92+
]
6493
}
6594
];
6695

@@ -72,6 +101,7 @@ const AIChatbot = () => {
72101
const [shouldLoadIframe, setShouldLoadIframe] = useState(false);
73102
const [currentPersonality, setCurrentPersonality] = useState(0);
74103
const [proactiveMessage, setProactiveMessage] = useState('');
104+
const [messageCount, setMessageCount] = useState(0);
75105

76106
// Get current personality
77107
const personality = useMemo(() => AI_PERSONALITIES[currentPersonality], [currentPersonality]);
@@ -85,10 +115,13 @@ const AIChatbot = () => {
85115
return () => clearTimeout(timer);
86116
}, []);
87117

88-
// Proactive engagement with rotating personalities
118+
// Proactive engagement with rotating personalities - shorter initial delay
89119
useEffect(() => {
90120
if (hasInteracted || isOpen) return;
91121

122+
// First message after 30 seconds
123+
const initialDelay = messageCount === 0 ? 30000 : 120000; // 30s first, then 2min
124+
92125
const timer = setTimeout(() => {
93126
// Pick random personality and message
94127
const randomPersonalityIndex = Math.floor(Math.random() * AI_PERSONALITIES.length);
@@ -98,21 +131,31 @@ const AIChatbot = () => {
98131
setCurrentPersonality(randomPersonalityIndex);
99132
setProactiveMessage(randomMessage);
100133
setShowProactive(true);
134+
setMessageCount(prev => prev + 1);
101135

102-
// Auto-hide after 10 seconds
103-
setTimeout(() => setShowProactive(false), 10000);
104-
}, 45000);
136+
// Auto-hide after 12 seconds
137+
setTimeout(() => setShowProactive(false), 12000);
138+
}, initialDelay);
105139

106140
return () => clearTimeout(timer);
107-
}, [hasInteracted, isOpen]);
141+
}, [hasInteracted, isOpen, messageCount]);
108142

109-
// Track user interaction
143+
// Track user interaction - only on meaningful interactions
110144
useEffect(() => {
111-
const handleInteraction = () => setHasInteracted(true);
112-
window.addEventListener('click', handleInteraction, { once: true });
113-
window.addEventListener('scroll', handleInteraction, { once: true });
145+
const handleInteraction = () => {
146+
// Don't mark as interacted if just scrolling slightly
147+
if (window.scrollY > 200) {
148+
setHasInteracted(true);
149+
}
150+
};
151+
152+
const handleClick = () => setHasInteracted(true);
153+
154+
window.addEventListener('click', handleClick, { once: true });
155+
window.addEventListener('scroll', handleInteraction);
156+
114157
return () => {
115-
window.removeEventListener('click', handleInteraction);
158+
window.removeEventListener('click', handleClick);
116159
window.removeEventListener('scroll', handleInteraction);
117160
};
118161
}, []);
@@ -144,19 +187,19 @@ const AIChatbot = () => {
144187
)}
145188
</AnimatePresence>
146189

147-
{/* Proactive Engagement Bubble with Personality */}
190+
{/* Proactive Engagement Bubble with Personality - FIXED: Right side */}
148191
<AnimatePresence>
149192
{showProactive && !isOpen && (
150193
<motion.div
151194
initial={{ opacity: 0, x: 20, scale: 0.8 }}
152195
animate={{ opacity: 1, x: 0, scale: 1 }}
153196
exit={{ opacity: 0, x: 20, scale: 0.8 }}
154197
className="fixed bottom-36 right-4 sm:bottom-40 sm:right-8 z-40
155-
max-w-[280px] rounded-2xl rounded-br-sm overflow-hidden
198+
max-w-[300px] rounded-2xl rounded-br-sm overflow-hidden
156199
backdrop-blur-xl border shadow-2xl cursor-pointer"
157200
style={{
158201
background: 'linear-gradient(135deg, rgba(17,17,25,0.95) 0%, rgba(30,30,40,0.95) 100%)',
159-
borderColor: `rgba(${personality.id === 'support' ? '57,255,20' : personality.id === 'dao' ? '240,216,130' : personality.id === 'sentinel' ? '168,85,247' : '34,211,238'},0.4)`
202+
borderColor: `rgba(${personality.id === 'support' ? '57,255,20' : personality.id === 'dao' ? '240,216,130' : personality.id === 'sentinel' ? '168,85,247' : personality.id === 'promo' ? '236,72,153' : personality.id === 'welcome' ? '249,115,22' : '34,211,238'},0.4)`
160203
}}
161204
onClick={handleOpen}
162205
>
@@ -175,7 +218,9 @@ const AIChatbot = () => {
175218
style={{
176219
filter: personality.id === 'sentinel' ? 'hue-rotate(270deg)' :
177220
personality.id === 'guide' ? 'hue-rotate(180deg)' :
178-
personality.id === 'dao' ? 'sepia(30%)' : 'none'
221+
personality.id === 'dao' ? 'sepia(30%)' :
222+
personality.id === 'promo' ? 'hue-rotate(320deg)' :
223+
personality.id === 'welcome' ? 'hue-rotate(30deg)' : 'none'
179224
}}
180225
/>
181226
</div>
@@ -185,6 +230,12 @@ const AIChatbot = () => {
185230
<p className="text-gray-200 font-exo text-sm leading-relaxed">
186231
{proactiveMessage}
187232
</p>
233+
{/* CTA for promo messages */}
234+
{personality.id === 'promo' && (
235+
<span className="inline-block mt-2 text-xs text-alien-gold font-nasalization hover:text-alien-green transition-colors">
236+
Descubre más →
237+
</span>
238+
)}
188239
</div>
189240
{/* Close button */}
190241
<button
@@ -199,7 +250,7 @@ const AIChatbot = () => {
199250
)}
200251
</AnimatePresence>
201252

202-
{/* Floating Button with pulsing ring */}
253+
{/* Floating Button - FIXED: Positioned at bottom RIGHT */}
203254
<motion.button
204255
initial={{ opacity: 0, scale: 0.5 }}
205256
animate={{ opacity: 1, scale: 1 }}
@@ -340,4 +391,4 @@ const AIChatbot = () => {
340391
);
341392
};
342393

343-
export default AIChatbot;
394+
export default AIChatbot;
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import React, { useEffect, useState } from 'react';
2+
import { TrendingUp, TrendingDown } from 'lucide-react';
3+
4+
interface CryptoPrice {
5+
id: string;
6+
symbol: string;
7+
price: number;
8+
change24h: number;
9+
}
10+
11+
const CRYPTO_IDS = ['bitcoin', 'ethereum', 'solana', 'polygon-ecosystem-token'];
12+
13+
const CompactPriceTicker: React.FC = () => {
14+
const [prices, setPrices] = useState<CryptoPrice[]>([]);
15+
const [currentIndex, setCurrentIndex] = useState(0);
16+
17+
useEffect(() => {
18+
const fetchPrices = async () => {
19+
try {
20+
const response = await fetch(
21+
`https://api.coingecko.com/api/v3/simple/price?ids=${CRYPTO_IDS.join(',')}&vs_currencies=usd&include_24hr_change=true`
22+
);
23+
const data = await response.json();
24+
25+
const symbolMap: Record<string, string> = {
26+
'bitcoin': 'BTC',
27+
'ethereum': 'ETH',
28+
'solana': 'SOL',
29+
'polygon-ecosystem-token': 'POL'
30+
};
31+
32+
const formattedPrices: CryptoPrice[] = CRYPTO_IDS.map(id => ({
33+
id,
34+
symbol: symbolMap[id] || id.toUpperCase(),
35+
price: data[id]?.usd || 0,
36+
change24h: data[id]?.usd_24h_change || 0
37+
}));
38+
39+
setPrices(formattedPrices);
40+
} catch (error) {
41+
console.error('Error fetching crypto prices:', error);
42+
}
43+
};
44+
45+
fetchPrices();
46+
const interval = setInterval(fetchPrices, 60000); // Update every minute
47+
return () => clearInterval(interval);
48+
}, []);
49+
50+
// Rotate through cryptos
51+
useEffect(() => {
52+
const rotateInterval = setInterval(() => {
53+
setCurrentIndex(prev => (prev + 1) % prices.length);
54+
}, 4000);
55+
return () => clearInterval(rotateInterval);
56+
}, [prices.length]);
57+
58+
if (prices.length === 0) {
59+
return null;
60+
}
61+
62+
const currentCrypto = prices[currentIndex];
63+
const isPositive = currentCrypto?.change24h >= 0;
64+
65+
return (
66+
<div className="flex items-center gap-2 px-3 py-1.5 bg-alien-space-dark/60 rounded-full border border-alien-gold/20 text-xs font-mono">
67+
<span className="text-alien-gold font-semibold">{currentCrypto?.symbol}</span>
68+
<span className="text-gray-300">
69+
${currentCrypto?.price >= 1000
70+
? currentCrypto?.price.toLocaleString(undefined, { maximumFractionDigits: 0 })
71+
: currentCrypto?.price.toFixed(2)
72+
}
73+
</span>
74+
<span className={`flex items-center gap-0.5 ${isPositive ? 'text-alien-green' : 'text-red-400'}`}>
75+
{isPositive ? <TrendingUp className="w-3 h-3" /> : <TrendingDown className="w-3 h-3" />}
76+
{Math.abs(currentCrypto?.change24h || 0).toFixed(1)}%
77+
</span>
78+
</div>
79+
);
80+
};
81+
82+
export default CompactPriceTicker;

src/components/Header/DesktopNav.tsx

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
21
import React from 'react';
32
import { Link } from 'react-router-dom';
4-
import { Globe, ChevronDown } from 'lucide-react';
3+
import { Globe, ChevronDown, ArrowLeftRight } from 'lucide-react';
54
import {
65
DropdownMenu,
76
DropdownMenuContent,
@@ -11,7 +10,11 @@ import {
1110
import { Button } from "@/components/ui/button";
1211
import { translateTo } from '@/lib/translator';
1312

14-
const DesktopNav = () => {
13+
interface DesktopNavProps {
14+
isCompact?: boolean;
15+
}
16+
17+
const DesktopNav: React.FC<DesktopNavProps> = ({ isCompact = false }) => {
1518
const navLinks = [
1619
{ to: "/", label: "Home" },
1720
{ to: "/about", label: "About" },
@@ -40,14 +43,16 @@ const DesktopNav = () => {
4043
};
4144

4245
return (
43-
<nav className="hidden lg:flex items-center gap-8">
44-
<div className="flex items-center space-x-6">
46+
<nav className="hidden lg:flex items-center gap-6">
47+
<div className={`flex items-center ${isCompact ? 'space-x-4' : 'space-x-6'}`}>
4548
{/* Main Navigation */}
4649
{navLinks.map((link) => (
4750
<Link
4851
key={link.to}
4952
to={link.to}
50-
className="text-alien-gold hover:text-alien-green px-3 py-2 font-nasalization transition-all duration-300 hover:scale-105 relative group"
53+
className={`text-alien-gold hover:text-alien-green font-nasalization transition-all duration-300 hover:scale-105 relative group ${
54+
isCompact ? 'text-sm px-2 py-1' : 'px-3 py-2'
55+
}`}
5156
>
5257
{link.label}
5358
<span className="absolute inset-x-0 -bottom-1 h-0.5 bg-alien-green scale-x-0 group-hover:scale-x-100 transition-transform duration-300 origin-left"></span>
@@ -59,10 +64,12 @@ const DesktopNav = () => {
5964
<DropdownMenuTrigger asChild>
6065
<Button
6166
variant="ghost"
62-
className="text-alien-gold hover:text-alien-green bg-transparent hover:bg-alien-space-light/20 px-3 py-2 rounded-lg flex items-center group font-nasalization transition-all duration-300 hover:scale-105"
67+
className={`text-alien-gold hover:text-alien-green bg-transparent hover:bg-alien-space-light/20 rounded-lg flex items-center group font-nasalization transition-all duration-300 hover:scale-105 ${
68+
isCompact ? 'text-sm px-2 py-1' : 'px-3 py-2'
69+
}`}
6370
>
64-
<span>Explore Spaces</span>
65-
<ChevronDown className="ml-2 h-4 w-4 transform transition-transform duration-300 group-data-[state=open]:rotate-180" />
71+
<span>{isCompact ? 'Spaces' : 'Explore Spaces'}</span>
72+
<ChevronDown className={`ml-1 transform transition-transform duration-300 group-data-[state=open]:rotate-180 ${isCompact ? 'h-3 w-3' : 'h-4 w-4'}`} />
6673
</Button>
6774
</DropdownMenuTrigger>
6875
<DropdownMenuContent
@@ -88,16 +95,31 @@ const DesktopNav = () => {
8895
</div>
8996
</DropdownMenuContent>
9097
</DropdownMenu>
98+
99+
{/* DEX Button */}
100+
<a
101+
href="https://app.uniswap.org/swap"
102+
target="_blank"
103+
rel="noopener noreferrer"
104+
className={`flex items-center gap-1.5 bg-gradient-to-r from-alien-green/20 to-alien-gold/20 hover:from-alien-green/30 hover:to-alien-gold/30 border border-alien-green/40 hover:border-alien-green/60 rounded-full font-nasalization text-alien-green hover:text-alien-gold transition-all duration-300 hover:scale-105 ${
105+
isCompact ? 'text-xs px-2.5 py-1' : 'text-sm px-3 py-1.5'
106+
}`}
107+
>
108+
<ArrowLeftRight className={isCompact ? 'w-3 h-3' : 'w-4 h-4'} />
109+
<span>DEX</span>
110+
</a>
91111

92112
{/* Language Selector Dropdown */}
93113
<DropdownMenu>
94114
<DropdownMenuTrigger asChild>
95115
<Button
96116
variant="ghost"
97-
className="flex items-center text-alien-gold hover:text-alien-green focus:outline-none group p-2 hover:bg-alien-space-light/20 rounded-lg transition-all duration-300 hover:scale-105"
117+
className={`flex items-center text-alien-gold hover:text-alien-green focus:outline-none group hover:bg-alien-space-light/20 rounded-lg transition-all duration-300 hover:scale-105 ${
118+
isCompact ? 'p-1.5' : 'p-2'
119+
}`}
98120
>
99-
<Globe className="h-5 w-5" />
100-
<ChevronDown className="ml-1 h-4 w-4 transform transition-transform duration-300 group-data-[state=open]:rotate-180" />
121+
<Globe className={isCompact ? 'h-4 w-4' : 'h-5 w-5'} />
122+
<ChevronDown className={`ml-1 transform transition-transform duration-300 group-data-[state=open]:rotate-180 ${isCompact ? 'h-3 w-3' : 'h-4 w-4'}`} />
101123
</Button>
102124
</DropdownMenuTrigger>
103125
<DropdownMenuContent
@@ -128,4 +150,4 @@ const DesktopNav = () => {
128150
);
129151
};
130152

131-
export default DesktopNav;
153+
export default DesktopNav;

0 commit comments

Comments
 (0)