From c0bd6d7e3d970d15de146b8a9688b4315d9f21a8 Mon Sep 17 00:00:00 2001 From: MMrp89 Date: Thu, 19 Feb 2026 16:22:10 -0300 Subject: [PATCH] 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. --- .gitignore | 24 + App.tsx | 104 ++++ README.md | 25 +- components/Admin/Dashboard.tsx | 880 +++++++++++++++++++++++++++++++++ components/Admin/Login.tsx | 76 +++ components/AppContent.tsx | 760 ++++++++++++++++++++++++++++ components/Header.tsx | 108 ++++ components/Hero.tsx | 64 +++ components/Pages.tsx | 176 +++++++ components/Shared.tsx | 75 +++ contexts/DataContext.tsx | 123 +++++ data.ts | 373 ++++++++++++++ index.html | 56 +++ index.tsx | 21 + lib/supabase.ts | 18 + metadata.json | 5 + package.json | 26 + tsconfig.json | 29 ++ types.ts | 223 +++++++++ vite.config.ts | 23 + 20 files changed, 3181 insertions(+), 8 deletions(-) create mode 100644 .gitignore create mode 100644 App.tsx create mode 100644 components/Admin/Dashboard.tsx create mode 100644 components/Admin/Login.tsx create mode 100644 components/AppContent.tsx create mode 100644 components/Header.tsx create mode 100644 components/Hero.tsx create mode 100644 components/Pages.tsx create mode 100644 components/Shared.tsx create mode 100644 contexts/DataContext.tsx create mode 100644 data.ts create mode 100644 index.html create mode 100644 index.tsx create mode 100644 lib/supabase.ts create mode 100644 metadata.json create mode 100644 package.json create mode 100644 tsconfig.json create mode 100644 types.ts create mode 100644 vite.config.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/App.tsx b/App.tsx new file mode 100644 index 0000000..0070ad0 --- /dev/null +++ b/App.tsx @@ -0,0 +1,104 @@ +import React, { useEffect } from 'react'; +import { Routes, Route, Navigate, Outlet } from 'react-router-dom'; +import { Header } from './components/Header'; +import { Login } from './components/Admin/Login'; +import { Dashboard } from './components/Admin/Dashboard'; +import { useData } from './contexts/DataContext'; +import { Footer } from './components/AppContent'; + +// Páginas +import { + HomePage, + AboutPage, + ServicesPage, + PromotionsPage, + BlogPage, + BlogPostPage, + ContactPage +} from './components/Pages'; + +// Layout do site público com Tema Dinâmico e Estrutura Fixa +const PublicLayout = () => { + const { data, getWhatsAppLink } = useData(); + + // Aplica cor primária dinamicamente + useEffect(() => { + document.documentElement.style.setProperty('--primary-color', data.settings.primaryColor); + + // Atualiza Favicon + if (data.settings.faviconUrl) { + const link = document.querySelector("link[rel~='icon']") as HTMLLinkElement; + if (link) { + link.href = data.settings.faviconUrl; + } else { + const newLink = document.createElement('link'); + newLink.rel = 'icon'; + newLink.href = data.settings.faviconUrl; + document.head.appendChild(newLink); + } + } + + // Atualiza Título + document.title = data.settings.siteName; + }, [data.settings]); + + return ( +
+
+
+ +
+
+ ); +}; + +// Proteção de rota simples +const ProtectedRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const isAuth = localStorage.getItem('admin_auth') === 'true'; + return isAuth ? <>{children} : ; +}; + +function App() { + return ( + + {/* Rotas Públicas */} + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + {/* Rotas Administrativas */} + } /> + + + + } + /> + + {/* Rota Catch-all para redirecionar para Home se nada corresponder */} + } /> + + ); +} + +export default App; \ No newline at end of file diff --git a/README.md b/README.md index 2241000..35f6d97 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,20 @@
- GHBanner - -

Built with AI Studio

- -

The fastest path from prompt to production with Gemini.

- - Start building -
+ +# Run and deploy your AI Studio app + +This contains everything you need to run your app locally. + +View your app in AI Studio: https://ai.studio/apps/drive/16UZt4XL5C3Sit2eoOCGMMWHc9ox45SJD + +## Run Locally + +**Prerequisites:** Node.js + + +1. Install dependencies: + `npm install` +2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key +3. Run the app: + `npm run dev` diff --git a/components/Admin/Dashboard.tsx b/components/Admin/Dashboard.tsx new file mode 100644 index 0000000..638fcc7 --- /dev/null +++ b/components/Admin/Dashboard.tsx @@ -0,0 +1,880 @@ +import React, { useState } from 'react'; +import { + LayoutDashboard, + Settings, + LogOut, + Save, + Monitor, + Box, + Users, + MessageSquare, + Wrench, + Tags, + FileText, + Check, + Eye, + Trash2, + PlusCircle, + Palette, + Briefcase, + Image, + Upload, + Menu as MenuIcon, + Columns, + X, + ArrowLeftRight +} from 'lucide-react'; +import { useNavigate } from 'react-router-dom'; +import { useData } from '../../contexts/DataContext'; +import { Hero } from '../Hero'; +import { Header } from '../Header'; +import { + ServicesSection, + PackagesSection, + AboutSection, + TeamSection, + TestimonialsSection, + FaqSection, + BlogSection, + SpecialOffersSection, + ClientsSection, + WhyChooseSection, + GallerySection, + StatsSection, + Footer, + BeforeAfterSection +} from '../AppContent'; +import { FooterColumn, FooterLink } from '../../types'; + +const SidebarItem = ({ icon: Icon, label, active, onClick }: any) => ( + +); + +const InputGroup = ({ label, value, onChange, type = "text", textarea = false, hint }: any) => ( +
+
+ + {hint && {hint}} +
+ {textarea ? ( + + + +
+ + + ); +}; + +export const ClientsSection = () => { + const { data } = useData(); + return ( +
+ +

Empresas que confiam na CAR

+
+ {data.clients.map((client, i) => ( + client.url ? ( + {client.name} + ) : ( + {client.name} + ) + ))} +
+
+
+ ); +}; + +// 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 ( +
+

{column.title}

+
    + {data.services.slice(0, 4).map((s) => ( +
  • {s.title}
  • + ))} +
+
+ ); + } + + if (column.type === 'hours') { + return ( +
+

{column.title}

+
    +
  • Seg - Sex: 08h - 18h
  • +
  • Sábado: 08h - 14h
  • +
  • Emergência 24h: {data.settings.contactPhoneDisplay}
  • +
+
+ ); + } + + if (column.type === 'custom' && column.links) { + return ( +
+

{column.title}

+
    + {column.links.map((link, idx) => ( +
  • + {link.href.startsWith('/') ? ( + {link.label} + ) : ( + {link.label} + )} +
  • + ))} +
+
+ ); + } + + return null; +} + +export const Footer = () => { + const { data } = useData(); + return ( +
+ +
+ {/* Logo & Descrição sempre primeiro */} +
+ + {data.settings.logoUrl ? ( + {data.settings.siteName} + ) : ( +
+ {data.settings.siteName}. +
+ )} + +

+ {data.footer.description} +

+ + {/* Social Icons Render */} +
+ {data.settings.social?.instagram && ( + + + + )} + {data.settings.social?.facebook && ( + + + + )} + {data.settings.social?.twitter && ( + + + + )} + {data.settings.social?.youtube && ( + + + + )} + {data.settings.social?.linkedin && ( + + + + )} +
+
+ + {/* Renderiza Colunas Dinâmicas */} + {data.footer.columns.map((col) => ( + + ))} + +
+
+

© 2024 {data.settings.siteName}. Todos os direitos reservados.

+
+ {data.footer.bottomLinks.map((link, idx) => ( + {link.label} + ))} + + Área Administrativa + +
+
+
+
+ ); +}; \ No newline at end of file diff --git a/components/Header.tsx b/components/Header.tsx new file mode 100644 index 0000000..52d2d24 --- /dev/null +++ b/components/Header.tsx @@ -0,0 +1,108 @@ +import React, { useState, useEffect } from 'react'; +import { Menu, X, Phone } from 'lucide-react'; +import { Link, useLocation } from 'react-router-dom'; +import { Button, Container } from './Shared'; +import { useData } from '../contexts/DataContext'; + +export const Header: React.FC = () => { + const { data, getWhatsAppLink } = useData(); + const [isScrolled, setIsScrolled] = useState(false); + const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); + const location = useLocation(); + + useEffect(() => { + const handleScroll = () => { + setIsScrolled(window.scrollY > 50); + }; + window.addEventListener('scroll', handleScroll); + return () => window.removeEventListener('scroll', handleScroll); + }, []); + + const handleCtaClick = () => { + const { url } = data.header.ctaButton; + if (url && url.trim() !== '') { + window.open(url, '_blank'); + } else { + window.open(getWhatsAppLink("Olá! Gostaria de agendar um horário."), '_blank'); + } + }; + + return ( +
+ +
+ + {data.settings.logoUrl ? ( + {data.settings.siteName} + ) : ( +
+ {data.settings.siteName}. +
+ )} + + + {/* Desktop Nav */} + + +
+
+ + {data.settings.contactPhoneDisplay} +
+ {data.header.ctaButton.show && ( + + )} +
+ + {/* Mobile Toggle */} + +
+ + {/* Mobile Menu */} + {isMobileMenuOpen && ( +
+ {data.header.items.map((item, index) => ( + setIsMobileMenuOpen(false)} + > + {item.label} + + ))} +
+ {data.header.ctaButton.show && ( + + )} +
+ )} +
+
+ ); +}; \ No newline at end of file diff --git a/components/Hero.tsx b/components/Hero.tsx new file mode 100644 index 0000000..fbf5298 --- /dev/null +++ b/components/Hero.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import { ArrowRight, Settings } from 'lucide-react'; +import { Link } from 'react-router-dom'; +import { Button, Container } from './Shared'; +import { useData } from '../contexts/DataContext'; + +export const Hero: React.FC = () => { + const { data } = useData(); + const { hero, texts } = data; + + return ( +
+ {/* Background Image with Overlay */} +
+ Oficina Mecânica +
+
+ + +
+ {/* Badge atualizado com border-primary sólido */} +
+ + Centro Automotivo Premium +
+ +

+ ') }} /> +

+ +

+ {hero.subtitle} +

+ +
+ + + + +
+ +
+
+ + {texts.hero.feature1} +
+
|
+
{texts.hero.feature2}
+
|
+
{texts.hero.feature3}
+
+
+
+
+ ); +}; \ No newline at end of file diff --git a/components/Pages.tsx b/components/Pages.tsx new file mode 100644 index 0000000..05dca7c --- /dev/null +++ b/components/Pages.tsx @@ -0,0 +1,176 @@ +import React, { useEffect } from 'react'; +import { Hero } from './Hero'; +import { + AboutSection, + ServicesSection, + BigCTA, + PackagesSection, + GallerySection, + StatsSection, + WhyChooseSection, + TestimonialsSection, + TeamSection, + FaqSection, + BlogSection, + ContactSection, + ClientsSection, + SpecialOffersSection, + BeforeAfterSection // Importado +} from './AppContent'; +import { useData } from '../contexts/DataContext'; +import { Container, Button } from './Shared'; +import { useParams, Link } from 'react-router-dom'; +import { ArrowLeft, Calendar } from 'lucide-react'; + +// Componente para rolar para o topo ao mudar de página +const ScrollToTop = () => { + useEffect(() => { + window.scrollTo(0, 0); + }, []); + return null; +}; + +export const HomePage = () => { + const { data } = useData(); + const { visibility } = data; + return ( + <> + + {visibility.hero && } + {visibility.specialOffers && } + {visibility.about && } + {visibility.services && } + {visibility.beforeAfter && } + {visibility.bigCta && } + {visibility.packages && } + {visibility.gallery && } + {visibility.stats && } + {visibility.whyChoose && } + {visibility.testimonials && } + {visibility.team && } + {visibility.faq && } + {visibility.blog && } + {visibility.contact && } + {visibility.clients && } + + ); +}; + +export const AboutPage = () => ( + <> + +
+ + + + + +
+ +); + +export const PromotionsPage = () => ( + <> + +
+
+ +

Promoções & Ofertas

+

Confira nossas condições especiais para pneus e revisões.

+
+
+ + + +
+ +); + +export const ServicesPage = () => ( + <> + +
+ + + + +
+ +); + +export const BlogPage = () => ( + <> + +
+
+ +

Blog Automotivo

+

Dicas, manutenção e novidades do mundo automotivo.

+
+
+ +
+ +); + +export const BlogPostPage = () => { + const { id } = useParams(); + const { data } = useData(); + const post = data.blog.find(p => p.id === Number(id)); + + if (!post) { + return ( +
+

Artigo não encontrado

+ +
+ ) + } + + return ( + <> + +
+ + + Voltar para o Blog + + +
+ {post.title} +
+ +
+ + {post.date} +
+ +

+ {post.title} +

+ +
+ +
+

Gostou da dica?

+ +
+ +
+ + ) +} + +export const ContactPage = () => ( + <> + +
+ + +
+ +); \ No newline at end of file diff --git a/components/Shared.tsx b/components/Shared.tsx new file mode 100644 index 0000000..b5b4378 --- /dev/null +++ b/components/Shared.tsx @@ -0,0 +1,75 @@ +import React from 'react'; +import { clsx, type ClassValue } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} + +interface ButtonProps extends React.ButtonHTMLAttributes { + variant?: 'primary' | 'outline' | 'white' | 'ghost'; + size?: 'sm' | 'md' | 'lg'; +} + +export const Button: React.FC = ({ + children, + className, + variant = 'primary', + size = 'md', + ...props +}) => { + const variants = { + primary: 'bg-primary text-white hover:brightness-110 border border-transparent', + outline: 'bg-transparent border-2 border-primary text-primary hover:bg-primary hover:text-white', + white: 'bg-white text-black hover:bg-gray-200 border border-transparent', + ghost: 'bg-transparent text-white hover:bg-white/10' + }; + + const sizes = { + sm: 'px-4 py-2 text-sm', + md: 'px-6 py-3 text-base', + lg: 'px-8 py-4 text-lg font-semibold' + }; + + return ( + + ); +}; + +export const SectionTitle: React.FC<{ subtitle: string; title: string; center?: boolean; light?: boolean }> = ({ + subtitle, + title, + center = true, + light = true +}) => { + return ( +
+ + {subtitle} + +

+ {title} +

+
+
+ ); +}; + +export const Container: React.FC<{ children: React.ReactNode; className?: string }> = ({ children, className }) => ( +
+ {children} +
+); \ No newline at end of file diff --git a/contexts/DataContext.tsx b/contexts/DataContext.tsx new file mode 100644 index 0000000..0781e21 --- /dev/null +++ b/contexts/DataContext.tsx @@ -0,0 +1,123 @@ +import React, { createContext, useContext, useState, useEffect } from 'react'; +import { + PROMISES, SERVICES, PACKAGES, SPECIAL_OFFERS, + GALLERY_IMAGES, STATS, WHY_CHOOSE_ITEMS, TESTIMONIALS, + TEAM, FAQS, BLOG_POSTS, DEFAULT_SETTINGS, DEFAULT_VISIBILITY, CLIENT_LOGOS, DEFAULT_TEXTS, + DEFAULT_HEADER, DEFAULT_FOOTER, BEFORE_AFTER_ITEMS +} from '../data'; +import { supabase, isSupabaseConfigured } from '../lib/supabase'; +import { GlobalSettings, SectionVisibility, SectionTexts, HeaderConfig, FooterConfig, BeforeAfterItem } from '../types'; + +// Estado inicial combina todos os dados +const INITIAL_DATA = { + settings: DEFAULT_SETTINGS, + visibility: DEFAULT_VISIBILITY, + hero: { + title: "Manutenção e Reparos Automotivos", + subtitle: "24h de atendimento • Especialistas em todas as marcas • Garantia em todos os serviços.", + bgImage: "https://images.unsplash.com/photo-1625047509168-a7026f36de04?auto=format&fit=crop&q=80&w=1920", + buttonText: "Agendar Agora" + }, + texts: DEFAULT_TEXTS, // Novos textos + header: DEFAULT_HEADER, // Config do Menu + footer: DEFAULT_FOOTER, // Config do Rodapé + promises: PROMISES, + services: SERVICES, + packages: PACKAGES, + specialOffers: SPECIAL_OFFERS, + gallery: GALLERY_IMAGES, + beforeAfter: BEFORE_AFTER_ITEMS, // Nova seção + stats: STATS, + whyChoose: WHY_CHOOSE_ITEMS, + testimonials: TESTIMONIALS, + team: TEAM, + faqs: FAQS, + blog: BLOG_POSTS, + clients: CLIENT_LOGOS +}; + +type DataType = typeof INITIAL_DATA; + +interface DataContextType { + data: DataType; + updateData: (section: keyof DataType, newData: any) => Promise; + isLoading: boolean; + getWhatsAppLink: (message?: string) => string; +} + +const DataContext = createContext(undefined); + +export const DataProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [data, setData] = useState(INITIAL_DATA); + const [isLoading, setIsLoading] = useState(true); + + // Carregar dados ao iniciar + useEffect(() => { + loadData(); + }, []); + + const loadData = async () => { + try { + if (isSupabaseConfigured()) { + const { data: dbData, error } = await supabase.from('site_content').select('*').single(); + if (dbData && !error) { + // Merge with initial data to ensure new fields are present if DB is old + setData(prev => ({ + ...INITIAL_DATA, + ...dbData.content, + texts: { ...INITIAL_DATA.texts, ...dbData.content.texts }, + header: { ...INITIAL_DATA.header, ...dbData.content.header }, + footer: { ...INITIAL_DATA.footer, ...dbData.content.footer } + })); + } + } else { + const localData = localStorage.getItem('car_site_data'); + if (localData) { + const parsedData = JSON.parse(localData); + setData(prev => ({ + ...INITIAL_DATA, + ...parsedData, + texts: { ...INITIAL_DATA.texts, ...parsedData.texts }, + header: { ...INITIAL_DATA.header, ...parsedData.header }, + footer: { ...INITIAL_DATA.footer, ...parsedData.footer } + })); + } + } + } catch (error) { + console.error("Erro ao carregar dados", error); + } finally { + setIsLoading(false); + } + }; + + const updateData = async (section: keyof DataType, newData: any) => { + const updatedFullData = { ...data, [section]: newData }; + setData(updatedFullData); + + if (isSupabaseConfigured()) { + await supabase.from('site_content').upsert({ id: 1, content: updatedFullData }); + } else { + localStorage.setItem('car_site_data', JSON.stringify(updatedFullData)); + } + }; + + const getWhatsAppLink = (message: string = '') => { + const phone = data.settings.whatsappNumber.replace(/\D/g, ''); + const encodedMessage = encodeURIComponent(message); + return `https://wa.me/${phone}?text=${encodedMessage}`; + }; + + return ( + + {children} + + ); +}; + +export const useData = () => { + const context = useContext(DataContext); + if (context === undefined) { + throw new Error('useData must be used within a DataProvider'); + } + return context; +}; \ No newline at end of file diff --git a/data.ts b/data.ts new file mode 100644 index 0000000..fa7d134 --- /dev/null +++ b/data.ts @@ -0,0 +1,373 @@ +import { NavItem, Service, PromiseItem, Package, Stat, Testimonial, TeamMember, FaqItem, BlogPost, SpecialOffer, WhyChooseItem, GlobalSettings, SectionVisibility, ClientLogo, SectionTexts, HeaderConfig, FooterConfig, BeforeAfterItem } from './types'; + +// Mantemos NAV_ITEMS como fallback ou para inicialização +export const NAV_ITEMS: NavItem[] = [ + { label: 'Início', href: '/' }, + { label: 'Sobre', href: '/sobre' }, + { label: 'Promoções', href: '/promocoes' }, + { label: 'Serviços', href: '/servicos' }, + { label: 'Blog', href: '/blog' }, + { label: 'Contato', href: '/contato' }, +]; + +export const PROMISES: PromiseItem[] = [ + { icon: 'Search', title: 'Transparência Total', description: 'Você acompanha cada etapa do reparo.' }, + { icon: 'ShieldCheck', title: 'Garantia Vitalícia', description: 'Garantia em serviços selecionados.' }, + { icon: 'Award', title: 'Equipe Certificada', description: 'Mecânicos treinados nas melhores montadoras.' }, + { icon: 'Clock', title: 'Entrega Pontual', description: 'Respeitamos seu tempo rigorosamente.' }, + { icon: 'Wrench', title: 'Peças Originais', description: 'Utilizamos apenas peças de reposição de qualidade.' }, + { icon: 'Users', title: 'Atendimento Premium', description: 'Sala de espera VIP e café enquanto aguarda.' }, +]; + +export const SERVICES: Service[] = [ + { id: 1, title: 'Troca de Óleo', description: 'Lubrificantes de alta performance para maior durabilidade.', image: 'https://images.unsplash.com/photo-1487754180451-c456f719a1fc?auto=format&fit=crop&q=80&w=600' }, + { id: 2, title: 'Freios e ABS', description: 'Diagnóstico completo e troca de pastilhas e discos.', image: 'https://images.unsplash.com/photo-1486262715619-67b85e0b08d3?auto=format&fit=crop&q=80&w=600' }, + { id: 3, title: 'Motor e Câmbio', description: 'Reparos complexos, retífica e manutenção preventiva.', image: 'https://images.unsplash.com/photo-1619642751034-765dfdf7c58e?auto=format&fit=crop&q=80&w=600' }, + { id: 4, title: 'Ar-condicionado', description: 'Higienização, carga de gás e reparo de compressores.', image: 'https://images.unsplash.com/photo-1504222490245-4366323443c8?auto=format&fit=crop&q=80&w=600' }, + { id: 5, title: 'Bateria e Elétrica', description: 'Check-up elétrico completo e troca de baterias.', image: 'https://images.unsplash.com/photo-1503376763036-066120622c74?auto=format&fit=crop&q=80&w=600' }, + { id: 6, title: 'Suspensão e Pneus', description: 'Alinhamento 3D, balanceamento e amortecedores.', image: 'https://images.unsplash.com/photo-1580273916550-e323be2ed532?auto=format&fit=crop&q=80&w=600' }, +]; + +export const PACKAGES: Package[] = [ + { + name: 'Básico', + price: 170, + features: ['Troca de Óleo (até 4L)', 'Filtro de Óleo', 'Check-up de 15 itens', 'Lavagem Externa Simples'], + recommended: false, + }, + { + name: 'Padrão', + price: 250, + features: ['Tudo do Básico', 'Filtro de Ar', 'Alinhamento e Balanceamento', 'Rodízio de Pneus', 'Higienização de Ar'], + recommended: true, + }, + { + name: 'Premium', + price: 350, + features: ['Tudo do Padrão', 'Cristalização de Vidros', 'Polimento de Faróis', 'Check-up Completo (50 itens)', 'Leva e Traz (Raio 10km)'], + recommended: false, + }, +]; + +export const SPECIAL_OFFERS: SpecialOffer[] = [ + { + id: 1, + title: 'Kit 4 Pneus Aro 13 Westlake Zuper Eco Z-108 175/70R13 82T', + image: 'https://images.unsplash.com/photo-1578844251758-2f71da64522f?auto=format&fit=crop&q=80&w=400', + price: 'R$ 899,90', + installment: '12x de R$ 88,22 sem juros', + rating: 4.8, + reviews: 262, + specs: { fuel: 'E', grip: 'E', noise: '70dB' } + }, + { + id: 2, + title: 'Kit 4 Pneus Aro 14 Pirelli Cinturato P1 175/65R14 82T', + image: 'https://images.unsplash.com/photo-1578844251758-2f71da64522f?auto=format&fit=crop&q=80&w=400', + price: 'R$ 1.199,90', + installment: '12x de R$ 117,63 sem juros', + rating: 4.9, + reviews: 415, + specs: { fuel: 'C', grip: 'C', noise: '69dB' } + }, + { + id: 3, + title: 'Kit 4 Pneus Aro 15 Michelin Primacy 4 195/60R15 88V', + image: 'https://images.unsplash.com/photo-1578844251758-2f71da64522f?auto=format&fit=crop&q=80&w=400', + price: 'R$ 2.450,00', + installment: '12x de R$ 240,19 sem juros', + rating: 5.0, + reviews: 189, + specs: { fuel: 'B', grip: 'A', noise: '68dB' } + }, + { + id: 4, + title: 'Kit 4 Pneus Aro 16 Bridgestone Turanza 205/55R16 91V', + image: 'https://images.unsplash.com/photo-1578844251758-2f71da64522f?auto=format&fit=crop&q=80&w=400', + price: 'R$ 2.890,90', + installment: '12x de R$ 283,42 sem juros', + rating: 4.7, + reviews: 310, + specs: { fuel: 'E', grip: 'C', noise: '71dB' } + } +]; + +export const GALLERY_IMAGES = [ + 'https://images.unsplash.com/photo-1492144534655-ae79c964c9d7?auto=format&fit=crop&q=80&w=500', + 'https://images.unsplash.com/photo-1597505294881-133d26a1b212?auto=format&fit=crop&q=80&w=500', + 'https://images.unsplash.com/photo-1615906655593-ad0386982a0f?auto=format&fit=crop&q=80&w=500', + 'https://images.unsplash.com/photo-1494976388531-d1058494cdd8?auto=format&fit=crop&q=80&w=500', + 'https://images.unsplash.com/photo-1530046339160-71153320c0f6?auto=format&fit=crop&q=80&w=500', + 'https://images.unsplash.com/photo-1517524008697-84bbe3c3fd98?auto=format&fit=crop&q=80&w=500', + 'https://images.unsplash.com/photo-1583121274602-3e2820c69888?auto=format&fit=crop&q=80&w=500', + 'https://images.unsplash.com/photo-1517026575992-5e117c86699f?auto=format&fit=crop&q=80&w=500', +]; + +export const BEFORE_AFTER_ITEMS: BeforeAfterItem[] = [ + { + id: 1, + title: "Martelinho de Ouro", + imageBefore: "https://images.unsplash.com/photo-1626856557876-2e8c14040956?auto=format&fit=crop&q=80&w=800", + imageAfter: "https://images.unsplash.com/photo-1626856557833-255017006841?auto=format&fit=crop&q=80&w=800" + }, + { + id: 2, + title: "Polimento Cristalizado", + imageBefore: "https://images.unsplash.com/photo-1601362840469-51e4d8d58785?auto=format&fit=crop&q=80&w=800", + imageAfter: "https://images.unsplash.com/photo-1601362840464-5ea2b733b82d?auto=format&fit=crop&q=80&w=800" + } +]; + +export const STATS: Stat[] = [ + { label: 'Anos de Experiência', value: '20+' }, + { label: 'Carros Reparados', value: '3500+' }, + { label: 'Técnicos Especialistas', value: '15+' }, + { label: 'Clientes Satisfeitos', value: '3000+' }, +]; + +export const TESTIMONIALS: Testimonial[] = [ + { name: 'Carlos Mendes', role: 'Empresário', stars: 5, text: 'O atendimento foi impecável. Resolveram o problema do meu carro que outras três oficinas não conseguiram.', image: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&q=80&w=100' }, + { name: 'Fernanda Lima', role: 'Médica', stars: 5, text: 'Rápido, limpo e transparente. Adorei receber as fotos das peças trocadas pelo WhatsApp.', image: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&q=80&w=100' }, + { name: 'Roberto Silva', role: 'Motorista Uber', stars: 5, text: 'Melhor custo-benefício da região. O pacote de revisão padrão salvou meu orçamento.', image: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=crop&q=80&w=100' }, +]; + +export const TEAM: TeamMember[] = [ + { name: 'João Ferreira', role: 'Engenheiro Mecânico Chefe', image: 'https://images.unsplash.com/photo-1560250097-0b93528c311a?auto=format&fit=crop&q=80&w=300' }, + { name: 'Ana Souza', role: 'Especialista em Elétrica', image: 'https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?auto=format&fit=crop&q=80&w=300' }, + { name: 'Marcos Oliveira', role: 'Técnico de Suspensão', image: 'https://images.unsplash.com/photo-1530268729831-4b0b9e170218?auto=format&fit=crop&q=80&w=300' }, + { name: 'Ricardo Santos', role: 'Gerente de Oficina', image: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?auto=format&fit=crop&q=80&w=300' }, +]; + +export const FAQS: FaqItem[] = [ + { question: 'Quanto tempo leva uma revisão básica?', answer: 'Geralmente, uma revisão básica (troca de óleo e filtros) é realizada em cerca de 45 a 60 minutos.' }, + { question: 'Vocês trabalham com todas as seguradoras?', answer: 'Sim, atendemos as principais seguradoras do mercado para reparos de funilaria e pintura.' }, + { question: 'Oferecem garantia nos serviços?', answer: 'Todos os nossos serviços possuem garantia mínima de 3 meses, e algumas peças têm garantia vitalícia do fabricante.' }, + { question: 'Posso agendar online?', answer: 'Com certeza! Utilize o botão "Agendar Agora" no topo do site para escolher o melhor horário.' }, + { question: 'Aceitam quais formas de pagamento?', answer: 'Aceitamos cartões de crédito em até 10x, débito, PIX e dinheiro.' }, + { question: 'Buscam o carro em casa?', answer: 'Sim, possuímos serviço de Leva e Traz em um raio de 10km da oficina.' }, +]; + +export const BLOG_POSTS: BlogPost[] = [ + { + id: 1, + title: 'Sinais que seu freio precisa de atenção', + excerpt: 'Chiados, pedal baixo ou vibrações? Saiba identificar os problemas antes que seja tarde.', + content: ` +

O sistema de freios é, sem dúvida, o componente de segurança mais crítico do seu veículo. Ignorar os primeiros sinais de desgaste pode levar a reparos caros e, pior, a situações perigosas na estrada. Aqui estão os principais sinais de que seus freios precisam de uma visita à oficina:

+ +

1. Ruídos Estranhos

+

Se você ouvir um guincho agudo ao frear, é provável que o indicador de desgaste das pastilhas esteja tocando o disco. Já um som de moagem metálica indica que as pastilhas acabaram totalmente e o metal está raspando no metal - uma emergência imediata.

+ +

2. Vibração no Pedal ou Volante

+

Sentir o pedal do freio ou o volante tremer ao frear geralmente indica discos de freio empenados. Isso ocorre devido ao superaquecimento ou desgaste irregular.

+ +

3. Pedal "Baixo" ou "Mole"

+

Se você precisa pisar até o fundo para o carro parar, pode haver ar no sistema hidráulico, vazamento de fluido ou desgaste excessivo das pastilhas.

+ +

Na CAR Auto Center, realizamos uma inspeção completa do sistema de freios gratuitamente. Agende sua revisão hoje mesmo!

+ `, + image: 'https://images.unsplash.com/photo-1486262715619-67b85e0b08d3?auto=format&fit=crop&q=80&w=800', + date: '12 Out 2023' + }, + { + id: 2, + title: 'Como economizar combustível na cidade', + excerpt: 'Dicas práticas de direção e manutenção que podem reduzir seu consumo em até 20%.', + content: ` +

Com o preço do combustível em alta, cada quilômetro conta. Felizmente, pequenas mudanças nos hábitos de direção e na manutenção do veículo podem gerar uma economia significativa no final do mês.

+ +

Mantenha os Pneus Calibrados

+

Pneus murchos aumentam a resistência à rolagem, forçando o motor a trabalhar mais. Verifique a calibração semanalmente.

+ +

Evite Acelerações Bruscas

+

Sair do sinal verde acelerando fundo consome muito mais combustível do que uma aceleração gradual. Tente manter uma velocidade constante sempre que possível.

+ +

Troque as Marchas no Tempo Certo

+

Esticar demais as marchas eleva o giro do motor desnecessariamente. Consulte o manual do proprietário para saber a rotação ideal de troca.

+ +

Manutenção em Dia

+

Velas desgastadas, filtros de ar sujos e óleo velho podem aumentar o consumo em até 10%. Mantenha suas revisões em dia com a CAR Auto Center.

+ `, + image: 'https://images.unsplash.com/photo-1616422285623-13ff0162193c?auto=format&fit=crop&q=80&w=800', + date: '05 Nov 2023' + }, + { + id: 3, + title: 'A importância da troca de óleo regular', + excerpt: 'Entenda por que o óleo é o sangue do motor e o que acontece se você atrasar a troca.', + content: ` +

O óleo do motor tem funções vitais: lubrificar, limpar, resfriar e vedar as partes internas do motor. Com o tempo, ele perde suas propriedades e acumula impurezas.

+ +

O que acontece se não trocar?

+

O óleo velho se transforma em uma borra espessa que entope os canais de lubrificação. Isso causa atrito excessivo, superaquecimento e pode levar à fundição do motor.

+ +

Sintético vs. Mineral

+

Sempre respeite a especificação da montadora. Óleos sintéticos tendem a ter maior durabilidade e oferecer melhor proteção em altas temperaturas.

+ +

Não arrisque o coração do seu carro. A troca de óleo é o serviço de manutenção preventiva mais barato e importante que você pode fazer.

+ `, + image: 'https://images.unsplash.com/photo-1487754180451-c456f719a1fc?auto=format&fit=crop&q=80&w=800', + date: '20 Nov 2023' + }, +]; + +export const WHY_CHOOSE_ITEMS: WhyChooseItem[] = [ + { icon: 'Settings', text: 'Equipamentos de diagnóstico de última geração' }, + { icon: 'ShieldCheck', text: 'Certificação ISO 9001 de Qualidade' }, + { icon: 'Users', text: 'Transparência total com o cliente' }, + { icon: 'Clock', text: 'Atendimento 24h para emergências' }, + { icon: 'Droplet', text: 'Descarte ecológico de fluidos e peças' }, +]; + +export const CLIENT_LOGOS: ClientLogo[] = [ + { name: 'LEAR', url: '' }, + { name: 'SMART', url: '' }, + { name: 'BOSCH', url: '' }, + { name: 'PIRELLI', url: '' }, + { name: 'MOTUL', url: '' }, +]; + +export const DEFAULT_SETTINGS: GlobalSettings = { + siteName: 'CAR Auto Center', + logoUrl: '', // Se vazio, usa texto + faviconUrl: '', + primaryColor: '#FF6200', + whatsappNumber: '5511999999999', + contactEmail: 'contato@carautocenter.com.br', + contactPhoneDisplay: '(11) 99999-9999', + address: 'Av. Paulista, 1000 - Bela Vista, São Paulo - SP', + social: { + instagram: '#', + facebook: '#', + twitter: '#', + youtube: '', + linkedin: '' + } +}; + +// Aqui fazemos a injeção do HTML no título inicial para colorir "Automotivos" +const INITIAL_HERO_TITLE = "Manutenção e Reparos Automotivos"; + +export const DEFAULT_VISIBILITY: SectionVisibility = { + hero: true, + specialOffers: true, + about: true, + services: true, + bigCta: true, + packages: true, + gallery: true, + beforeAfter: true, // Nova visibilidade padrão + stats: true, + whyChoose: true, + testimonials: true, + team: true, + faq: true, + blog: true, + contact: true, + clients: true, +}; + +export const DEFAULT_TEXTS: SectionTexts = { + hero: { + feature1: "Oficina Aberta Agora", + feature2: "Atendemos Blindados", + feature3: "Importados & Nacionais" + }, + specialOffers: { + badge: "Super Ofertas", + title: "Promoção de Pneus", + subtitle: "Estoque limitado. Garanta o seu!" + }, + about: { + subtitle: "Sobre Nós", + title: "O Que Prometemos Para Você", + badgeYear: "20+", + badgeText: "Anos de Experiência no Mercado" + }, + services: { + subtitle: "O Que Fazemos", + title: "Nossos Serviços" + }, + packages: { + subtitle: "Preços Claros", + title: "Pacotes de Revisão" + }, + gallery: { + subtitle: "Nosso Trabalho", + title: "Galeria de Fotos" + }, + beforeAfter: { + subtitle: "Resultados Reais", + title: "Antes e Depois" + }, + whyChoose: { + subtitle: "Diferenciais", + title: "Por Que Escolher a CAR?" + }, + testimonials: { + subtitle: "Avaliações", + title: "O Que Dizem Nossos Clientes" + }, + team: { + subtitle: "Especialistas", + title: "Nossa Equipe" + }, + faq: { + subtitle: "Dúvidas", + title: "Perguntas Frequentes" + }, + blog: { + subtitle: "Notícias", + title: "Dicas e Artigos Recentes" + }, + contact: { + subtitle: "Fale Conosco", + title: "Entre em Contato", + description: "Preencha o formulário para agendamento ou dúvidas. Nossa equipe responderá em até 30 minutos." + }, + bigCta: { + title: "Precisa de Ajuda Imediata?", + subtitle: "Atendimento de emergência e guincho 24 horas. Clique para chamar.", + buttonText: "24/7" + } +}; + +export const DEFAULT_HEADER: HeaderConfig = { + items: NAV_ITEMS, + ctaButton: { + text: "Agendar Agora", + url: "", + show: true + } +}; + +export const DEFAULT_FOOTER: FooterConfig = { + description: "Sua oficina de confiança para manutenção e reparos automotivos de alta performance.", + columns: [ + { + id: "col_services", + title: "Serviços", + type: "services_dynamic" + }, + { + id: "col_links", + title: "Links Rápidos", + type: "custom", + links: [ + { label: "Sobre Nós", href: "/sobre" }, + { label: "Pacotes & Promoções", href: "/promocoes" }, + { label: "Blog", href: "/blog" }, + { label: "Contato", href: "/contato" } + ] + }, + { + id: "col_hours", + title: "Horário", + type: "hours" + } + ], + bottomLinks: [ + { label: "Política de Privacidade", href: "#" }, + { label: "Termos de Uso", href: "#" } + ] +}; \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..005c8ee --- /dev/null +++ b/index.html @@ -0,0 +1,56 @@ + + + + + + CAR Auto Center - Manutenção e Reparos + + + + + + + +
+ + \ No newline at end of file diff --git a/index.tsx b/index.tsx new file mode 100644 index 0000000..32f1b7f --- /dev/null +++ b/index.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { HashRouter } from 'react-router-dom'; +import App from './App'; +import { DataProvider } from './contexts/DataContext'; + +const rootElement = document.getElementById('root'); +if (!rootElement) { + throw new Error("Could not find root element to mount to"); +} + +const root = ReactDOM.createRoot(rootElement); +root.render( + + + + + + + +); \ No newline at end of file diff --git a/lib/supabase.ts b/lib/supabase.ts new file mode 100644 index 0000000..0cd8ae1 --- /dev/null +++ b/lib/supabase.ts @@ -0,0 +1,18 @@ +import { createClient } from '@supabase/supabase-js'; + +// NOTA: Em produção, estas variáveis devem estar no .env +// Para teste imediato, se as variáveis não existirem, o sistema usará localStorage + +// Safely access environment variables +const envUrl = (import.meta as any).env?.VITE_SUPABASE_URL; +const envKey = (import.meta as any).env?.VITE_SUPABASE_ANON_KEY; + +// Fallback to placeholder values to prevent createClient from throwing "supabaseUrl is required" error +const supabaseUrl = envUrl || 'https://placeholder.supabase.co'; +const supabaseAnonKey = envKey || 'placeholder'; + +export const supabase = createClient(supabaseUrl, supabaseAnonKey); + +export const isSupabaseConfigured = () => { + return !!envUrl && !!envKey && envUrl !== '' && envKey !== ''; +}; \ No newline at end of file diff --git a/metadata.json b/metadata.json new file mode 100644 index 0000000..160f266 --- /dev/null +++ b/metadata.json @@ -0,0 +1,5 @@ +{ + "name": "CAR Auto Center", + "description": "Centro automotivo completo com agendamento online e serviços 24h.", + "requestFramePermissions": [] +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..784b948 --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "car-auto-center", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.2.4", + "react-dom": "^19.2.4", + "clsx": "^2.1.1", + "lucide-react": "^0.564.0", + "tailwind-merge": "^3.4.1", + "react-router-dom": "^7.13.0", + "@supabase/supabase-js": "^2.95.3" + }, + "devDependencies": { + "@types/node": "^22.14.0", + "@vitejs/plugin-react": "^5.0.0", + "typescript": "~5.8.2", + "vite": "^6.2.0" + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..2c6eed5 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "target": "ES2022", + "experimentalDecorators": true, + "useDefineForClassFields": false, + "module": "ESNext", + "lib": [ + "ES2022", + "DOM", + "DOM.Iterable" + ], + "skipLibCheck": true, + "types": [ + "node" + ], + "moduleResolution": "bundler", + "isolatedModules": true, + "moduleDetection": "force", + "allowJs": true, + "jsx": "react-jsx", + "paths": { + "@/*": [ + "./*" + ] + }, + "allowImportingTsExtensions": true, + "noEmit": true + } +} \ No newline at end of file diff --git a/types.ts b/types.ts new file mode 100644 index 0000000..c2ec800 --- /dev/null +++ b/types.ts @@ -0,0 +1,223 @@ +export interface NavItem { + label: string; + href: string; +} + +export interface Service { + id: number; + title: string; + description: string; + image: string; +} + +export interface PromiseItem { + icon: string; // Changed from LucideIcon to string for JSON storage + title: string; + description: string; +} + +export interface Package { + name: string; + price: number; + features: string[]; + recommended?: boolean; +} + +export interface Stat { + label: string; + value: string; +} + +export interface Testimonial { + name: string; + role: string; + image: string; + text: string; + stars: number; +} + +export interface TeamMember { + name: string; + role: string; + image: string; +} + +export interface FaqItem { + question: string; + answer: string; +} + +export interface BlogPost { + id: number; // Adicionado ID para roteamento + title: string; + image: string; + excerpt: string; + content: string; // Adicionado conteúdo completo + date: string; +} + +export interface SpecialOffer { + id: number; + title: string; + image: string; + price: string; + installment: string; + rating: number; + reviews: number; + specs: { + fuel: string; + grip: string; + noise: string; + }; +} + +export interface ClientLogo { + name: string; + url: string; +} + +export interface WhyChooseItem { + icon: string; + text: string; +} + +// Novo Tipo para Antes e Depois +export interface BeforeAfterItem { + id: number; + title: string; + imageBefore: string; + imageAfter: string; +} + +export interface GlobalSettings { + siteName: string; + logoUrl: string; // URL for the logo image + faviconUrl: string; + primaryColor: string; + whatsappNumber: string; // Only numbers, e.g., 5511999999999 + contactEmail: string; // For form submissions + contactPhoneDisplay: string; // Formatted phone for display + address: string; + social: { + instagram: string; + facebook: string; + twitter: string; + youtube: string; + linkedin: string; + }; +} + +export interface SectionVisibility { + hero: boolean; + specialOffers: boolean; + about: boolean; + services: boolean; + bigCta: boolean; + packages: boolean; + gallery: boolean; + beforeAfter: boolean; // Nova seção + stats: boolean; + whyChoose: boolean; + testimonials: boolean; + team: boolean; + faq: boolean; + blog: boolean; + contact: boolean; + clients: boolean; +} + +// Novos tipos para textos editáveis +export interface SectionTexts { + hero: { + feature1: string; + feature2: string; + feature3: string; + }; + specialOffers: { + badge: string; + title: string; + subtitle: string; + }; + about: { + subtitle: string; + title: string; + badgeYear: string; + badgeText: string; + }; + services: { + subtitle: string; + title: string; + }; + packages: { + subtitle: string; + title: string; + }; + gallery: { + subtitle: string; + title: string; + }; + // Nova seção de textos + beforeAfter: { + subtitle: string; + title: string; + }; + whyChoose: { + subtitle: string; + title: string; + }; + testimonials: { + subtitle: string; + title: string; + }; + team: { + subtitle: string; + title: string; + }; + faq: { + subtitle: string; + title: string; + }; + blog: { + subtitle: string; + title: string; + }; + contact: { + subtitle: string; + title: string; + description: string; + }; + bigCta: { + title: string; + subtitle: string; + buttonText: string; + } +} + +// Configuração do Header +export interface HeaderConfig { + items: NavItem[]; + ctaButton: { + text: string; + url: string; // Se vazio, usa a lógica padrão do WhatsApp + show: boolean; + }; +} + +// Configuração do Footer +export interface FooterLink { + label: string; + href: string; +} + +export interface FooterColumn { + id: string; // Identificador único para ajudar na renderização/edição + title: string; + type: 'custom' | 'services_dynamic' | 'hours'; // Tipo de coluna + links?: FooterLink[]; // Apenas usado se type === 'custom' +} + +export interface FooterConfig { + description: string; + columns: FooterColumn[]; + bottomLinks: FooterLink[]; // Links de privacidade, termos, etc. +} \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..ee5fb8d --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,23 @@ +import path from 'path'; +import { defineConfig, loadEnv } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, '.', ''); + return { + server: { + port: 3000, + host: '0.0.0.0', + }, + plugins: [react()], + define: { + 'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY), + 'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY) + }, + resolve: { + alias: { + '@': path.resolve(__dirname, '.'), + } + } + }; +});