Add src/components/sections/animated-chat-demo/AnimatedChatDemo.tsx
This commit is contained in:
282
src/components/sections/animated-chat-demo/AnimatedChatDemo.tsx
Normal file
282
src/components/sections/animated-chat-demo/AnimatedChatDemo.tsx
Normal file
@@ -0,0 +1,282 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Send } from "lucide-react";
|
||||
|
||||
interface ChatMessage {
|
||||
id: string;
|
||||
text: string;
|
||||
isUser: boolean;
|
||||
isComplete?: boolean;
|
||||
}
|
||||
|
||||
interface AppIdea {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
const appIdeas: AppIdea[] = [
|
||||
{
|
||||
id: "1", title: "Weather Dashboard", description: "A beautiful real-time weather app with hourly forecasts and weather alerts"
|
||||
},
|
||||
{
|
||||
id: "2", title: "Todo Manager", description: "A productivity app with task organization, categories, and smart reminders"
|
||||
},
|
||||
{
|
||||
id: "3", title: "Budget Tracker", description: "Personal finance app with spending analytics and budget goals tracking"
|
||||
}
|
||||
];
|
||||
|
||||
export default function AnimatedChatDemo() {
|
||||
const [stage, setStage] = useState<"chat" | "process" | "finished">("chat");
|
||||
const [currentIdeaIndex, setCurrentIdeaIndex] = useState(0);
|
||||
const [messages, setMessages] = useState<ChatMessage[]>([]);
|
||||
const [processSteps, setProcessSteps] = useState<Array<{ name: string; active: boolean }>>([]);
|
||||
const [finishedApp, setFinishedApp] = useState<AppIdea | null>(null);
|
||||
const [isAutoPlaying, setIsAutoPlaying] = useState(true);
|
||||
|
||||
// Stage 1: Chat simulation with auto-filling and sending
|
||||
useEffect(() => {
|
||||
if (stage !== "chat" || !isAutoPlaying) return;
|
||||
|
||||
let timeoutId: NodeJS.Timeout;
|
||||
const currentIdea = appIdeas[currentIdeaIndex];
|
||||
|
||||
// Clear messages and start fresh
|
||||
if (messages.length === 0) {
|
||||
timeoutId = setTimeout(() => {
|
||||
// Add user message character by character
|
||||
const userMessage = currentIdea.description;
|
||||
let charIndex = 0;
|
||||
const typeInterval = setInterval(() => {
|
||||
if (charIndex <= userMessage.length) {
|
||||
setMessages((prev) => {
|
||||
if (prev.length === 0 || !prev[prev.length - 1].isUser) {
|
||||
return [
|
||||
...prev,
|
||||
{
|
||||
id: `user-${Date.now()}`,
|
||||
text: userMessage.substring(0, charIndex),
|
||||
isUser: true,
|
||||
isComplete: false
|
||||
}
|
||||
];
|
||||
} else {
|
||||
const updated = [...prev];
|
||||
updated[updated.length - 1].text = userMessage.substring(0, charIndex);
|
||||
return updated;
|
||||
}
|
||||
});
|
||||
charIndex++;
|
||||
} else {
|
||||
clearInterval(typeInterval);
|
||||
// Mark message as complete and move to next stage
|
||||
setTimeout(() => {
|
||||
setMessages((prev) => {
|
||||
const updated = [...prev];
|
||||
if (updated.length > 0) {
|
||||
updated[updated.length - 1].isComplete = true;
|
||||
}
|
||||
return updated;
|
||||
});
|
||||
// Auto-send and transition to process
|
||||
setTimeout(() => {
|
||||
setStage("process");
|
||||
}, 800);
|
||||
}, 500);
|
||||
}
|
||||
}, 30);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
return () => clearTimeout(timeoutId);
|
||||
}, [stage, currentIdeaIndex, messages.length, isAutoPlaying]);
|
||||
|
||||
// Stage 2: Process flow animation
|
||||
useEffect(() => {
|
||||
if (stage !== "process" || !isAutoPlaying) return;
|
||||
|
||||
const steps = [
|
||||
{ name: "Design", active: false },
|
||||
{ name: "Planning", active: false },
|
||||
{ name: "Coding", active: false }
|
||||
];
|
||||
setProcessSteps(steps);
|
||||
|
||||
let stepIndex = 0;
|
||||
const stepInterval = setInterval(() => {
|
||||
if (stepIndex < steps.length) {
|
||||
setProcessSteps((prev) =>
|
||||
prev.map((step, idx) => ({
|
||||
...step,
|
||||
active: idx === stepIndex
|
||||
}))
|
||||
);
|
||||
stepIndex++;
|
||||
} else {
|
||||
clearInterval(stepInterval);
|
||||
// Transition to finished
|
||||
setTimeout(() => {
|
||||
setStage("finished");
|
||||
setFinishedApp(appIdeas[currentIdeaIndex]);
|
||||
}, 1000);
|
||||
}
|
||||
}, 1200);
|
||||
|
||||
return () => clearInterval(stepInterval);
|
||||
}, [stage, currentIdeaIndex, isAutoPlaying]);
|
||||
|
||||
// Stage 3: Finished app display and loop
|
||||
useEffect(() => {
|
||||
if (stage !== "finished" || !isAutoPlaying) return;
|
||||
|
||||
const loopTimeout = setTimeout(() => {
|
||||
const nextIndex = (currentIdeaIndex + 1) % appIdeas.length;
|
||||
setCurrentIdeaIndex(nextIndex);
|
||||
setStage("chat");
|
||||
setMessages([]);
|
||||
setProcessSteps([]);
|
||||
setFinishedApp(null);
|
||||
}, 4000);
|
||||
|
||||
return () => clearTimeout(loopTimeout);
|
||||
}, [stage, currentIdeaIndex, isAutoPlaying]);
|
||||
|
||||
return (
|
||||
<div className="w-full py-20 px-4 sm:px-6 lg:px-8 bg-gradient-to-b from-background via-background to-background-accent/10">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
{/* Section Header */}
|
||||
<div className="text-center mb-16 opacity-0 animate-in fade-in slide-in-from-bottom-4 duration-700">
|
||||
<p className="text-sm font-semibold text-primary-cta mb-2">INTERACTIVE DEMO</p>
|
||||
<h2 className="text-3xl sm:text-4xl md:text-5xl font-bold mb-4">See It In Action</h2>
|
||||
<p className="text-base sm:text-lg text-foreground/70 max-w-2xl mx-auto">
|
||||
Watch how Native Line transforms your ideas into complete apps in real-time
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Main Demo Container */}
|
||||
<div className="relative">
|
||||
{/* Chat Stage */}
|
||||
{stage === "chat" && (
|
||||
<div className="animate-in fade-in duration-500">
|
||||
<div className="bg-card border border-accent/20 rounded-lg p-6 sm:p-8 shadow-lg">
|
||||
<div className="space-y-4 mb-6 h-40 flex flex-col justify-center">
|
||||
{messages.map((msg) => (
|
||||
<div
|
||||
key={msg.id}
|
||||
className={`flex ${msg.isUser ? "justify-end" : "justify-start"} animate-in fade-in slide-in-from-bottom-2 duration-300`}
|
||||
>
|
||||
<div
|
||||
className={`px-4 py-2 rounded-lg max-w-xs ${
|
||||
msg.isUser
|
||||
? "bg-primary-cta text-white"
|
||||
: "bg-background text-foreground border border-accent/30"
|
||||
}`}
|
||||
>
|
||||
<p className="text-sm">{msg.text}</p>
|
||||
{msg.isComplete && msg.isUser && (
|
||||
<div className="mt-2 flex justify-end">
|
||||
<Send className="w-4 h-4" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Process Flow Stage */}
|
||||
{stage === "process" && (
|
||||
<div className="animate-in fade-in duration-500">
|
||||
<div className="flex justify-center items-center gap-2 sm:gap-4 h-40 flex-wrap">
|
||||
{processSteps.map((step, idx) => (
|
||||
<div key={idx} className="flex items-center">
|
||||
<div
|
||||
className={`w-16 sm:w-20 h-16 sm:h-20 rounded-full flex items-center justify-center text-sm font-semibold transition-all duration-500 ${
|
||||
step.active
|
||||
? "bg-primary-cta text-white scale-110 shadow-lg animate-pulse"
|
||||
: "bg-background border-2 border-accent/30 text-foreground"
|
||||
}`}
|
||||
>
|
||||
{step.name}
|
||||
</div>
|
||||
{idx < processSteps.length - 1 && (
|
||||
<div
|
||||
className={`w-8 sm:w-12 h-1 mx-1 sm:mx-2 transition-all duration-500 ${
|
||||
processSteps[idx + 1].active ? "bg-primary-cta" : "bg-accent/20"
|
||||
}`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Finished App Stage */}
|
||||
{stage === "finished" && finishedApp && (
|
||||
<div className="animate-in fade-in duration-700">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 items-center">
|
||||
{/* App Image - Right */}
|
||||
<div className="order-2 md:order-2 h-64 sm:h-80 bg-gradient-to-br from-primary-cta/10 to-accent/10 rounded-lg border border-accent/30 flex items-center justify-center animate-in fade-in slide-in-from-right-4 duration-700">
|
||||
<div className="text-center">
|
||||
<div className="w-20 h-20 sm:w-24 sm:h-24 bg-primary-cta/20 rounded-full mx-auto mb-4 flex items-center justify-center">
|
||||
<div className="w-16 h-16 sm:w-20 sm:h-20 bg-primary-cta rounded-full" />
|
||||
</div>
|
||||
<p className="text-sm text-foreground/50">App Preview</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* App Details - Left */}
|
||||
<div className="order-1 md:order-1 animate-in fade-in slide-in-from-left-4 duration-700 space-y-4">
|
||||
<div>
|
||||
<h3 className="text-2xl sm:text-3xl font-bold mb-2">{finishedApp.title}</h3>
|
||||
<p className="text-foreground/70 text-sm sm:text-base">{finishedApp.description}</p>
|
||||
</div>
|
||||
<div className="space-y-2 pt-4">
|
||||
<p className="text-xs sm:text-sm font-semibold text-primary-cta">✓ Production-Ready Code</p>
|
||||
<p className="text-xs sm:text-sm font-semibold text-primary-cta">✓ Ready to Deploy</p>
|
||||
<p className="text-xs sm:text-sm font-semibold text-primary-cta">✓ Fully Yours to Modify</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Loop Indicator */}
|
||||
<div className="mt-12 flex justify-center gap-2">
|
||||
{appIdeas.map((idea, idx) => (
|
||||
<button
|
||||
key={idea.id}
|
||||
onClick={() => {
|
||||
setCurrentIdeaIndex(idx);
|
||||
setStage("chat");
|
||||
setMessages([]);
|
||||
setProcessSteps([]);
|
||||
setFinishedApp(null);
|
||||
}}
|
||||
className={`w-2 h-2 rounded-full transition-all duration-300 ${
|
||||
idx === currentIdeaIndex ? "bg-primary-cta w-8" : "bg-accent/30"
|
||||
}`}
|
||||
aria-label={`Show ${idea.title}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Auto-play Toggle */}
|
||||
<div className="mt-8 flex justify-center">
|
||||
<button
|
||||
onClick={() => setIsAutoPlaying(!isAutoPlaying)}
|
||||
className="px-4 py-2 text-sm rounded-full border border-accent/30 hover:bg-card transition-colors"
|
||||
>
|
||||
{isAutoPlaying ? "Pause" : "Play"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user