feat: Initialize CAR Auto Center project with Vite and React
Sets up the foundational structure for the CAR Auto Center application using Vite and React. Includes project dependencies, basic HTML structure, TypeScript configuration, and initial README content. This commit establishes the project's build tool, core libraries, and essential configuration files.
This commit is contained in:
760
components/AppContent.tsx
Normal file
760
components/AppContent.tsx
Normal file
@@ -0,0 +1,760 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import {
|
||||
CheckCircle2,
|
||||
Phone,
|
||||
Star,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
MapPin,
|
||||
Mail,
|
||||
Fuel,
|
||||
CloudRain,
|
||||
Volume2,
|
||||
Lock,
|
||||
ShieldCheck,
|
||||
Wrench,
|
||||
Clock,
|
||||
Award,
|
||||
Users,
|
||||
Search,
|
||||
Car,
|
||||
Settings,
|
||||
Battery,
|
||||
Thermometer,
|
||||
Droplet,
|
||||
Instagram,
|
||||
Facebook,
|
||||
Twitter,
|
||||
Youtube,
|
||||
Linkedin,
|
||||
ArrowLeftRight
|
||||
} from 'lucide-react';
|
||||
import { Button, Container, SectionTitle, cn } from './Shared';
|
||||
import { useData } from '../contexts/DataContext';
|
||||
import { FooterColumn } from '../types';
|
||||
|
||||
// ---- HELPERS ----
|
||||
|
||||
const IconResolver = ({ name, size = 24, className }: { name: string, size?: number, className?: string }) => {
|
||||
const icons: any = {
|
||||
ShieldCheck, Wrench, Clock, Award, Users, Search, Car, Settings, Battery, Thermometer, Droplet
|
||||
};
|
||||
const Icon = icons[name] || Settings;
|
||||
return <Icon size={size} className={className} />;
|
||||
};
|
||||
|
||||
// ---- SUB-COMPONENTS ----
|
||||
|
||||
const FeatureItem: React.FC<{ icon: string, title: string, description: string }> = ({ icon, title, description }) => (
|
||||
<div className="flex gap-4 p-4 rounded-lg hover:bg-zinc-800/50 transition-colors">
|
||||
<div className="shrink-0 w-12 h-12 rounded-lg bg-primary/10 flex items-center justify-center text-primary">
|
||||
<IconResolver name={icon} size={24} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-white mb-1">{title}</h3>
|
||||
<p className="text-gray-400 text-sm leading-relaxed">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const ServiceCard: React.FC<{ service: any, whatsappLink: string }> = ({ service, whatsappLink }) => (
|
||||
<div className="group relative bg-zinc-900 border border-zinc-800 rounded-xl overflow-hidden hover:border-primary/50 transition-all duration-300 flex flex-col h-full">
|
||||
<div className="h-48 overflow-hidden shrink-0">
|
||||
<img src={service.image} alt={service.title} className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500" />
|
||||
</div>
|
||||
<div className="p-6 flex flex-col flex-1">
|
||||
<h3 className="text-xl font-bold text-white mb-2">{service.title}</h3>
|
||||
<p className="text-gray-400 text-sm mb-4 flex-1">{service.description}</p>
|
||||
<a href={whatsappLink} target="_blank" rel="noopener noreferrer" className="text-primary font-semibold text-sm uppercase tracking-wider flex items-center gap-1 group-hover:gap-2 transition-all mt-auto">
|
||||
Agendar <span className="text-lg">→</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const PackageCard: React.FC<{ pkg: any, whatsappLink: string }> = ({ pkg, whatsappLink }) => (
|
||||
<div className={cn(
|
||||
"relative p-8 rounded-2xl border flex flex-col h-full",
|
||||
pkg.recommended
|
||||
? "bg-zinc-900 border-primary shadow-[0_0_30px_-10px_rgba(var(--primary-color),0.3)] scale-105 z-10"
|
||||
: "bg-black border-zinc-800"
|
||||
)}>
|
||||
{pkg.recommended && (
|
||||
<div className="absolute top-0 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-primary text-white text-xs font-bold px-3 py-1 rounded-full uppercase tracking-wider">
|
||||
Mais Popular
|
||||
</div>
|
||||
)}
|
||||
<h3 className="text-xl font-bold text-white mb-2">{pkg.name}</h3>
|
||||
<div className="flex items-baseline gap-1 mb-6">
|
||||
<span className="text-sm text-gray-400">R$</span>
|
||||
<span className="text-4xl font-black text-white">{pkg.price}</span>
|
||||
<span className="text-sm text-gray-400">/ serviço</span>
|
||||
</div>
|
||||
<ul className="flex-1 space-y-4 mb-8">
|
||||
{pkg.features.map((feat: string, i: number) => (
|
||||
<li key={i} className="flex items-start gap-3 text-gray-300 text-sm">
|
||||
<CheckCircle2 size={18} className="text-primary shrink-0 mt-0.5" />
|
||||
{feat}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<Button variant={pkg.recommended ? 'primary' : 'outline'} className="w-full" onClick={() => window.open(whatsappLink, '_blank')}>
|
||||
Escolher Plano
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
const AccordionItem: React.FC<{ faq: any }> = ({ faq }) => {
|
||||
const [isOpen, setIsOpen] = React.useState(false);
|
||||
return (
|
||||
<div className="border-b border-zinc-800">
|
||||
<button
|
||||
className="w-full py-4 flex items-center justify-between text-left text-white font-medium hover:text-primary transition-colors"
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
>
|
||||
{faq.question}
|
||||
{isOpen ? <ChevronUp size={20} className="text-primary" /> : <ChevronDown size={20} className="text-gray-500" />}
|
||||
</button>
|
||||
<div className={cn("overflow-hidden transition-all duration-300", isOpen ? "max-h-40 pb-4" : "max-h-0")}>
|
||||
<p className="text-gray-400 text-sm">{faq.answer}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Componente Antes e Depois Slider
|
||||
const BeforeAfterSlider: React.FC<{ before: string, after: string, title: string }> = ({ before, after, title }) => {
|
||||
const [sliderPosition, setSliderPosition] = useState(50);
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
|
||||
const handleMove = (event: React.MouseEvent<HTMLDivElement> | React.TouchEvent<HTMLDivElement>) => {
|
||||
if (!isDragging) return;
|
||||
|
||||
const rect = event.currentTarget.getBoundingClientRect();
|
||||
const x = 'touches' in event ? event.touches[0].clientX : (event as React.MouseEvent).clientX;
|
||||
const position = ((x - rect.left) / rect.width) * 100;
|
||||
|
||||
setSliderPosition(Math.min(100, Math.max(0, position)));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative group">
|
||||
<h3 className="text-white font-bold mb-2 text-lg">{title}</h3>
|
||||
<div
|
||||
className="relative w-full aspect-[4/3] rounded-xl overflow-hidden cursor-ew-resize select-none border border-zinc-700"
|
||||
onMouseDown={() => setIsDragging(true)}
|
||||
onMouseUp={() => setIsDragging(false)}
|
||||
onMouseLeave={() => setIsDragging(false)}
|
||||
onMouseMove={handleMove}
|
||||
onTouchStart={() => setIsDragging(true)}
|
||||
onTouchEnd={() => setIsDragging(false)}
|
||||
onTouchMove={handleMove}
|
||||
>
|
||||
<img src={after} alt="Depois" className="absolute inset-0 w-full h-full object-cover pointer-events-none" />
|
||||
<div
|
||||
className="absolute inset-0 w-full h-full overflow-hidden pointer-events-none border-r-2 border-primary bg-black/5"
|
||||
style={{ width: `${sliderPosition}%` }}
|
||||
>
|
||||
<img src={before} alt="Antes" className="absolute inset-0 w-full h-full object-cover max-w-none" style={{ width: '100vw', maxWidth: '100%' }} /> {/* Trick to keep image fixed */}
|
||||
{/* Fix for width calculation in simple implementation: better to just let it fit container */}
|
||||
<div className="absolute inset-0 bg-black/0" /> {/* Layer to prevent drag issues */}
|
||||
<img src={before} alt="Antes" className="absolute top-0 left-0 h-full object-cover max-w-none" style={{ width: '100%' }} />
|
||||
</div>
|
||||
|
||||
{/* Handle */}
|
||||
<div
|
||||
className="absolute top-0 bottom-0 w-1 bg-primary cursor-ew-resize z-10 flex items-center justify-center"
|
||||
style={{ left: `${sliderPosition}%` }}
|
||||
>
|
||||
<div className="w-8 h-8 bg-primary rounded-full flex items-center justify-center shadow-lg transform -translate-x-1/2">
|
||||
<ArrowLeftRight size={16} className="text-white" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Labels */}
|
||||
<div className="absolute bottom-4 left-4 bg-black/70 text-white text-xs font-bold px-2 py-1 rounded pointer-events-none">ANTES</div>
|
||||
<div className="absolute bottom-4 right-4 bg-primary text-white text-xs font-bold px-2 py-1 rounded pointer-events-none">DEPOIS</div>
|
||||
</div>
|
||||
{/* Alternative slider logic for better compatibility if custom drag fails often */}
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="100"
|
||||
value={sliderPosition}
|
||||
onChange={(e) => setSliderPosition(Number(e.target.value))}
|
||||
className="absolute inset-0 w-full h-full opacity-0 cursor-ew-resize z-20"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// ---- SECTIONS ----
|
||||
|
||||
export const SpecialOffersSection = () => {
|
||||
const { data, getWhatsAppLink } = useData();
|
||||
const t = data.texts.specialOffers;
|
||||
return (
|
||||
<section id="special-offers" className="py-16 bg-zinc-900/50 border-b border-zinc-900 relative">
|
||||
<Container>
|
||||
<div className="flex items-center justify-between mb-8">
|
||||
<div>
|
||||
<div className="inline-flex items-center gap-2 mb-2">
|
||||
<span className="relative flex h-3 w-3">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-primary opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-3 w-3 bg-primary"></span>
|
||||
</span>
|
||||
<span className="text-primary font-bold text-xs uppercase tracking-wider">{t.badge}</span>
|
||||
</div>
|
||||
<h2 className="text-3xl font-bold text-white">{t.title}</h2>
|
||||
</div>
|
||||
<div className="hidden sm:block text-right">
|
||||
<p className="text-gray-400 text-sm">{t.subtitle}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{data.specialOffers.map((offer) => (
|
||||
<div key={offer.id} className="bg-white rounded-lg p-4 shadow-lg hover:shadow-xl transition-shadow relative group">
|
||||
<div className="relative mb-4">
|
||||
<div className="absolute top-0 left-0 bg-red-600 text-white text-[10px] font-bold px-2 py-0.5 rounded-sm z-10 flex items-center gap-1 shadow-sm">
|
||||
<span className="text-white">🛍️ RETIRE NA LOJA</span>
|
||||
</div>
|
||||
<div className="absolute top-0 right-0 z-10">
|
||||
<div className="w-8 h-8 flex items-center justify-center border border-blue-900 rounded-sm bg-white text-[8px] text-blue-900 font-bold leading-none text-center p-0.5">
|
||||
INMETRO
|
||||
</div>
|
||||
</div>
|
||||
<div className="aspect-square flex items-center justify-center p-2">
|
||||
<img src={offer.image} alt={offer.title} className="max-w-full max-h-full object-cover rounded-md" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 mb-3">
|
||||
<div className="flex items-center bg-[#F37021] text-white rounded px-1.5 py-0.5 text-xs font-bold gap-1">
|
||||
<Fuel size={12} fill="white" /> <span className="border-l border-white/50 pl-1">{offer.specs.fuel}</span>
|
||||
</div>
|
||||
<div className="flex items-center bg-[#F37021] text-white rounded px-1.5 py-0.5 text-xs font-bold gap-1">
|
||||
<CloudRain size={12} fill="white" /> <span className="border-l border-white/50 pl-1">{offer.specs.grip}</span>
|
||||
</div>
|
||||
<div className="flex items-center bg-[#D4E000] text-black rounded px-1.5 py-0.5 text-xs font-bold gap-1">
|
||||
<Volume2 size={12} /> <span className="border-l border-black/20 pl-1">{offer.specs.noise}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 className="text-gray-900 font-semibold text-sm mb-2 leading-tight min-h-[40px]">
|
||||
{offer.title}
|
||||
</h3>
|
||||
|
||||
<div className="flex items-center gap-1 mb-3">
|
||||
<div className="flex text-yellow-400">
|
||||
{[...Array(5)].map((_, i) => (
|
||||
<Star
|
||||
key={i}
|
||||
size={14}
|
||||
fill={i < Math.floor(offer.rating) ? "currentColor" : "none"}
|
||||
className={i < Math.floor(offer.rating) ? "text-yellow-400" : "text-gray-300"}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<span className="text-gray-400 text-xs">({offer.reviews})</span>
|
||||
</div>
|
||||
|
||||
<div className="mt-auto">
|
||||
<div className="text-3xl font-bold text-gray-900 leading-none mb-1">
|
||||
{offer.price}
|
||||
</div>
|
||||
<div className="text-gray-500 text-xs">
|
||||
No PIX ou <span className="font-semibold text-gray-700">{offer.installment}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="absolute inset-x-0 bottom-0 p-4 opacity-0 group-hover:opacity-100 transition-opacity bg-white/95 backdrop-blur-sm flex items-center justify-center">
|
||||
<Button size="sm" className="w-full" onClick={() => window.open(getWhatsAppLink(`Olá! Tenho interesse no pneu: ${offer.title}`), '_blank')}>Comprar Agora</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export const AboutSection = () => {
|
||||
const { data } = useData();
|
||||
const t = data.texts.about;
|
||||
return (
|
||||
<section id="about" className="py-20 bg-black">
|
||||
<Container>
|
||||
<div className="grid lg:grid-cols-2 gap-12 items-center">
|
||||
<div>
|
||||
<SectionTitle subtitle={t.subtitle} title={t.title} center={false} />
|
||||
<div className="grid sm:grid-cols-2 gap-4">
|
||||
{data.promises.map((item, idx) => <FeatureItem key={idx} {...item} />)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1619642751034-765dfdf7c58e?auto=format&fit=crop&q=80&w=800"
|
||||
alt="Mecânico"
|
||||
className="rounded-lg shadow-2xl grayscale hover:grayscale-0 transition-all duration-500"
|
||||
/>
|
||||
<div className="absolute -bottom-6 -left-6 bg-white text-black p-6 rounded-lg shadow-xl max-w-[200px]">
|
||||
<span className="block text-4xl font-black text-primary">{t.badgeYear}</span>
|
||||
<span className="text-sm font-bold uppercase leading-tight block mt-1">{t.badgeText}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export const ServicesSection = () => {
|
||||
const { data, getWhatsAppLink } = useData();
|
||||
const t = data.texts.services;
|
||||
return (
|
||||
<section id="services" className="py-20 bg-zinc-950">
|
||||
<Container>
|
||||
<SectionTitle subtitle={t.subtitle} title={t.title} />
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{data.services.map((s) => <ServiceCard key={s.id} service={s} whatsappLink={getWhatsAppLink(`Olá! Gostaria de agendar o serviço: ${s.title}`)} />)}
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export const BigCTA = () => {
|
||||
const { data, getWhatsAppLink } = useData();
|
||||
const t = data.texts.bigCta;
|
||||
const handleCall = () => {
|
||||
window.open(getWhatsAppLink("Olá! Preciso de ajuda imediata/emergência."), '_blank');
|
||||
};
|
||||
return (
|
||||
<section className="py-24 relative overflow-hidden flex items-center cursor-pointer group" onClick={handleCall}>
|
||||
<div className="absolute inset-0 bg-black">
|
||||
<img src="https://images.unsplash.com/photo-1492144534655-ae79c964c9d7?auto=format&fit=crop&q=80&w=1920" className="w-full h-full object-cover opacity-30" alt="Carro preto" />
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black via-transparent to-black" />
|
||||
</div>
|
||||
<Container className="relative z-10 text-center">
|
||||
<h2 className="text-3xl md:text-5xl font-bold text-white mb-6">{t.title}</h2>
|
||||
<div className="flex flex-col md:flex-row items-center justify-center gap-6">
|
||||
<div className="text-4xl md:text-6xl font-black text-primary tracking-tighter group-hover:scale-105 transition-transform">
|
||||
{data.settings.contactPhoneDisplay}
|
||||
</div>
|
||||
<span className="bg-white text-black px-3 py-1 rounded font-bold text-sm">{t.buttonText}</span>
|
||||
</div>
|
||||
<p className="text-gray-400 mt-4">{t.subtitle}</p>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export const PackagesSection = () => {
|
||||
const { data, getWhatsAppLink } = useData();
|
||||
const t = data.texts.packages;
|
||||
return (
|
||||
<section id="packages" className="py-20 bg-black">
|
||||
<Container>
|
||||
<SectionTitle subtitle={t.subtitle} title={t.title} />
|
||||
<div className="grid md:grid-cols-3 gap-8 items-center max-w-5xl mx-auto">
|
||||
{data.packages.map((p, i) => <PackageCard key={i} pkg={p} whatsappLink={getWhatsAppLink(`Olá! Tenho interesse no pacote: ${p.name}`)} />)}
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export const GallerySection = () => {
|
||||
const { data } = useData();
|
||||
const t = data.texts.gallery;
|
||||
return (
|
||||
<section id="gallery" className="py-20 bg-zinc-900">
|
||||
<Container>
|
||||
<SectionTitle subtitle={t.subtitle} title={t.title} />
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
{data.gallery.map((img, idx) => (
|
||||
<div key={idx} className="aspect-square overflow-hidden rounded-lg group cursor-pointer">
|
||||
<img src={img} alt="Galeria" className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
// --- NOVA SEÇÃO DE ANTES E DEPOIS ---
|
||||
export const BeforeAfterSection = () => {
|
||||
const { data } = useData();
|
||||
const t = data.texts.beforeAfter;
|
||||
|
||||
return (
|
||||
<section id="results" className="py-20 bg-black">
|
||||
<Container>
|
||||
<SectionTitle subtitle={t.subtitle} title={t.title} />
|
||||
<div className="grid md:grid-cols-2 gap-8">
|
||||
{data.beforeAfter.map((item) => (
|
||||
<BeforeAfterSlider
|
||||
key={item.id}
|
||||
title={item.title}
|
||||
before={item.imageBefore}
|
||||
after={item.imageAfter}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export const StatsSection = () => {
|
||||
const { data } = useData();
|
||||
return (
|
||||
<section className="py-16 bg-primary">
|
||||
<Container>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-8 text-center text-white">
|
||||
{data.stats.map((s, i) => (
|
||||
<div key={i}>
|
||||
<div className="text-4xl md:text-5xl font-black mb-2">{s.value}</div>
|
||||
<div className="text-white/80 font-medium uppercase text-sm tracking-wider">{s.label}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export const WhyChooseSection = () => {
|
||||
const { data } = useData();
|
||||
const t = data.texts.whyChoose;
|
||||
return (
|
||||
<section className="py-20 bg-black overflow-hidden">
|
||||
<Container>
|
||||
<div className="grid lg:grid-cols-2 gap-16 items-center">
|
||||
<div className="order-2 lg:order-1">
|
||||
<SectionTitle subtitle={t.subtitle} title={t.title} center={false} />
|
||||
<div className="space-y-6">
|
||||
{data.whyChoose.map((item, i) => (
|
||||
<div key={i} className="flex items-center gap-4">
|
||||
<div className="w-10 h-10 rounded-full bg-zinc-800 flex items-center justify-center text-primary shrink-0">
|
||||
<IconResolver name={item.icon} size={20} />
|
||||
</div>
|
||||
<p className="text-gray-200 font-medium">{item.text}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="order-1 lg:order-2 relative">
|
||||
<div className="absolute -inset-4 bg-primary rounded-full blur-[100px] opacity-20" />
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1605559424843-9e4c228bf1c2?auto=format&fit=crop&q=80&w=800"
|
||||
alt="Carro Vermelho"
|
||||
className="relative z-10 w-full rounded-xl shadow-2xl transform lg:translate-x-12"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export const TestimonialsSection = () => {
|
||||
const { data } = useData();
|
||||
const t = data.texts.testimonials;
|
||||
return (
|
||||
<section id="testimonials" className="py-20 bg-zinc-950">
|
||||
<Container>
|
||||
<SectionTitle subtitle={t.subtitle} title={t.title} />
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
{data.testimonials.map((t, i) => (
|
||||
<div key={i} className="bg-zinc-900 p-8 rounded-xl border border-zinc-800">
|
||||
<div className="flex gap-1 text-primary mb-4">
|
||||
{[...Array(5)].map((_, i) => <Star key={i} size={16} fill="currentColor" />)}
|
||||
</div>
|
||||
<p className="text-gray-300 mb-6 italic">"{t.text}"</p>
|
||||
<div className="flex items-center gap-4">
|
||||
<img src={t.image} alt={t.name} className="w-12 h-12 rounded-full object-cover" />
|
||||
<div>
|
||||
<h4 className="text-white font-bold text-sm">{t.name}</h4>
|
||||
<p className="text-gray-500 text-xs">{t.role}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export const TeamSection = () => {
|
||||
const { data } = useData();
|
||||
const t = data.texts.team;
|
||||
return (
|
||||
<section className="py-20 bg-black">
|
||||
<Container>
|
||||
<SectionTitle subtitle={t.subtitle} title={t.title} />
|
||||
<div className="grid sm:grid-cols-2 md:grid-cols-4 gap-6">
|
||||
{data.team.map((member, i) => (
|
||||
<div key={i} className="group text-center">
|
||||
<div className="mb-4 overflow-hidden rounded-xl aspect-[3/4]">
|
||||
<img src={member.image} alt={member.name} className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500 filter grayscale group-hover:grayscale-0" />
|
||||
</div>
|
||||
<h3 className="text-white font-bold">{member.name}</h3>
|
||||
<p className="text-primary text-sm">{member.role}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export const FaqSection = () => {
|
||||
const { data } = useData();
|
||||
const t = data.texts.faq;
|
||||
return (
|
||||
<section className="py-20 bg-zinc-900">
|
||||
<Container>
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<SectionTitle subtitle={t.subtitle} title={t.title} />
|
||||
<div className="space-y-2">
|
||||
{data.faqs.map((faq, i) => <AccordionItem key={i} faq={faq} />)}
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export const BlogSection = () => {
|
||||
const { data } = useData();
|
||||
const t = data.texts.blog;
|
||||
return (
|
||||
<section id="blog" className="py-20 bg-black">
|
||||
<Container>
|
||||
<SectionTitle subtitle={t.subtitle} title={t.title} />
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
{data.blog.map((post, i) => (
|
||||
<div key={i} className="bg-zinc-900 rounded-xl overflow-hidden group flex flex-col h-full">
|
||||
<div className="h-48 overflow-hidden relative shrink-0">
|
||||
<img src={post.image} alt={post.title} className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500" />
|
||||
<div className="absolute top-4 left-4 bg-primary text-white text-xs font-bold px-2 py-1 rounded">
|
||||
{post.date}
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-6 flex flex-col flex-1">
|
||||
<h3 className="text-white font-bold text-xl mb-3 group-hover:text-primary transition-colors">{post.title}</h3>
|
||||
<p className="text-gray-400 text-sm mb-4 line-clamp-2 flex-1">{post.excerpt}</p>
|
||||
<Link to={`/blog/${post.id}`} className="text-white text-sm font-semibold border-b border-primary pb-0.5 hover:text-primary transition-colors inline-block w-fit">
|
||||
Ler mais
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export const ContactSection = () => {
|
||||
const { data } = useData();
|
||||
const t = data.texts.contact;
|
||||
return (
|
||||
<section id="contact" className="py-20 bg-zinc-950">
|
||||
<Container>
|
||||
<div className="grid lg:grid-cols-2 gap-12">
|
||||
<div>
|
||||
<SectionTitle subtitle={t.subtitle} title={t.title} center={false} />
|
||||
<p className="text-gray-400 mb-8">
|
||||
{t.description}
|
||||
</p>
|
||||
|
||||
<div className="space-y-6 mb-8">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="bg-zinc-900 p-3 rounded text-primary"><MapPin /></div>
|
||||
<div>
|
||||
<h4 className="text-white font-bold">Endereço</h4>
|
||||
<p className="text-gray-400 text-sm">{data.settings.address}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="bg-zinc-900 p-3 rounded text-primary"><Phone /></div>
|
||||
<div>
|
||||
<h4 className="text-white font-bold">Telefone</h4>
|
||||
<p className="text-gray-400 text-sm">{data.settings.contactPhoneDisplay}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="bg-zinc-900 p-3 rounded text-primary"><Mail /></div>
|
||||
<div>
|
||||
<h4 className="text-white font-bold">Email</h4>
|
||||
<p className="text-gray-400 text-sm">{data.settings.contactEmail}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form action={`mailto:${data.settings.contactEmail}`} method="post" encType="text/plain" className="bg-zinc-900 p-8 rounded-2xl border border-zinc-800 space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<input type="text" name="nome" placeholder="Nome" className="bg-black border border-zinc-800 rounded p-3 text-white focus:border-primary focus:outline-none w-full" />
|
||||
<input type="text" name="telefone" placeholder="Telefone" className="bg-black border border-zinc-800 rounded p-3 text-white focus:border-primary focus:outline-none w-full" />
|
||||
</div>
|
||||
<input type="email" name="email" placeholder="Email" className="bg-black border border-zinc-800 rounded p-3 text-white focus:border-primary focus:outline-none w-full" />
|
||||
<textarea rows={4} name="mensagem" placeholder="Mensagem" className="bg-black border border-zinc-800 rounded p-3 text-white focus:border-primary focus:outline-none w-full"></textarea>
|
||||
<Button type="submit" className="w-full">Enviar Mensagem</Button>
|
||||
</form>
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export const ClientsSection = () => {
|
||||
const { data } = useData();
|
||||
return (
|
||||
<section className="py-12 bg-black border-t border-zinc-900">
|
||||
<Container>
|
||||
<p className="text-center text-gray-500 text-sm uppercase tracking-widest mb-8">Empresas que confiam na CAR</p>
|
||||
<div className="flex flex-wrap justify-center gap-12 opacity-50 grayscale hover:grayscale-0 transition-all duration-500 items-center">
|
||||
{data.clients.map((client, i) => (
|
||||
client.url ? (
|
||||
<img key={i} src={client.url} alt={client.name} className="h-12 object-contain" />
|
||||
) : (
|
||||
<span key={i} className="text-2xl font-black text-white">{client.name}</span>
|
||||
)
|
||||
))}
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
// Renderiza o conteúdo de uma coluna baseado no tipo
|
||||
const FooterColumnRenderer: React.FC<{ column: FooterColumn }> = ({ column }) => {
|
||||
const { data } = useData();
|
||||
|
||||
if (column.type === 'services_dynamic') {
|
||||
return (
|
||||
<div>
|
||||
<h4 className="text-white font-bold mb-4">{column.title}</h4>
|
||||
<ul className="space-y-2 text-sm">
|
||||
{data.services.slice(0, 4).map((s) => (
|
||||
<li key={s.id}><Link to="/servicos" className="hover:text-primary">{s.title}</Link></li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (column.type === 'hours') {
|
||||
return (
|
||||
<div>
|
||||
<h4 className="text-white font-bold mb-4">{column.title}</h4>
|
||||
<ul className="space-y-2 text-sm">
|
||||
<li>Seg - Sex: 08h - 18h</li>
|
||||
<li>Sábado: 08h - 14h</li>
|
||||
<li className="text-primary font-bold">Emergência 24h: {data.settings.contactPhoneDisplay}</li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (column.type === 'custom' && column.links) {
|
||||
return (
|
||||
<div>
|
||||
<h4 className="text-white font-bold mb-4">{column.title}</h4>
|
||||
<ul className="space-y-2 text-sm">
|
||||
{column.links.map((link, idx) => (
|
||||
<li key={idx}>
|
||||
{link.href.startsWith('/') ? (
|
||||
<Link to={link.href} className="hover:text-primary">{link.label}</Link>
|
||||
) : (
|
||||
<a href={link.href} className="hover:text-primary">{link.label}</a>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export const Footer = () => {
|
||||
const { data } = useData();
|
||||
return (
|
||||
<footer className="bg-zinc-950 border-t border-zinc-900 py-12 text-gray-400">
|
||||
<Container>
|
||||
<div className="grid md:grid-cols-4 gap-8 mb-8">
|
||||
{/* Logo & Descrição sempre primeiro */}
|
||||
<div>
|
||||
<Link to="/" className="flex items-center gap-2 mb-4">
|
||||
{data.settings.logoUrl ? (
|
||||
<img src={data.settings.logoUrl} alt={data.settings.siteName} className="h-10 object-contain" />
|
||||
) : (
|
||||
<div className="text-2xl font-black italic tracking-tighter text-white">
|
||||
{data.settings.siteName}<span className="text-primary">.</span>
|
||||
</div>
|
||||
)}
|
||||
</Link>
|
||||
<p className="text-sm mb-6">
|
||||
{data.footer.description}
|
||||
</p>
|
||||
|
||||
{/* Social Icons Render */}
|
||||
<div className="flex gap-4">
|
||||
{data.settings.social?.instagram && (
|
||||
<a href={data.settings.social.instagram} target="_blank" rel="noopener noreferrer" className="bg-zinc-900 p-2 rounded-full hover:bg-primary hover:text-white transition-all">
|
||||
<Instagram size={18} />
|
||||
</a>
|
||||
)}
|
||||
{data.settings.social?.facebook && (
|
||||
<a href={data.settings.social.facebook} target="_blank" rel="noopener noreferrer" className="bg-zinc-900 p-2 rounded-full hover:bg-primary hover:text-white transition-all">
|
||||
<Facebook size={18} />
|
||||
</a>
|
||||
)}
|
||||
{data.settings.social?.twitter && (
|
||||
<a href={data.settings.social.twitter} target="_blank" rel="noopener noreferrer" className="bg-zinc-900 p-2 rounded-full hover:bg-primary hover:text-white transition-all">
|
||||
<Twitter size={18} />
|
||||
</a>
|
||||
)}
|
||||
{data.settings.social?.youtube && (
|
||||
<a href={data.settings.social.youtube} target="_blank" rel="noopener noreferrer" className="bg-zinc-900 p-2 rounded-full hover:bg-primary hover:text-white transition-all">
|
||||
<Youtube size={18} />
|
||||
</a>
|
||||
)}
|
||||
{data.settings.social?.linkedin && (
|
||||
<a href={data.settings.social.linkedin} target="_blank" rel="noopener noreferrer" className="bg-zinc-900 p-2 rounded-full hover:bg-primary hover:text-white transition-all">
|
||||
<Linkedin size={18} />
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Renderiza Colunas Dinâmicas */}
|
||||
{data.footer.columns.map((col) => (
|
||||
<FooterColumnRenderer key={col.id} column={col} />
|
||||
))}
|
||||
|
||||
</div>
|
||||
<div className="border-t border-zinc-900 pt-8 flex flex-col md:flex-row justify-between items-center text-xs">
|
||||
<p>© 2024 {data.settings.siteName}. Todos os direitos reservados.</p>
|
||||
<div className="flex gap-4 mt-4 md:mt-0 items-center">
|
||||
{data.footer.bottomLinks.map((link, idx) => (
|
||||
<a key={idx} href={link.href} className="hover:text-white">{link.label}</a>
|
||||
))}
|
||||
<Link to="/admin" className="hover:text-primary flex items-center gap-1 border-l border-zinc-800 pl-4 ml-2">
|
||||
<Lock size={12} /> Área Administrativa
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user