Files
fast-next-template/frontend/src/components/home/AnimatedTerminal.tsx
Felipe Cardoso d1b47006f4 Remove all obsolete authentication, settings, admin, and demo-related components and pages
- Eliminated redundant components, pages, and layouts related to authentication (`login`, `register`, `password-reset`, etc.), user settings, admin, and demos.
- Simplified the frontend structure by removing unused dynamic imports, forms, and test code.
- Refactored configurations and metadata imports to exclude references to removed features.
- Streamlined the project for future development and improved maintainability by discarding legacy and unused code.
2025-11-18 12:41:57 +01:00

147 lines
5.2 KiB
TypeScript

/**
* Animated Terminal
* Terminal with typing animation showing installation/setup commands
*/
'use client';
import { useEffect, useState, useRef } from 'react';
import { motion } from 'framer-motion';
import { Terminal, Play } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Link } from '@/lib/i18n/routing';
const commands = [
{ text: '# Clone the repository', delay: 0 },
{ text: '$ git clone https://github.com/your-org/fast-next-template.git', delay: 800 },
{ text: '$ cd fast-next-template', delay: 1600 },
{ text: '', delay: 2200 },
{ text: '# Start with Docker (one command)', delay: 2400 },
{ text: '$ docker-compose up', delay: 3200 },
{ text: '', delay: 4000 },
{ text: '✓ Backend running at http://localhost:8000', delay: 4200, isSuccess: true },
{ text: '✓ Frontend running at http://localhost:3000', delay: 4400, isSuccess: true },
{ text: '✓ Admin panel at http://localhost:3000/admin', delay: 4600, isSuccess: true },
{ text: '✓ API docs at http://localhost:8000/docs', delay: 4800, isSuccess: true },
];
export function AnimatedTerminal() {
const [displayedLines, setDisplayedLines] = useState<typeof commands>([]);
const [isAnimating, setIsAnimating] = useState(false);
const containerRef = useRef<HTMLDivElement>(null);
const hasAnimated = useRef(false);
useEffect(() => {
// Only animate once when component enters viewport
if (hasAnimated.current) return;
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting && !hasAnimated.current) {
hasAnimated.current = true;
setIsAnimating(true);
animateCommands();
}
},
{ threshold: 0.3 }
);
if (containerRef.current) {
observer.observe(containerRef.current);
}
return () => observer.disconnect();
}, []);
const animateCommands = () => {
commands.forEach((cmd) => {
setTimeout(() => {
setDisplayedLines((prev) => [...prev, cmd]);
}, cmd.delay);
});
};
return (
<section className="container mx-auto px-6 py-16 md:py-24" ref={containerRef}>
<motion.div
className="mx-auto max-w-4xl"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-100px' }}
transition={{ duration: 0.6 }}
>
{/* Title */}
<div className="text-center mb-8">
<h2 className="text-3xl md:text-4xl font-bold mb-4">Get Started in Seconds</h2>
<p className="text-lg text-muted-foreground">
Clone, run, and start building. No complex setup required.
</p>
</div>
{/* Terminal Window */}
<div className="rounded-lg border bg-card shadow-lg overflow-hidden">
{/* Terminal Header */}
<div className="flex items-center gap-2 border-b bg-muted/50 px-4 py-3">
<div className="flex gap-1.5">
<div className="h-3 w-3 rounded-full bg-red-500" aria-hidden="true" />
<div className="h-3 w-3 rounded-full bg-yellow-500" aria-hidden="true" />
<div className="h-3 w-3 rounded-full bg-green-500" aria-hidden="true" />
</div>
<div className="flex items-center gap-2 ml-4 text-sm text-muted-foreground">
<Terminal className="h-4 w-4" aria-hidden="true" />
<span className="font-mono">bash</span>
</div>
</div>
{/* Terminal Content */}
<div
className="bg-slate-950 p-6 font-mono text-sm overflow-x-auto"
style={{ minHeight: '400px' }}
>
<div className="space-y-2">
{displayedLines.map((line, index) => (
<motion.div
key={index}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.2 }}
className={`${
line.isSuccess
? 'text-green-400'
: line.text.startsWith('#')
? 'text-slate-500'
: line.text.startsWith('$')
? 'text-blue-400'
: 'text-slate-300'
}`}
>
{line.text || '\u00A0'}
{index === displayedLines.length - 1 && isAnimating && !line.isSuccess && (
<span
className="inline-block w-2 h-4 ml-1 bg-slate-400 animate-pulse"
aria-hidden="true"
/>
)}
</motion.div>
))}
</div>
</div>
</div>
{/* CTA Below Terminal */}
<div className="mt-8 text-center">
<p className="text-muted-foreground mb-4">
Or try the live demo without installing anything
</p>
<Button asChild size="lg" variant="outline" className="gap-2">
<Link href="/login">
<Play className="h-5 w-5" aria-hidden="true" />
Try Live Demo
</Link>
</Button>
</div>
</motion.div>
</section>
);
}