feat(contact, experience, projects, skills, workModalities): enhance component structure and user experience
- Refactored the Contact component to improve form handling and user feedback, introducing a custom shimmer button and enhanced form validation. - Updated the Experience component to optimize rendering and improve data handling with memoization, ensuring efficient performance. - Enhanced the Projects component with a new layout and improved project filtering, utilizing memoization for better performance. - Refined the Skills component to streamline the display of technical skills, incorporating a loading state and error handling for better user experience. - Revamped the WorkModalities component to present service options more clearly, including a market comparison section for better user decision-making.
This commit is contained in:
1 parent
375862a65a
commit
8987d06eb4
5 files changed
+606
-1048
No files matched your search
+91
-241
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import React, { useMemo } from "react";
|
||||
import { motion } from "motion/react";
|
||||
import { BentoGrid, BentoGridItem } from "@/components/magicui/bento-grid";
|
||||
import {
|
||||
@@ -10,292 +11,141 @@ import {
|
||||
Globe,
|
||||
Cpu,
|
||||
Code2,
|
||||
Terminal,
|
||||
} 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>
|
||||
);
|
||||
// --- Configuração de Cores (Harmonizado com a seção anterior) ---
|
||||
const CATEGORY_THEMES: Record<string, any> = {
|
||||
"full-stack": { color: "text-blue-400", bg: "bg-blue-500/10", border: "border-blue-500/20", icon: Globe },
|
||||
iot: { color: "text-purple-400", bg: "bg-purple-500/10", border: "border-purple-500/20", icon: Cpu },
|
||||
api: { color: "text-orange-400", bg: "bg-orange-500/10", border: "border-orange-500/20", icon: Code2 },
|
||||
web: { color: "text-green-400", bg: "bg-green-500/10", border: "border-green-500/20", icon: Globe },
|
||||
desktop: { color: "text-emerald-400", bg: "bg-emerald-500/10", border: "border-emerald-500/20", icon: Terminal },
|
||||
};
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
const projects = useMemo(() =>
|
||||
data?.projects.filter((p) => p.featured) || [],
|
||||
[data]);
|
||||
|
||||
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);
|
||||
if (loading) return <div className="py-20 text-center animate-pulse text-slate-500 font-mono">Loading_Assets...</div>;
|
||||
if (error) return <div className="py-20 text-center text-red-500">System_Error: Failed to sync projects.</div>;
|
||||
|
||||
return (
|
||||
<section id="projects" className="py-20 px-4 mx-auto">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<section id="projects" className="py-32 px-6 bg-[#030712] relative overflow-hidden">
|
||||
{/* Background Decor */}
|
||||
<div className="absolute inset-0 bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-blue-500/5 via-transparent to-transparent pointer-events-none" />
|
||||
|
||||
<div className="max-w-7xl mx-auto relative z-10">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
viewport={{ once: true }}
|
||||
className="text-center mb-16"
|
||||
className="text-left mb-20"
|
||||
>
|
||||
<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
|
||||
<span className="text-blue-500 font-mono text-sm tracking-[0.3em] uppercase block mb-4">Produção_Intelectual</span>
|
||||
<h2 className="text-5xl md:text-7xl font-black text-white tracking-tighter">
|
||||
Projetos <span className="text-slate-700 italic">Principais</span>
|
||||
</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">
|
||||
<BentoGrid className="grid-cols-1 md:grid-cols-3 gap-4">
|
||||
{projects.map((project, index) => {
|
||||
const config =
|
||||
categoryConfig[project.category as keyof typeof categoryConfig] ||
|
||||
categoryConfig["web"];
|
||||
const IconComponent = config.icon;
|
||||
const theme = CATEGORY_THEMES[project.category.toLowerCase()] || CATEGORY_THEMES.web;
|
||||
|
||||
// Lógica de Grid Inteligente:
|
||||
// Index 0: NutriScan (Destaque - 2 colunas)
|
||||
// Index 1: Twins Lab (Complemento - 1 coluna)
|
||||
// Demais: 1 coluna cada (Preenche a linha 2 perfeitamente)
|
||||
const colSpan = index === 0 ? "md:col-span-2" : "md:col-span-1";
|
||||
|
||||
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`}
|
||||
className={`${colSpan} group relative flex flex-col justify-between overflow-hidden rounded-3xl border border-slate-800 bg-slate-900/40 backdrop-blur-xl p-8 transition-all duration-500 hover:border-blue-500/30 min-h-[22rem]`}
|
||||
>
|
||||
<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}`}
|
||||
/>
|
||||
{/* Header: Icon & Links */}
|
||||
<div className="relative z-10">
|
||||
<div className="flex items-start justify-between mb-8">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className={`p-4 rounded-2xl ${theme.bg} border ${theme.border} group-hover:scale-110 transition-transform duration-500`}>
|
||||
<theme.icon className={`w-6 h-6 ${theme.color}`} />
|
||||
</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>
|
||||
<h3 className="text-2xl font-bold text-white tracking-tight">{project.title}</h3>
|
||||
<p className="text-sm text-slate-500 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>
|
||||
)}
|
||||
{project.githubUrl && <ProjectLink href={project.githubUrl} icon={Github} />}
|
||||
{project.demoUrl && <ProjectLink href={project.demoUrl} icon={ExternalLink} highlight />}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Badges */}
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{project.badges.map((badge, idx) => (
|
||||
<ProjectBadge key={badge} badge={badge} index={idx} />
|
||||
{/* Badges de Tecnologias */}
|
||||
<div className="flex flex-wrap gap-2 mb-6">
|
||||
{project.technologies.slice(0, 5).map((tech: string) => (
|
||||
<span key={tech} className="px-2 py-1 text-[10px] font-mono font-bold bg-slate-950 text-slate-400 border border-slate-800 rounded-md">
|
||||
{tech}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<p className="text-gray-300 leading-relaxed text-sm">
|
||||
<p className="text-slate-400 text-sm leading-relaxed mb-6 line-clamp-3 group-hover:line-clamp-none transition-all duration-500">
|
||||
{project.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 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>
|
||||
{/* Footer: Metrics & Status */}
|
||||
<div className="relative z-10 pt-6 border-t border-slate-800/50 mt-auto flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-[10px] uppercase tracking-widest text-slate-600 font-bold">Complexity</span>
|
||||
<ComplexityStars rating={project.metrics?.complexity || 3} />
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className={`w-2 h-2 rounded-full animate-pulse ${project.status === 'Em Produção' ? 'bg-emerald-500' : 'bg-blue-500'}`} />
|
||||
<span className="text-[10px] font-bold text-slate-400 uppercase">{project.status}</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>
|
||||
{/* Background Hover Glow */}
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-blue-500/5 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-700" />
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
||||
// --- Subcomponentes Auxiliares ---
|
||||
|
||||
function ProjectLink({ href, icon: Icon, highlight }: { href: string; icon: any; highlight?: boolean }) {
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={`p-2 rounded-xl transition-all duration-300 hover:scale-110 ${
|
||||
highlight ? 'bg-blue-600 text-white shadow-lg shadow-blue-600/20' : 'bg-slate-900 border border-slate-800 text-slate-500 hover:text-white'
|
||||
}`}
|
||||
>
|
||||
<Icon className="w-4 h-4" />
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
function ComplexityStars({ rating }: { rating: number }) {
|
||||
return (
|
||||
<div className="flex gap-1">
|
||||
{[...Array(5)].map((_, i) => (
|
||||
<div key={i} className={`w-1.5 h-1.5 rounded-full ${i < rating ? 'bg-yellow-500' : 'bg-slate-800'}`} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user