diff --git a/web/src/components/ShopCard.tsx b/web/src/components/ShopCard.tsx index 6bbeeed..3b63d67 100644 --- a/web/src/components/ShopCard.tsx +++ b/web/src/components/ShopCard.tsx @@ -1,90 +1,90 @@ import { Link } from 'react-router-dom'; -import { Star, MapPin, Scissors, Heart, Calendar, Users } from 'lucide-react'; +import { Star, MapPin, Scissors, Heart, Calendar } from 'lucide-react'; import { BarberShop } from '../types'; import { useApp } from '../context/AppContext'; -const gradients = [ - 'from-emerald-600 to-teal-700', - 'from-violet-600 to-indigo-700', - 'from-slate-600 to-slate-800', - 'from-sky-600 to-blue-700', - 'from-rose-600 to-pink-700', - 'from-teal-500 to-cyan-700', -]; - -function shopGradient(name: string) { - return gradients[name.charCodeAt(0) % gradients.length]; -} - -export const ShopCard = ({ shop }: { shop: BarberShop }) => { +export const ShopCard = ({ shop, compact = false }: { shop: BarberShop; compact?: boolean }) => { const { toggleFavorite, isFavorite } = useApp(); const favorite = isFavorite(shop.id); - const initials = shop.name.slice(0, 2).toUpperCase(); return ( -
- {/* Header */} -
- {/* Avatar */} -
-
- {shop.imageUrl ? ( - {shop.name} - ) : ( - {initials} - )} -
- {/* Rating */} -
- - - {shop.rating ? shop.rating.toFixed(1) : '—'} - +
+ {/* Cover image */} +
+ {shop.imageUrl ? ( + {shop.name} + ) : ( +
+ +

Sem foto de capa

+ )} + {/* Gradient overlay */} +
+ + {/* Rating badge */} +
+ + + {shop.rating ? shop.rating.toFixed(1) : '—'} +
- {/* Name + address */} -
-
-

- {shop.name} -

- -
-
- -

{shop.address || 'Endereço não disponível'}

-
-
- {(shop.services || []).length} serviços - {(shop.barbers || []).length} barb. -
+ {/* Favorite button */} + + + {/* Shop name on image */} +
+

+ {shop.name} +

- {/* Divider */} -
+ {/* Info section */} +
+
+ +

{shop.address || 'Endereço não disponível'}

+
- {/* Actions */} -
- - Ver detalhes - - - - Agendar - +
+ + + {(shop.services || []).length} serviços + + + {(shop.barbers || []).length} barbeiros +
+ + {/* Action buttons */} +
+ + Ver detalhes + + + + Agendar + +
); diff --git a/web/src/components/layout/Header.tsx b/web/src/components/layout/Header.tsx index d415670..5e2f832 100644 --- a/web/src/components/layout/Header.tsx +++ b/web/src/components/layout/Header.tsx @@ -1,5 +1,5 @@ import { Link, useNavigate, useLocation } from 'react-router-dom' -import { ShoppingCart, User, LogOut, Menu, X, Scissors } from 'lucide-react' +import { ShoppingCart, User, LogOut, Menu, X, Scissors, Search } from 'lucide-react' import { useApp } from '../../context/AppContext' import { useState } from 'react' @@ -7,40 +7,54 @@ export const Header = () => { const { user, cart, logout } = useApp() const navigate = useNavigate() const location = useLocation() - const [open, setOpen] = useState(false) + const [mobileMenuOpen, setMobileMenuOpen] = useState(false) - const handleLogout = () => { logout(); navigate('/'); setOpen(false) } + const handleLogout = () => { + logout() + navigate('/') + setMobileMenuOpen(false) + } - const isActive = (path: string) => location.pathname === path + const navLink = (to: string) => + `flex items-center gap-1.5 text-sm font-medium transition-colors px-3 py-1.5 rounded-lg ${location.pathname === to + ? 'bg-amber-50 text-amber-700' + : 'text-slate-600 hover:text-amber-700 hover:bg-amber-50' + }` return ( -
-
+
+
{/* Logo */} - setOpen(false)} className="flex items-center gap-2"> -
- + setMobileMenuOpen(false)} + className="flex items-center gap-2 group" + > +
+
- SmartAgenda + SmartAgenda - {/* Desktop nav */} + {/* Desktop Navigation */} - {/* Mobile */} + {/* Mobile right area */}
{user?.role !== 'barbearia' && ( - - + + {cart.length > 0 && ( - + {cart.length} )} )} -
- {/* Mobile menu */} - {open && ( -
-
+ {/* Mobile Menu */} + {mobileMenuOpen && ( +
+
+
)}
diff --git a/web/src/components/layout/Shell.tsx b/web/src/components/layout/Shell.tsx index 1587cef..2715105 100644 --- a/web/src/components/layout/Shell.tsx +++ b/web/src/components/layout/Shell.tsx @@ -5,7 +5,7 @@ export function Shell() { return ( <>
-
+
diff --git a/web/src/index.css b/web/src/index.css index 48ce529..daada6f 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -1,4 +1,4 @@ -@import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,400;0,14..32,500;0,14..32,600;0,14..32,700;0,14..32,800;0,14..32,900;1,14..32,400&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap'); @tailwind base; @tailwind components; @@ -14,14 +14,16 @@ } body { - font-family: 'Inter', system-ui, sans-serif; + font-family: 'Inter', system-ui, -apple-system, sans-serif; @apply bg-slate-50 text-slate-900 antialiased; + font-feature-settings: 'cv02', 'cv03', 'cv04', 'cv11'; } a { @apply text-inherit no-underline; } + /* Scrollbar styling */ ::-webkit-scrollbar { @apply w-1.5 h-1.5; } @@ -33,4 +35,19 @@ ::-webkit-scrollbar-thumb { @apply bg-slate-300 rounded-full hover:bg-slate-400; } -} \ No newline at end of file +} + +@layer utilities { + .text-balance { + text-wrap: balance; + } + + /* Smooth fade-in animation for modals */ + @keyframes fadeIn { + from { opacity: 0; transform: translateY(8px); } + to { opacity: 1; transform: translateY(0); } + } + .animate-fadeIn { + animation: fadeIn 0.25s ease-out forwards; + } +} diff --git a/web/src/pages/AuthLogin.tsx b/web/src/pages/AuthLogin.tsx index 1183f11..3a0c254 100644 --- a/web/src/pages/AuthLogin.tsx +++ b/web/src/pages/AuthLogin.tsx @@ -1,106 +1,120 @@ +/** + * @file AuthLogin.tsx + * @description Página de Autenticação (Login) para a versão Web da aplicação. + * Permite aos utilizadores (clientes e barbearias) entrarem na sua conta + * através de credenciais de email e palavra-passe, ligando-se ao fluxo de + * sessões do Supabase. + */ import { FormEvent, useEffect, useState } from 'react' import { useNavigate, Link } from 'react-router-dom' -import { LogIn, Scissors, Eye, EyeOff } from 'lucide-react' +import { Input } from '../components/ui/input' +import { Button } from '../components/ui/button' +import { Card } from '../components/ui/card' +import { LogIn } from 'lucide-react' import { supabase } from '../lib/supabase' import { useApp } from '../context/AppContext' export default function AuthLogin() { const [email, setEmail] = useState('') const [password, setPassword] = useState('') - const [showPw, setShowPw] = useState(false) const [error, setError] = useState('') const [loading, setLoading] = useState(false) + + // Utilização do hook do react-router-dom para navegação entre páginas web const navigate = useNavigate() const { user } = useApp() + /** + * Hook executado na montagem inicial do componente. + * Interage diretamente com o AppContext para verificar + * de forma reativa se o utilizador já está logado. + * Se os dados do utilizador confirmarem autenticação ativa, redireciona o fluxo consoante a role. + */ useEffect(() => { - if (user) navigate(user.role === 'barbearia' ? '/painel' : '/explorar', { replace: true }) + if (user) { + navigate(user.role === 'barbearia' ? '/painel' : '/explorar', { replace: true }) + } }, [user, navigate]) + /** + * Manipula a submissão do formulário na view para validar as credenciais. + * @param {FormEvent} e - Evento padrão de submissão form para prevenir comportamento `submit` comum. + */ const handleLogin = async (e: FormEvent) => { e.preventDefault() setError('') setLoading(true) + try { - const { data, error } = await supabase.auth.signInWithPassword({ email, password }) + // Comunicação via API REST do Supabase para Login + // A biblioteca gera pedido POST com as credenciais (Email e JSON PW) para endpoint /token + const { data, error } = await supabase.auth.signInWithPassword({ + email, + password, + }) if (error) throw error + + // Sucesso na verificação origina redirecionamento baseado na rule (metadados guardados no Auth) const role = data.user?.user_metadata?.role - navigate(role === 'barbearia' ? '/painel' : '/explorar', { replace: true }) - } catch { - setError('Email ou palavra-passe incorretos.') + if (role) { + navigate(role === 'barbearia' ? '/painel' : '/explorar', { replace: true }) + } + } catch (e: any) { + setError('Credenciais inválidas ou email não confirmado') setLoading(false) } } return ( -
-
- {/* Logo */} -
-
- +
+ +
+
+
-

Bem-vindo de volta

-

Aceda à sua conta SmartAgenda

+

Entrar

+

Aceda à sua conta

-
- {error && ( -
- {error} -
- )} + {error && ( +
+ {error} +
+ )} -
-
- - setEmail(e.target.value)} - placeholder="seu@email.com" - required - className="w-full px-3.5 py-2.5 rounded-xl border border-slate-200 text-sm focus:outline-none focus:ring-2 focus:border-transparent placeholder:text-slate-400" - style={{ '--tw-ring-color': '#10b981' } as any} - /> -
+ + setEmail(e.target.value)} + placeholder="seu@email.com" + required + /> -
- -
- setPassword(e.target.value)} - placeholder="••••••••" - required - className="w-full px-3.5 py-2.5 pr-10 rounded-xl border border-slate-200 text-sm focus:outline-none focus:ring-2 focus:border-transparent placeholder:text-slate-400" - /> - -
-
+ setPassword(e.target.value)} + placeholder="••••••••" + required + /> - -
+ + -

+

+

Não tem conta?{' '} - - Criar conta grátis + + Criar conta

-
+
) } diff --git a/web/src/pages/AuthRegister.tsx b/web/src/pages/AuthRegister.tsx index 129d264..9d2a5a8 100644 --- a/web/src/pages/AuthRegister.tsx +++ b/web/src/pages/AuthRegister.tsx @@ -1,5 +1,14 @@ +/** + * @file AuthRegister.tsx + * @description Página de Registo para a versão Web. Permite a criação de + * novas contas segmentadas por perfil ('cliente' ou 'barbearia'). Interage + * diretamente com o serviço de autenticação do Supabase. + */ import { FormEvent, useEffect, useState } from 'react' import { Link, useNavigate } from 'react-router-dom' +import { Input } from '../components/ui/input' +import { Button } from '../components/ui/button' +import { Card } from '../components/ui/card' import { UserPlus, User, Scissors } from 'lucide-react' import { supabase } from '../lib/supabase' @@ -7,32 +16,58 @@ export default function AuthRegister() { const [name, setName] = useState('') const [email, setEmail] = useState('') const [password, setPassword] = useState('') + + // Estado local para definir as permissões associadas à conta nova const [role, setRole] = useState<'cliente' | 'barbearia'>('cliente') + + // Condicional, só inserido no metadados se o tipo de utilizador for 'barbearia' const [shopName, setShopName] = useState('') const [error, setError] = useState('') const [loading, setLoading] = useState(false) + const navigate = useNavigate() + /** + * Previne acesso à página de registo por utilizadores com Sessão ativa. + * Utiliza um padrão `mounted` para não alterar state se o componente for desmontado. + */ + // 🔐 Se já estiver logado, não pode ver o registo useEffect(() => { let mounted = true ; (async () => { + // Efetua um fetch à sessão existente injetada pelo SDK da Supabase const { data } = await supabase.auth.getSession() if (!mounted) return - if (data.session) navigate('/explorar', { replace: true }) + if (data.session) { + navigate('/explorar', { replace: true }) + } })() - return () => { mounted = false } + return () => { + mounted = false + } }, [navigate]) + /** + * Processa a criação e envio do novo perfil e das informações para a BD via Supabase Auth. + * @param {FormEvent} e - Evento de submissão do formulário. + */ async function onSubmit(e: FormEvent) { e.preventDefault() setError('') + + // Regras básicas de negócio à submissão (proteção pre-API) if (!name.trim()) return setError('Preencha o nome completo') if (!email.trim()) return setError('Preencha o email') if (!password.trim()) return setError('Preencha a senha') - if (role === 'barbearia' && !shopName.trim()) return setError('Informe o nome da barbearia') + if (role === 'barbearia' && !shopName.trim()) { + return setError('Informe o nome da barbearia') + } try { setLoading(true) + + // Registo propriamente dito perante serviço do Supabase + // Encaminha, adicionalmente, opções nos metadados (Data) da auth do serviço para mapeamento da "profile" table na BD const { data, error } = await supabase.auth.signUp({ email, password, @@ -40,17 +75,31 @@ export default function AuthRegister() { data: { name: name.trim(), role, + // Apenas vincula shopName no json object se a rule aplicável confirmar que a role o exige + // Envia ambas as formatações (camelCase para web, snake_case para o trigger SQL) shopName: role === 'barbearia' ? shopName.trim() : null, shop_name: role === 'barbearia' ? shopName.trim() : null, }, }, }) + if (error) throw error + + // 🔔 Se confirmação de email estiver ON (verificação pendente via SMTP em Supabase Configs) if (!data.session) { - navigate('/login', { replace: true, state: { msg: 'Conta criada! Confirme o email antes de fazer login.' } }) + navigate('/login', { + replace: true, + state: { + msg: 'Conta criada! Confirma o email antes de fazer login.', + }, + }) return } - navigate(role === 'barbearia' ? '/painel' : '/explorar', { replace: true }) + + // ✅ Login automático: Rotas divergentes de acordo com a função Role + navigate(role === 'barbearia' ? '/painel' : '/explorar', { + replace: true, + }) } catch (err: any) { setError(err?.message || 'Erro ao criar conta') } finally { @@ -58,91 +107,115 @@ export default function AuthRegister() { } } - const field = (label: string, node: React.ReactNode) => ( -
- - {node} -
- ) - const inputCls = "w-full px-3.5 py-2.5 rounded-xl border border-slate-200 text-sm focus:outline-none placeholder:text-slate-400" - return ( -
-
- {/* Logo */} -
-
- +
+ +
+
+
-

Criar conta

-

Escolha o seu perfil e comece já

+

+ Criar conta +

+

+ Escolha o tipo de acesso +

-
- {error && ( -
- {error} + {error && ( +
+ {error} +
+ )} + +
+ {/* Tipo de conta */} +
+ +
+ {(['cliente', 'barbearia'] as const).map((r) => ( + + ))}
+
+ + setName(e.target.value)} + placeholder="João Silva" + required + /> + + setEmail(e.target.value)} + placeholder="seu@email.com" + required + /> + + setPassword(e.target.value)} + placeholder="••••••••" + required + /> + + {role === 'barbearia' && ( + setShopName(e.target.value)} + placeholder="Barbearia XPTO" + required + /> )} - - {/* Tipo de conta */} -
- -
- {(['cliente', 'barbearia'] as const).map(r => ( - - ))} -
-
+ +
- {field('Nome completo', - setName(e.target.value)} placeholder="João Silva" required className={inputCls} /> - )} - {field('Email', - setEmail(e.target.value)} placeholder="seu@email.com" required className={inputCls} /> - )} - {field('Palavra-passe', - setPassword(e.target.value)} placeholder="••••••••" required className={inputCls} /> - )} - {role === 'barbearia' && field('Nome da barbearia', - setShopName(e.target.value)} placeholder="Barbearia XPTO" required className={inputCls} /> - )} - - - - -

+

+

Já tem conta?{' '} - + Entrar

-
+
) } diff --git a/web/src/pages/Explore.tsx b/web/src/pages/Explore.tsx index 447909a..bb72084 100644 --- a/web/src/pages/Explore.tsx +++ b/web/src/pages/Explore.tsx @@ -1,117 +1,150 @@ +/** + * @file Explore.tsx + */ import { useMemo, useState } from 'react'; import { ShopCard } from '../components/ShopCard'; import { useApp } from '../context/AppContext'; -import { Search, Heart, X, SlidersHorizontal } from 'lucide-react'; +import { Search, Heart, Compass, SlidersHorizontal } from 'lucide-react'; export default function Explore() { const { shops, favorites } = useApp(); + const [query, setQuery] = useState(''); - const [filter, setFilter] = useState<'todas' | 'top' | 'produtos' | 'favoritas'>('todas'); + const [filter, setFilter] = useState<'todas' | 'top' | 'produtos' | 'barbeiros' | 'favoritas'>('todas'); const [sortBy, setSortBy] = useState<'relevancia' | 'avaliacao' | 'preco' | 'servicos'>('relevancia'); + const favoriteShops = useMemo(() => shops.filter((s) => favorites.includes(s.id)), [shops, favorites]); + const filtered = useMemo(() => { - const norm = query.trim().toLowerCase(); - const matchQ = (n: string, a: string) => !norm || n.toLowerCase().includes(norm) || a.toLowerCase().includes(norm); - const matchF = (s: (typeof shops)[0]) => { - if (filter === 'favoritas') return favorites.includes(s.id); - if (filter === 'top') return (s.rating || 0) >= 4; - if (filter === 'produtos') return (s.products || []).length > 0; + const normalized = query.trim().toLowerCase(); + const matchesQuery = (name: string, address: string) => + !normalized || name.toLowerCase().includes(normalized) || address.toLowerCase().includes(normalized); + + const passesFilter = (shop: (typeof shops)[number]) => { + if (filter === 'favoritas') return favorites.includes(shop.id); + if (filter === 'top') return (shop.rating || 0) >= 4; + if (filter === 'produtos') return (shop.products || []).length > 0; + if (filter === 'barbeiros') return (shop.barbers || []).length >= 2; return true; }; - return [...shops].filter(s => matchQ(s.name, s.address || '')).filter(matchF).sort((a, b) => { - if (sortBy === 'avaliacao') return (b.rating || 0) - (a.rating || 0); - if (sortBy === 'servicos') return (b.services || []).length - (a.services || []).length; - if (sortBy === 'preco') { - const aMin = a.services?.length ? Math.min(...a.services.map(s => s.price)) : Infinity; - const bMin = b.services?.length ? Math.min(...b.services.map(s => s.price)) : Infinity; - return aMin - bMin; - } - return (b.rating || 0) - (a.rating || 0); - }); + + return [...shops] + .filter((s) => matchesQuery(s.name, s.address || '')) + .filter(passesFilter) + .sort((a, b) => { + if (sortBy === 'avaliacao') return (b.rating || 0) - (a.rating || 0); + if (sortBy === 'servicos') return (b.services || []).length - (a.services || []).length; + if (sortBy === 'preco') { + const aMin = a.services?.length ? Math.min(...a.services.map((s) => s.price)) : Infinity; + const bMin = b.services?.length ? Math.min(...b.services.map((s) => s.price)) : Infinity; + return aMin - bMin; + } + if (b.rating !== a.rating) return (b.rating || 0) - (a.rating || 0); + return (b.services || []).length - (a.services || []).length; + }); }, [shops, query, filter, sortBy, favorites]); - const chips = [ - { id: 'todas' as const, label: 'Todas' }, - { id: 'favoritas' as const, label: `❤️ Favoritas${favorites.length > 0 ? ` (${favorites.length})` : ''}` }, - { id: 'top' as const, label: '⭐ Melhor avaliadas' }, - { id: 'produtos' as const, label: '🛍️ Com produtos' }, + const chips: { id: typeof filter; label: string; icon?: React.ReactNode }[] = [ + { id: 'todas', label: 'Todas' }, + { id: 'favoritas', label: `Favoritas${favorites.length > 0 ? ` (${favorites.length})` : ''}`, icon: }, + { id: 'top', label: 'Top avaliadas' }, + { id: 'produtos', label: 'Com produtos' }, + { id: 'barbeiros', label: 'Mais barbeiros' }, ]; return ( -
- {/* Header */} -
-

Barbearias

-

Encontre e agende na sua barbearia favorita

-
- - {/* Search */} -
- - setQuery(e.target.value)} - placeholder="Pesquisar barbearia ou endereço..." - className="w-full pl-10 pr-9 py-2.5 rounded-xl border border-slate-200 bg-white text-sm focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:border-transparent placeholder:text-slate-400" - /> - {query && ( - - )} -
- - {/* Filters */} -
- {chips.map(chip => ( - - ))} -
- - +
+ {/* Hero Header */} +
+
+
+
+ + Explorar +
+

Barbearias

+

Encontre a sua favorita e agende em minutos.

- {/* Count */} -

- {filtered.length}{' '} - {filtered.length === 1 ? 'barbearia' : 'barbearias'} -

+ {/* Search & Sort bar */} +
+
+
+ + setQuery(e.target.value)} + placeholder="Pesquisar por nome ou endereço..." + className="w-full pl-10 pr-4 py-2.5 rounded-xl border border-slate-200 text-sm focus:outline-none focus:ring-2 focus:ring-amber-400 focus:border-transparent placeholder:text-slate-400" + /> +
+
+ + +
+
+ + {/* Filter chips */} +
+ {chips.map((chip) => ( + + ))} +
+
+ + {/* Results count */} +
+

+ {filtered.length}{' '} + {filtered.length === 1 ? 'barbearia encontrada' : 'barbearias encontradas'} +

+
{/* Grid */} {filtered.length === 0 ? ( -
-
- {filter === 'favoritas' ? : } +
+
+ {filter === 'favoritas' + ? + : + }
-

- {filter === 'favoritas' ? 'Sem favoritas ainda' : 'Nenhuma barbearia encontrada'} +

+ {filter === 'favoritas' ? 'Ainda não tem favoritas' : 'Nenhuma barbearia encontrada'}

-

- {filter === 'favoritas' ? 'Clique no ❤️ de qualquer barbearia.' : 'Tente ajustar os filtros.'} +

+ {filter === 'favoritas' + ? 'Clique no ❤️ em qualquer barbearia para a guardar aqui.' + : 'Tente ajustar a pesquisa ou os filtros.'}

) : ( -
- {filtered.map(shop => )} +
+ {filtered.map((shop) => ( + + ))}
)}
diff --git a/web/src/pages/Landing.tsx b/web/src/pages/Landing.tsx index beb6a21..ad5396a 100644 --- a/web/src/pages/Landing.tsx +++ b/web/src/pages/Landing.tsx @@ -1,7 +1,22 @@ +/** + * @file Landing.tsx + * @description Página de destino (Landing Page) da aplicação web. + * Serve como vitrine promocional e porta de entrada (Login/Registo - Call to Actions). + * Redireciona autonomamente utilizadores já autenticados para as suas áreas reservadas. + */ import { Link, useNavigate } from 'react-router-dom'; -import { Calendar, ShoppingBag, BarChart3, Sparkles, Users, Clock, Shield, ArrowRight, Star, Scissors, CheckCircle2, Smartphone, TrendingUp } from 'lucide-react'; +import { Button } from '../components/ui/button'; +import { Card } from '../components/ui/card'; +import { ShopCard } from '../components/ShopCard'; +import { + Calendar, ShoppingBag, BarChart3, Sparkles, + Users, Clock, Shield, TrendingUp, CheckCircle2, + ArrowRight, Star, Quote, Scissors, MapPin, + Zap, Smartphone, Globe +} from 'lucide-react'; import { useEffect } from 'react'; import { useApp } from '../context/AppContext'; +import { mockShops } from '../data/mock'; export default function Landing() { const { user } = useApp(); @@ -9,185 +24,314 @@ export default function Landing() { useEffect(() => { if (!user) return; - navigate(user.role === 'barbearia' ? '/painel' : '/explorar', { replace: true }); + const target = user.role === 'barbearia' ? '/painel' : '/explorar'; + navigate(target, { replace: true }); }, [user, navigate]); - return ( -
- {/* Hero */} -
-
-
+ const featuredShops = mockShops.slice(0, 3); -
-
- - Plataforma de gestão para barbearias + return ( +
+ {/* Hero Section */} +
+
+
+
+ +
+
+ + Revolucione sua barbearia
-

- Agendamentos e gestão{' '} - num único lugar +

+ Agendamentos, produtos e gestão em um{' '} + único lugar

-

- A plataforma completa para barbearias e clientes — agende, gira e cresce sem complicações. +

+ Experiência mobile-first para clientes e painel completo para barbearias. + Simplifique a gestão do seu negócio e aumente sua receita.

-
- - Explorar barbearias - - - Criar conta grátis - +
+ +
{/* Stats */} -
- {[['500+', 'Barbearias'], ['10k+', 'Agendamentos'], ['4.8 ⭐', 'Avaliação média']].map(([v, l]) => ( -
-
{v}
-
{l}
-
- ))} +
+
+
500+
+
Barbearias
+
+
+
10k+
+
Agendamentos
+
+
+
4.8
+
Avaliação média
+
- {/* Features */} + {/* Features Grid */}
-
-

Tudo o que precisa

-

Funcionalidades para clientes e barbearias

+
+

+ Tudo que você precisa +

+

+ Funcionalidades poderosas para clientes e barbearias +

-
+ +
{[ - { icon: Calendar, title: 'Agendamentos', desc: 'Escolha serviço, barbeiro e horário em segundos. Acompanhe em tempo real.', color: 'text-emerald-600 bg-emerald-50' }, - { icon: ShoppingBag, title: 'Loja de Produtos', desc: 'Compre produtos da barbearia diretamente na plataforma. Entrega ou levantamento.', color: 'text-violet-600 bg-violet-50' }, - { icon: BarChart3, title: 'Painel de Gestão', desc: 'Relatórios, pedidos e agendamentos num painel completo e intuitivo.', color: 'text-sky-600 bg-sky-50' }, - { icon: Users, title: 'Gestão de Barbeiros', desc: 'Gira horários, especialidades e disponibilidade de cada barbeiro.', color: 'text-teal-600 bg-teal-50' }, - { icon: Clock, title: 'Horários Flexíveis', desc: 'Configure o funcionamento, intervalos e disponibilidade da sua barbearia.', color: 'text-indigo-600 bg-indigo-50' }, - { icon: Shield, title: 'Seguro e Fiável', desc: 'Dados protegidos com RLS e autenticação Supabase. Conformidade RGPD.', color: 'text-rose-600 bg-rose-50' }, - ].map(f => ( -
-
- + { + icon: Calendar, + title: 'Agendamentos Inteligentes', + desc: 'Escolha serviço, barbeiro, data e horário com validação de slots em tempo real. Notificações automáticas.', + color: 'from-blue-500 to-blue-600' + }, + { + icon: ShoppingBag, + title: 'Carrinho Inteligente', + desc: 'Produtos e serviços agrupados por barbearia, checkout rápido e seguro. Histórico completo de compras.', + color: 'from-emerald-500 to-emerald-600' + }, + { + icon: BarChart3, + title: 'Painel Completo', + desc: 'Faturamento, agendamentos, pedidos e análises detalhadas. Tudo no controle da sua barbearia.', + color: 'from-purple-500 to-purple-600' + }, + { + icon: Users, + title: 'Gestão de Barbeiros', + desc: 'Gerencie horários, especialidades e disponibilidade de cada barbeiro. Calendário integrado.', + color: 'from-indigo-500 to-indigo-600' + }, + { + icon: Clock, + title: 'Horários Flexíveis', + desc: 'Configure horários de funcionamento, intervalos e disponibilidade. Sistema automático de bloqueio.', + color: 'from-orange-500 to-orange-600' + }, + { + icon: Shield, + title: 'Seguro e Confiável', + desc: 'Dados protegidos, pagamentos seguros e backup automático. Conformidade com LGPD.', + color: 'from-rose-500 to-rose-600' + }, + ].map((feature) => ( + +
+
-

{f.title}

-

{f.desc}

+
+

{feature.title}

+

{feature.desc}

+
+
+ ))} +
+
+ + {/* How it Works */} +
+
+

+ Como funciona +

+

+ Simples, rápido e eficiente em 3 passos +

+
+ +
+ {[ + { step: '1', title: 'Explore', desc: 'Navegue pelas barbearias disponíveis, veja avaliações e serviços oferecidos.' }, + { step: '2', title: 'Agende', desc: 'Escolha o serviço, barbeiro e horário que melhor se adequa à sua agenda.' }, + { step: '3', title: 'Aproveite', desc: 'Compareça no horário agendado e aproveite um serviço de qualidade.' }, + ].map((item) => ( +
+
+ {item.step} +
+

{item.title}

+

{item.desc}

))}
- {/* How it works */} -
-
-

Como funciona

-

Simples e rápido em 3 passos

+ {/* Featured Shops */} +
+
+
+

+ Barbearias em destaque +

+

+ Conheça algumas das melhores barbearias da plataforma +

+
+
-
- {[ - { n: '01', t: 'Explore', d: 'Navegue pelas barbearias, veja avaliações e serviços disponíveis.' }, - { n: '02', t: 'Agende', d: 'Escolha o serviço, barbeiro e horário que se encaixa na sua agenda.' }, - { n: '03', t: 'Aproveite', d: 'Compareça no horário e desfrute de um serviço de qualidade.' }, - ].map(s => ( -
-
{s.n}
-

{s.t}

-

{s.d}

-
+ +
+ {featuredShops.map((shop) => ( + ))}
+ +
+ +
{/* Benefits */} -
- {[ - { - icon: Smartphone, color: 'text-emerald-600 bg-emerald-50', - title: 'Mobile-First', - desc: 'Interface otimizada para telemóvel. Agende de qualquer lugar, a qualquer hora.', - items: ['Design responsivo', 'Carregamento rápido', 'Interface intuitiva'], - check: 'text-emerald-600' - }, - { - icon: TrendingUp, color: 'text-violet-600 bg-violet-50', - title: 'Aumente a Receita', - desc: 'Ferramentas de análise para gerir e fazer crescer o seu negócio.', - items: ['Análises em tempo real', 'Gestão de stock', 'Relatórios detalhados'], - check: 'text-violet-600' - }, - ].map(b => ( -
-
- -
-

{b.title}

-

{b.desc}

-
    - {b.items.map(i => ( -
  • - - {i} -
  • - ))} -
+
+ +
+
- ))} +

+ Mobile-First +

+

+ Interface otimizada para dispositivos móveis. Agende de qualquer lugar, + a qualquer hora. Experiência fluida e responsiva. +

+
    + {['Design responsivo', 'Carregamento rápido', 'Interface intuitiva'].map((item) => ( +
  • + + {item} +
  • + ))} +
+
+ + +
+ +
+

+ Aumente sua Receita +

+

+ Ferramentas poderosas para gerenciar seu negócio. Análises detalhadas, + gestão de estoque e muito mais. +

+
    + {['Análises em tempo real', 'Gestão de estoque', 'Relatórios detalhados'].map((item) => ( +
  • + + {item} +
  • + ))} +
+
{/* Testimonials */}
-
-

O que dizem os clientes

+
+

+ O que nossos clientes dizem +

+

+ Depoimentos reais de quem usa a plataforma +

-
+ +
{[ - { name: 'João Silva', role: 'Cliente', text: 'Facilita muito agendar o corte. Interface simples e rápida. Recomendo!' }, - { name: 'Carlos Mendes', role: 'Proprietário', text: 'O painel é completo e ajudou muito na organização da barbearia.' }, - { name: 'Miguel Santos', role: 'Cliente', text: 'Nunca mais perco horário. As notificações são muito úteis.' }, - ].map(t => ( -
-
- {[...Array(5)].map((_, i) => )} + { + name: 'João Silva', + role: 'Cliente', + text: 'Facilita muito agendar meu corte. Interface simples e rápida. Recomendo!', + rating: 5 + }, + { + name: 'Carlos Mendes', + role: 'Proprietário', + text: 'O painel é completo e me ajuda muito na gestão. Aumentou minha organização.', + rating: 5 + }, + { + name: 'Miguel Santos', + role: 'Cliente', + text: 'Nunca mais perco horário. As notificações são muito úteis.', + rating: 5 + }, + ].map((testimonial) => ( + +
+ {[...Array(testimonial.rating)].map((_, i) => ( + + ))}
-

"{t.text}"

-
-

{t.name}

-

{t.role}

+ +

{testimonial.text}

+
+
{testimonial.name}
+
{testimonial.role}
-
+
))}
- {/* CTA */} -
-
- + {/* CTA Final */} +
+
+
+ +
+

+ Pronto para começar? +

+

+ Junte-se a centenas de barbearias que já estão usando a Smart Agenda + para revolucionar seus negócios. +

+
+ + +
-

Pronto para começar?

-

- Junte-se a centenas de barbearias que já usam a SmartAgenda. -

- - Criar conta grátis -
); } + + + + +