Files
portifolio-evandrodevbr/app/components/Projects.tsx
T
Evandro Fonseca Junior 6ec67c8f5a feat(about): add story sections and core values to About component
Enhance the About page with new story sections detailing personal journey and core values, including icons and structured content for better user engagement and self-presentation.
2025-10-11 18:25:41 -03:00

302 lines
11 KiB
TypeScript

"use client";
import { motion } from "motion/react";
import { BentoGrid, BentoGridItem } from "@/components/magicui/bento-grid";
import {
ExternalLink,
Github,
Star,
Zap,
Globe,
Cpu,
Code2,
} from "lucide-react";
import { usePortfolioData } from "@/lib/usePortfolioData";
// Configuração de cores e ícones por categoria
const categoryConfig = {
"full-stack": {
color: "from-blue-500 to-blue-600",
bgColor: "bg-blue-500/10",
borderColor: "border-blue-500/20",
icon: Globe,
iconColor: "text-blue-400",
},
iot: {
color: "from-purple-500 to-purple-600",
bgColor: "bg-purple-500/10",
borderColor: "border-purple-500/20",
icon: Cpu,
iconColor: "text-purple-400",
},
api: {
color: "from-orange-500 to-orange-600",
bgColor: "bg-orange-500/10",
borderColor: "border-orange-500/20",
icon: Code2,
iconColor: "text-orange-400",
},
web: {
color: "from-green-500 to-green-600",
bgColor: "bg-green-500/10",
borderColor: "border-green-500/20",
icon: Globe,
iconColor: "text-green-400",
},
desktop: {
color: "from-emerald-500 to-emerald-600",
bgColor: "bg-emerald-500/10",
borderColor: "border-emerald-500/20",
icon: Code2,
iconColor: "text-emerald-400",
},
};
// Função para gerar estrelas de complexidade
const ComplexityStars = ({ complexity }: { complexity: number }) => {
return (
<div className="flex items-center gap-1">
{[...Array(5)].map((_, i) => (
<Star
key={i}
className={`w-3 h-3 ${
i < complexity ? "text-yellow-400 fill-yellow-400" : "text-gray-600"
}`}
/>
))}
</div>
);
};
// Componente de Badge
const ProjectBadge = ({ badge, index }: { badge: string; index: number }) => {
const badgeColors = [
"bg-blue-500/20 text-blue-400 border-blue-500/30",
"bg-green-500/20 text-green-400 border-green-500/30",
"bg-purple-500/20 text-purple-400 border-purple-500/30",
"bg-orange-500/20 text-orange-400 border-orange-500/30",
"bg-red-500/20 text-red-400 border-red-500/30",
];
return (
<span
className={`px-2 py-1 text-xs font-medium rounded-full border ${
badgeColors[index % badgeColors.length]
}`}
>
{badge}
</span>
);
};
export default function Projects() {
const { data, loading, error } = usePortfolioData();
if (loading) {
return (
<section id="projects" className="py-20 px-4">
<div className="max-w-7xl mx-auto text-center">
<div className="animate-pulse">
<div className="h-8 bg-gray-700 rounded w-1/3 mx-auto mb-4"></div>
<div className="h-4 bg-gray-700 rounded w-1/2 mx-auto"></div>
</div>
</div>
</section>
);
}
if (error || !data) {
return (
<section id="projects" className="py-20 px-4">
<div className="max-w-7xl mx-auto text-center">
<p className="text-red-400">Erro ao carregar dados de projetos.</p>
</div>
</section>
);
}
const projects = data.projects.filter((project) => project.featured);
return (
<section id="projects" className="py-20 px-4 mx-auto">
<div className="max-w-7xl mx-auto">
<motion.div
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }}
viewport={{ once: true }}
className="text-center mb-16"
>
<h2 className="text-4xl md:text-5xl lg:text-6xl font-bold mb-6 bg-gradient-to-r from-cyan-400 to-blue-500 bg-clip-text text-transparent">
Projetos em Destaque
</h2>
<p className="text-xl text-gray-300 max-w-3xl mx-auto">
Uma seleção dos projetos que desenvolvi, demonstrando minha evolução
técnica e capacidade de entregar soluções completas em diferentes
domínios
</p>
</motion.div>
<BentoGrid className="gap-6">
{projects.map((project, index) => {
const config =
categoryConfig[project.category as keyof typeof categoryConfig] ||
categoryConfig["web"];
const IconComponent = config.icon;
return (
<BentoGridItem
key={project.id}
colSpan={index <= 1 ? 2 : 1} // NutriScan e Twins Lab em destaque
className={`group hover:scale-105 transition-all duration-300 hover:shadow-2xl ${config.borderColor} border-2 bg-white/5 backdrop-blur-sm`}
>
<motion.div
className="space-y-5 h-full"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: index * 0.1 }}
viewport={{ once: true }}
>
{/* Header com ícone e badges */}
<div className="flex items-start justify-between">
<div className="flex items-center gap-3">
<div className={`p-3 rounded-xl ${config.bgColor}`}>
<IconComponent
className={`w-6 h-6 ${config.iconColor}`}
/>
</div>
<div>
<h3 className="text-xl font-bold text-white mb-1">
{project.title}
</h3>
<p className="text-sm text-gray-400 font-medium">
{project.subtitle}
</p>
</div>
</div>
<div className="flex gap-2">
{project.demoUrl && (
<a
href={project.demoUrl}
target="_blank"
rel="noopener noreferrer"
className="p-2 rounded-lg bg-green-500/20 text-green-400 hover:bg-green-500/30 transition-all duration-300 hover:scale-110"
aria-label="Ver demo"
>
<ExternalLink className="w-4 h-4" />
</a>
)}
{project.githubUrl && (
<a
href={project.githubUrl}
target="_blank"
rel="noopener noreferrer"
className="p-2 rounded-lg bg-gray-500/20 text-gray-400 hover:bg-gray-500/30 transition-all duration-300 hover:scale-110"
aria-label="Ver código"
>
<Github className="w-4 h-4" />
</a>
)}
</div>
</div>
{/* Badges */}
<div className="flex flex-wrap gap-2">
{project.badges.map((badge, idx) => (
<ProjectBadge key={badge} badge={badge} index={idx} />
))}
</div>
{/* Description */}
<p className="text-gray-300 leading-relaxed text-sm">
{project.description}
</p>
{/* Tecnologias principais */}
<div>
<h4 className="text-xs font-semibold text-gray-400 mb-2 uppercase tracking-wide">
Stack Principal
</h4>
<div className="flex flex-wrap gap-1">
{project.technologies.slice(0, 4).map((tech) => (
<span
key={tech}
className="px-2 py-1 bg-gray-800/50 text-gray-300 text-xs rounded border border-gray-700/50"
>
{tech}
</span>
))}
{project.technologies.length > 4 && (
<span className="px-2 py-1 bg-gray-800/30 text-gray-500 text-xs rounded">
+{project.technologies.length - 4}
</span>
)}
</div>
</div>
{/* Features principais */}
<div>
<h4 className="text-xs font-semibold text-gray-400 mb-2 uppercase tracking-wide">
Destaques
</h4>
<ul className="space-y-1">
{project.features.slice(0, 3).map((feature, idx) => (
<li
key={idx}
className="text-xs text-gray-400 flex items-start gap-2"
>
<Zap className="w-3 h-3 text-yellow-400 mt-0.5 flex-shrink-0" />
<span>{feature}</span>
</li>
))}
</ul>
</div>
{/* Footer com complexidade e status */}
<div className="pt-4 border-t border-white/10 flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-xs text-gray-500">
Complexidade:
</span>
<ComplexityStars
complexity={project.metrics?.complexity || 3}
/>
</div>
<span
className={`text-xs font-medium px-2 py-1 rounded-full ${
project.status === "Em Produção"
? "bg-green-500/20 text-green-400 border border-green-500/30"
: "bg-blue-500/20 text-blue-400 border border-blue-500/30"
}`}
>
{project.status}
</span>
</div>
</motion.div>
</BentoGridItem>
);
})}
</BentoGrid>
{/* Call to Action */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.5 }}
viewport={{ once: true }}
className="text-center mt-16"
>
<div className="inline-flex items-center gap-2 bg-gradient-to-r from-blue-500/10 to-purple-500/10 px-6 py-3 rounded-full border border-blue-500/20">
<div className="w-2 h-2 bg-blue-400 rounded-full animate-pulse"></div>
<span className="text-sm text-blue-300 font-medium">
Todos os projetos são desenvolvidos individualmente, demonstrando
capacidade full-stack completa
</span>
</div>
</motion.div>
</div>
</section>
);
}