feat: Implement initial application structure, core pages, UI components, and Supabase backend integration.

This commit is contained in:
Rodrigo Lopes dos Santos
2026-03-16 01:30:28 +00:00
parent 8ece90a37e
commit 0270a6cbdf
49 changed files with 2122 additions and 797 deletions

9
web/package-lock.json generated
View File

@@ -75,7 +75,6 @@
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5",
@@ -1362,7 +1361,6 @@
"integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.2.2"
@@ -1530,7 +1528,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@@ -2129,7 +2126,6 @@
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"jiti": "bin/jiti.js"
}
@@ -2397,7 +2393,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -2603,7 +2598,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
},
@@ -2616,7 +2610,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
@@ -3030,7 +3023,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -3159,7 +3151,6 @@
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.21.3",
"postcss": "^8.4.43",

View File

@@ -7,20 +7,20 @@ export const BarberList = ({ barbers }: { barbers: Barber[] }) => (
{barbers.map((b) => (
<Card key={b.id} hover className="p-5 flex flex-col items-center text-center gap-4 group">
<div className="relative">
<div className="w-full aspect-square rounded-2xl overflow-hidden border-4 border-slate-50 bg-slate-100 flex items-center justify-center shadow-sm group-hover:border-amber-100 transition-colors">
<div className="w-full aspect-square rounded-2xl overflow-hidden border-4 border-slate-50 bg-slate-100 flex items-center justify-center shadow-sm group-hover:border-indigo-100 transition-colors">
{b.imageUrl ? (
<img src={b.imageUrl} alt={b.name} className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-110" />
) : (
<User size={40} className="text-slate-300" />
)}
</div>
<div className="absolute -bottom-1 -right-1 w-8 h-8 bg-amber-600 rounded-full flex items-center justify-center text-white border-2 border-white shadow-sm">
<div className="absolute -bottom-1 -right-1 w-8 h-8 bg-indigo-600 rounded-full flex items-center justify-center text-white border-2 border-white shadow-sm">
<Scissors size={14} />
</div>
</div>
<div>
<h3 className="font-bold text-slate-900 text-lg group-hover:text-amber-700 transition-colors">{b.name}</h3>
<h3 className="font-bold text-slate-900 text-lg group-hover:text-indigo-700 transition-colors">{b.name}</h3>
<div className="mt-2 flex flex-wrap justify-center gap-1">
{b.specialties.map((spec, i) => (
<span key={i} className="px-2 py-0.5 bg-slate-100 text-slate-600 rounded text-[10px] font-bold uppercase tracking-wider">

View File

@@ -219,7 +219,7 @@ export const CalendarWeekView = ({
const service = shop.services.find((s) => s.id === apt.serviceId);
const barber = shop.barbers.find((b) => b.id === apt.barberId);
const statusColors = {
pendente: 'bg-amber-100 border-amber-300 text-amber-800',
pendente: 'bg-indigo-100 border-indigo-300 text-indigo-800',
confirmado: 'bg-green-100 border-green-300 text-green-800',
concluido: 'bg-blue-100 border-blue-300 text-blue-800',
cancelado: 'bg-red-100 border-red-300 text-red-800',
@@ -252,7 +252,7 @@ export const CalendarWeekView = ({
{/* Legenda */}
<div className="flex items-center gap-4 text-xs">
<div className="flex items-center gap-2">
<div className="w-4 h-4 rounded border-l-2 bg-amber-100 border-amber-300"></div>
<div className="w-4 h-4 rounded border-l-2 bg-indigo-100 border-indigo-300"></div>
<span>Pendente</span>
</div>
<div className="flex items-center gap-2">

View File

@@ -16,10 +16,10 @@ export const CartPanel = () => {
</div>
<div className="space-y-2">
<h3 className="text-2xl font-black text-slate-900 uppercase italic tracking-tighter">O seu carrinho está vazio</h3>
<p className="text-slate-500 font-medium italic">Explore os melhores produtos e serviços de luxo.</p>
<p className="text-slate-500 font-medium italic">Explore os melhores produtos e serviços disponíveis.</p>
</div>
<Button asChild className="h-14 px-8 bg-slate-900 hover:bg-slate-800 text-amber-500 font-black rounded-2xl uppercase tracking-widest text-xs italic">
<Link to="/explorar">Começar a Explorar</Link>
<Button asChild className="h-14 px-8 bg-slate-900 hover:bg-slate-800 text-indigo-400 font-black rounded-2xl uppercase tracking-widest text-xs italic">
<Link to="/explorar">Ver Barbearias</Link>
</Button>
</div>
);
@@ -60,7 +60,7 @@ export const CartPanel = () => {
return (
<div key={shopId} className="space-y-4 animate-in fade-in slide-in-from-bottom-4 duration-500">
<div className="flex items-center gap-2 px-2">
<ShoppingBag size={14} className="text-amber-600" />
<ShoppingBag size={14} className="text-indigo-600" />
<h3 className="text-[10px] font-black uppercase tracking-[0.3em] text-slate-400">Origem: <span className="text-slate-900">{shop?.name ?? 'Barbearia'}</span></h3>
</div>
@@ -105,7 +105,7 @@ export const CartPanel = () => {
{user ? (
<Button
onClick={() => handleCheckout(shopId)}
className="h-14 px-8 bg-slate-900 hover:bg-slate-800 text-amber-500 font-black rounded-2xl uppercase tracking-widest text-xs italic shadow-lg active:scale-95 transition-all"
className="h-14 px-8 bg-slate-900 hover:bg-slate-800 text-indigo-400 font-black rounded-2xl uppercase tracking-widest text-xs italic shadow-lg active:scale-95 transition-all"
>
Finalizar Compra
<ArrowRight size={14} className="ml-2" />

View File

@@ -41,7 +41,7 @@ export const DashboardCards = ({ periodFilter }: Props) => {
<div className="grid md:grid-cols-3 gap-3">
<Card className="p-4">
<div className="text-sm text-slate-600">Faturamento</div>
<div className="text-xl font-semibold text-amber-700">{currency(total)}</div>
<div className="text-xl font-semibold text-indigo-700">{currency(total)}</div>
</Card>
<Card className="p-4">
<div className="text-sm text-slate-600">Agendamentos pendentes</div>

View File

@@ -16,13 +16,13 @@ export const ProductList = ({
{products.map((p) => {
const lowStock = p.stock <= 3;
return (
<Card key={p.id} className="relative overflow-hidden border-none glass-card rounded-[2rem] premium-shadow group hover:shadow-2xl transition-all duration-300 flex flex-col">
<div className="aspect-square bg-slate-50 flex items-center justify-center p-8 group-hover:bg-amber-50 transition-colors">
<Package size={48} className="text-slate-200 group-hover:text-amber-200 transition-all group-hover:scale-110 duration-500" />
<Card key={p.id} className="relative overflow-hidden border-none glass-card rounded-[2rem] group hover:shadow-2xl transition-all duration-300 flex flex-col">
<div className="aspect-square bg-slate-50 flex items-center justify-center p-8 group-hover:bg-indigo-50 transition-colors">
<Package size={48} className="text-slate-200 group-hover:text-indigo-200 transition-all group-hover:scale-110 duration-500" />
{lowStock && (
<div className="absolute top-4 left-4">
<Badge color="amber" variant="solid" className="text-[9px] px-2 py-0.5 font-black uppercase tracking-widest shadow-lg animate-pulse">
<Badge color="indigo" variant="solid" className="text-[9px] px-2 py-0.5 font-black uppercase tracking-widest shadow-lg animate-pulse">
Últimas Unidades
</Badge>
</div>
@@ -31,7 +31,7 @@ export const ProductList = ({
<div className="p-5 flex-1 flex flex-col gap-4">
<div className="space-y-1">
<h3 className="font-black text-slate-900 text-base tracking-tight leading-tight group-hover:text-amber-600 transition-colors uppercase italic truncate">{p.name}</h3>
<h3 className="font-black text-slate-900 text-base tracking-tight leading-tight group-hover:text-indigo-600 transition-colors uppercase italic truncate">{p.name}</h3>
<div className="text-xs font-bold text-slate-400 uppercase tracking-widest">
{p.stock} em stock
</div>
@@ -46,7 +46,7 @@ export const ProductList = ({
<Button
onClick={() => onAdd(p.id)}
disabled={p.stock <= 0}
className="w-full h-10 bg-slate-900 hover:bg-slate-800 text-amber-500 font-black rounded-xl shadow-lg shadow-slate-200 transition-all active:scale-95 uppercase tracking-widest text-[10px]"
className="w-full h-10 bg-slate-900 hover:bg-slate-800 text-indigo-400 font-black rounded-xl shadow-lg shadow-slate-200 transition-all active:scale-95 uppercase tracking-widest text-[10px]"
>
{p.stock > 0 ? 'Adicionar' : 'Esgotado'}
</Button>

View File

@@ -56,7 +56,7 @@ export function ReviewModal({ shopName, appointmentId, onSubmit, onClose }: Revi
) : (
<>
<div className="text-center mb-6">
<div className="w-14 h-14 bg-gradient-to-br from-amber-400 to-amber-600 rounded-2xl flex items-center justify-center mx-auto mb-3 shadow-md">
<div className="w-14 h-14 bg-gradient-to-br from-indigo-400 to-indigo-600 rounded-2xl flex items-center justify-center mx-auto mb-3 shadow-md">
<Star size={28} className="text-white fill-white" />
</div>
<h3 className="text-xl font-bold text-slate-900">Avaliar atendimento</h3>
@@ -76,14 +76,14 @@ export function ReviewModal({ shopName, appointmentId, onSubmit, onClose }: Revi
<Star
size={36}
className={`transition-colors ${star <= (hovered || rating)
? 'text-amber-400 fill-amber-400'
? 'text-indigo-400 fill-indigo-400'
: 'text-slate-300'
}`}
/>
</button>
))}
</div>
<p className="text-center text-sm font-medium text-amber-600 mb-5 h-5">
<p className="text-center text-sm font-medium text-indigo-600 mb-5 h-5">
{labels[hovered || rating]}
</p>
@@ -93,7 +93,7 @@ export function ReviewModal({ shopName, appointmentId, onSubmit, onClose }: Revi
onChange={(e) => setComment(e.target.value)}
placeholder="Quer deixar um comentário? (opcional)"
rows={3}
className="w-full border border-slate-200 rounded-xl p-3 text-sm resize-none focus:outline-none focus:ring-2 focus:ring-amber-400 focus:border-transparent mb-4 placeholder:text-slate-400"
className="w-full border border-slate-200 rounded-xl p-3 text-sm resize-none focus:outline-none focus:ring-2 focus:ring-indigo-400 focus:border-transparent mb-4 placeholder:text-slate-400"
/>
<Button

View File

@@ -13,10 +13,10 @@ export const ServiceList = ({
}) => (
<div className="grid md:grid-cols-2 gap-6">
{services.map((s) => (
<Card key={s.id} className="p-6 border-none glass-card rounded-[2rem] premium-shadow group hover:shadow-2xl transition-all duration-300">
<Card key={s.id} className="p-6 border-none glass-card rounded-[2rem] group hover:shadow-2xl transition-all duration-300">
<div className="flex items-start justify-between gap-4">
<div className="flex-1 space-y-2">
<h3 className="font-black text-slate-900 text-xl tracking-tight leading-tight group-hover:text-amber-600 transition-colors uppercase italic">{s.name}</h3>
<h3 className="font-black text-slate-900 text-xl tracking-tight leading-tight group-hover:text-indigo-600 transition-colors uppercase italic">{s.name}</h3>
<div className="flex items-center gap-3">
<div className="flex items-center gap-1.5 px-2 py-1 bg-slate-100 rounded-lg text-xs font-bold text-slate-500 uppercase">
<Clock size={12} />
@@ -26,7 +26,7 @@ export const ServiceList = ({
<span className="text-xs font-black text-slate-400 uppercase tracking-widest italic">Corte Profissional</span>
</div>
</div>
<div className="text-2xl font-black text-slate-900 tracking-tighter bg-amber-50 px-3 py-1 rounded-xl border border-amber-100">
<div className="text-2xl font-black text-slate-900 tracking-tighter bg-indigo-50 px-3 py-1 rounded-xl border border-indigo-100">
{currency(s.price)}
</div>
</div>
@@ -36,7 +36,7 @@ export const ServiceList = ({
<p className="text-xs font-medium text-slate-400 max-w-[150px]">Lugar disponível hoje</p>
<Button
onClick={() => onSelect(s.id)}
className="flex-1 h-11 bg-slate-900 hover:bg-slate-800 text-amber-500 font-black rounded-xl shadow-lg shadow-slate-200 transition-all active:scale-95 uppercase tracking-widest text-xs"
className="flex-1 h-11 bg-slate-900 hover:bg-slate-800 text-indigo-400 font-black rounded-xl shadow-lg shadow-slate-200 transition-all active:scale-95 uppercase tracking-widest text-xs"
>
Reservar Agora
</Button>

View File

@@ -6,7 +6,7 @@ import { Button } from './ui/button';
export const ShopCard = ({ shop }: { shop: BarberShop }) => {
return (
<Card className="overflow-hidden border-none glass-card rounded-[2rem] premium-shadow group hover:shadow-2xl hover:shadow-slate-200/60 transition-all duration-500">
<Card className="overflow-hidden border-none glass-card rounded-[2.5rem] group hover:shadow-2xl hover:shadow-slate-200/60 transition-all duration-500">
<div className="relative h-44 overflow-hidden">
{shop.imageUrl ? (
<img
@@ -15,7 +15,7 @@ export const ShopCard = ({ shop }: { shop: BarberShop }) => {
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110"
/>
) : (
<div className="w-full h-full bg-slate-900 flex items-center justify-center text-amber-500">
<div className="w-full h-full bg-slate-900 flex items-center justify-center text-indigo-400">
<Scissors size={40} />
</div>
)}
@@ -23,7 +23,7 @@ export const ShopCard = ({ shop }: { shop: BarberShop }) => {
{/* Rating Badge */}
<div className="absolute top-4 right-4 bg-slate-900/90 backdrop-blur-md border border-white/10 px-3 py-1 rounded-full flex items-center gap-1.5 shadow-xl">
<Star size={14} className="fill-amber-500 text-amber-500" />
<Star size={14} className="fill-indigo-500 text-indigo-500" />
<span className="text-white text-xs font-black tracking-wider">
{shop.rating ? shop.rating.toFixed(1) : '0.0'}
</span>
@@ -32,11 +32,11 @@ export const ShopCard = ({ shop }: { shop: BarberShop }) => {
<div className="p-6 space-y-4">
<div>
<h2 className="text-slate-900 text-xl font-black tracking-tight group-hover:text-amber-600 transition-colors truncate">
<h2 className="text-slate-900 text-xl font-black tracking-tight group-hover:text-indigo-600 transition-colors truncate">
{shop.name}
</h2>
<div className="flex items-center gap-1.5 text-slate-500 mt-1">
<MapPin size={14} className="text-amber-600" />
<MapPin size={14} className="text-indigo-600" />
<p className="text-sm font-medium line-clamp-1">
{shop.address || 'Endereço Indisponível'}
</p>
@@ -57,7 +57,7 @@ export const ShopCard = ({ shop }: { shop: BarberShop }) => {
</span>
</div>
<Button asChild className="rounded-xl bg-slate-900 hover:bg-slate-800 text-amber-500 font-bold px-5 h-10 shadow-lg shadow-slate-200 transition-all active:scale-95">
<Button asChild className="rounded-xl bg-slate-900 hover:bg-slate-800 text-indigo-400 font-bold px-5 h-10 shadow-lg shadow-slate-200 transition-all active:scale-95">
<Link to={`/barbearia/${shop.id}`}>Reservar</Link>
</Button>
</div>

View File

@@ -22,10 +22,10 @@ export const Header = () => {
className="text-2xl font-black tracking-tighter text-slate-900 group flex items-center gap-2"
onClick={() => setMobileMenuOpen(false)}
>
<div className="w-8 h-8 rounded-lg bg-slate-900 flex items-center justify-center text-amber-500 shadow-lg shadow-slate-200">
<div className="w-8 h-8 rounded-lg bg-slate-900 flex items-center justify-center text-indigo-400 shadow-lg shadow-slate-200">
<User size={18} fill="currentColor" />
</div>
<span className="group-hover:text-amber-600 transition-colors">Smart Agenda</span>
<span className="group-hover:text-indigo-600 transition-colors uppercase italic font-black">Smart Agenda</span>
</Link>
{/* Desktop Navigation */}
@@ -36,8 +36,8 @@ export const Header = () => {
to="/explorar"
className="text-sm font-bold text-slate-600 hover:text-slate-900 transition-all flex items-center gap-2"
>
<MapPin size={16} className="text-amber-600" />
<span>Explorar</span>
<MapPin size={16} className="text-indigo-600" />
<span>Descobrir Barbearias</span>
</Link>
<Link
@@ -46,7 +46,7 @@ export const Header = () => {
>
<ShoppingCart size={20} />
{cart.length > 0 && (
<span className="absolute -right-1 -top-1 rounded-full bg-slate-900 px-1.5 py-0.5 text-[10px] font-black text-amber-500 shadow-md min-w-[18px] text-center">
<span className="absolute -right-1 -top-1 rounded-full bg-slate-900 px-1.5 py-0.5 text-[10px] font-black text-indigo-400 shadow-md min-w-[18px] text-center">
{cart.length}
</span>
)}
@@ -63,7 +63,7 @@ export const Header = () => {
className="flex items-center gap-3 bg-slate-50 hover:bg-slate-100 border border-slate-200/60 pl-3 pr-4 py-1.5 rounded-full transition-all group"
type="button"
>
<div className="w-7 h-7 rounded-full bg-slate-900 flex items-center justify-center text-amber-500 shadow-sm">
<div className="w-7 h-7 rounded-full bg-slate-900 flex items-center justify-center text-indigo-400 shadow-sm">
<User size={14} fill="currentColor" />
</div>
<span className="text-sm font-bold text-slate-700 group-hover:text-slate-900 max-w-[120px] truncate">{user.name}</span>
@@ -87,8 +87,8 @@ export const Header = () => {
Login
</Link>
<Link
to="/registro"
className="inline-flex items-center justify-center rounded-xl bg-slate-900 px-5 py-2 text-sm font-bold text-amber-500 shadow-lg shadow-slate-200 hover:bg-slate-800 transition-all"
to="/registo"
className="inline-flex items-center justify-center rounded-xl bg-slate-900 px-5 py-2 text-sm font-bold text-indigo-400 shadow-lg shadow-slate-200 hover:bg-slate-800 transition-all font-black uppercase italic tracking-widest"
>
Criar Conta
</Link>
@@ -115,21 +115,21 @@ export const Header = () => {
<Link
to="/explorar"
onClick={() => setMobileMenuOpen(false)}
className="flex items-center gap-3 text-base font-bold text-slate-700 hover:text-amber-600 p-3 rounded-2xl bg-slate-50 transition-all"
className="flex items-center gap-3 text-base font-black text-slate-700 hover:text-indigo-600 p-3 rounded-2xl bg-slate-50 transition-all uppercase italic"
>
<MapPin size={18} className="text-amber-600" />
Barbearias
<MapPin size={18} className="text-indigo-600" />
Descobrir Barbearias
</Link>
<Link
to="/carrinho"
onClick={() => setMobileMenuOpen(false)}
className="flex items-center gap-3 text-base font-bold text-slate-700 hover:text-amber-600 p-3 rounded-2xl bg-slate-50 transition-all"
className="flex items-center gap-3 text-base font-black text-slate-700 hover:text-indigo-600 p-3 rounded-2xl bg-slate-50 transition-all uppercase italic"
>
<ShoppingCart size={18} className="text-amber-600" />
<ShoppingCart size={18} className="text-indigo-600" />
Meu Carrinho
{cart.length > 0 && (
<span className="ml-auto rounded-full bg-slate-900 px-2 py-0.5 text-[10px] font-black text-amber-500">
<span className="ml-auto rounded-full bg-slate-900 px-2 py-0.5 text-[10px] font-black text-indigo-400">
{cart.length}
</span>
)}
@@ -172,9 +172,9 @@ export const Header = () => {
Entrar
</Link>
<Link
to="/registro"
to="/registo"
onClick={() => setMobileMenuOpen(false)}
className="flex items-center justify-center rounded-2xl bg-slate-900 py-3 text-sm font-bold text-amber-500 shadow-lg shadow-slate-200"
className="flex items-center justify-center rounded-2xl bg-slate-900 py-3 text-sm font-bold text-indigo-400 shadow-lg shadow-slate-200 font-black uppercase italic tracking-widest"
>
Criar Conta
</Link>

View File

@@ -1,25 +1,25 @@
import { cn } from '../../lib/cn';
type Props = { children: React.ReactNode; color?: 'amber' | 'slate' | 'green' | 'red' | 'blue'; className?: string; variant?: 'solid' | 'soft' };
type Props = { children: React.ReactNode; color?: 'slate' | 'green' | 'red' | 'blue' | 'indigo'; className?: string; variant?: 'solid' | 'soft' };
const colorMap = {
solid: {
amber: 'bg-amber-500 text-white',
slate: 'bg-slate-600 text-white',
green: 'bg-emerald-500 text-white',
red: 'bg-rose-500 text-white',
slate: 'bg-slate-500 text-white',
green: 'bg-green-500 text-white',
red: 'bg-red-500 text-white',
blue: 'bg-blue-500 text-white',
indigo: 'bg-indigo-500 text-white',
},
soft: {
amber: 'bg-amber-50 text-amber-700 border border-amber-200/60',
slate: 'bg-slate-50 text-slate-700 border border-slate-200/60',
green: 'bg-emerald-50 text-emerald-700 border border-emerald-200/60',
red: 'bg-rose-50 text-rose-700 border border-rose-200/60',
green: 'bg-green-50 text-green-700 border border-green-200/60',
red: 'bg-red-50 text-red-700 border border-red-200/60',
blue: 'bg-blue-50 text-blue-700 border border-blue-200/60',
indigo: 'bg-indigo-50 text-indigo-700 border border-indigo-200/60',
},
};
export const Badge = ({ children, color = 'amber', variant = 'soft', className }: Props) => (
export const Badge = ({ children, color = 'indigo', variant = 'soft', className }: Props) => (
<span className={cn('inline-flex items-center px-2.5 py-1 text-xs rounded-full font-semibold transition-colors', colorMap[variant][color], className)}>
{children}
</span>

View File

@@ -5,7 +5,7 @@ export const Chip = ({ children, active, onClick, className }: { children: React
onClick={onClick}
className={cn(
'px-3 py-1.5 rounded-full border text-sm transition font-medium',
active ? 'border-amber-500 bg-amber-50 text-amber-700' : 'border-slate-200 text-slate-700 hover:bg-slate-100',
active ? 'border-indigo-500 bg-indigo-50 text-indigo-700' : 'border-slate-200 text-slate-700 hover:bg-slate-100',
className
)}
>

View File

@@ -14,7 +14,7 @@ export const Input = ({ className, label, error, ...props }: Props) => {
'placeholder:text-slate-400',
error
? 'border-rose-300 focus:border-rose-500 focus:ring-2 focus:ring-rose-500/30'
: 'border-slate-300 focus:border-amber-500 focus:outline-none focus:ring-2 focus:ring-amber-500/30',
: 'border-slate-300 focus:border-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/30',
className
)}
{...props}

View File

@@ -11,13 +11,13 @@ export const Tabs = ({ tabs, active, onChange, className }: { tabs: Tab[]; activ
className={cn(
"px-6 py-2.5 text-sm font-black uppercase tracking-widest transition-all rounded-xl whitespace-nowrap",
active === t.id
? "bg-slate-900 text-amber-500 shadow-xl"
? "bg-slate-900 text-indigo-400 shadow-xl"
: "text-slate-500 hover:text-slate-900 hover:bg-white/50"
)}
>
{t.label}
{t.badge && (
<span className="ml-2 inline-flex items-center justify-center w-5 h-5 text-[10px] font-black text-white bg-slate-900 rounded-full border border-amber-500/50">
<span className="ml-2 inline-flex items-center justify-center w-5 h-5 text-[10px] font-black text-white bg-slate-900 rounded-full border border-indigo-500/50">
{t.badge}
</span>
)}

View File

@@ -16,63 +16,30 @@ export const mockShops: BarberShop[] = [
imageUrl:
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='800' height='480'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0%25' stop-color='%23111827'/%3E%3Cstop offset='100%25' stop-color='%232563eb'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='800' height='480' fill='url(%23g)'/%3E%3Ccircle cx='640' cy='120' r='120' fill='%23ffffff22'/%3E%3Crect x='80' y='320' width='300' height='12' fill='%23ffffff66'/%3E%3Crect x='80' y='350' width='220' height='10' fill='%23ffffff55'/%3E%3C/text%3E%3C/svg%3E",
barbers: [
{ id: 'b1', name: 'João', specialties: ['Fade', 'Navalha'], schedule: [{ day: '2025-01-01', slots: ['09:00', '10:00', '11:00'] }] },
{ id: 'b2', name: 'Carlos', specialties: ['Barba', 'Clássico'], schedule: [{ day: '2025-01-01', slots: ['14:00', '15:00'] }] },
{ id: 'b1', name: 'João', specialties: ['Fade', 'Navalha'], schedule: [{ day: '2026-03-15', slots: ['09:00', '10:00', '11:00'] }] },
{ id: 'b2', name: 'Carlos', specialties: ['Barba', 'Clássico'], schedule: [{ day: '2026-03-15', slots: ['14:00', '15:00'] }] },
],
services: [
{ id: 'sv1', name: 'Corte Fade', price: 60, duration: 45, barberIds: ['b1'] },
{ id: 'sv2', name: 'Barba Completa', price: 40, duration: 30, barberIds: ['b2'] },
{ id: 'sv1', name: 'Corte', price: 15, duration: 45, barberIds: ['b1'] },
{ id: 'sv2', name: 'Barba', price: 10, duration: 30, barberIds: ['b2'] },
],
products: [
{ id: 'p1', name: 'Pomada Matte', price: 35, stock: 8 },
{ id: 'p2', name: 'Óleo para Barba', price: 45, stock: 5 },
{ id: 'p1', name: 'Pomada Matte', price: 12, stock: 8 },
{ id: 'p2', name: 'Óleo para Barba', price: 15, stock: 5 },
],
},
{
id: 's2',
name: 'Barbearia Bairro',
address: 'Av. Verde, 45',
name: 'Barbearia do Bairro',
address: 'Av. Principal, 45',
rating: 4.5,
imageUrl:
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='800' height='480'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0%25' stop-color='%230f172a'/%3E%3Cstop offset='100%25' stop-color='%230ea5e9'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='800' height='480' fill='url(%23g)'/%3E%3Crect x='60' y='90' width='260' height='180' rx='24' fill='%23ffffff1f'/%3E%3Crect x='380' y='260' width='300' height='12' fill='%23ffffff66'/%3E%3Crect x='380' y='290' width='180' height='10' fill='%23ffffff55'/%3E%3C/svg%3E",
barbers: [
{ id: 'b3', name: 'Miguel', specialties: ['Clássico', 'Fade'], schedule: [{ day: '2025-01-01', slots: ['09:30', '10:30'] }] },
{ id: 'b3', name: 'Miguel', specialties: ['Clássico', 'Fade'], schedule: [{ day: '2026-03-15', slots: ['09:30', '10:30'] }] },
],
services: [{ id: 'sv3', name: 'Corte Clássico', price: 50, duration: 40, barberIds: ['b3'] }],
products: [{ id: 'p3', name: 'Shampoo Masculino', price: 30, stock: 10 }],
},
{
id: 's3',
name: 'Barbearia Central',
address: 'Rua Principal, 123',
rating: 4.7,
imageUrl:
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='800' height='480'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0%25' stop-color='%23111827'/%3E%3Cstop offset='100%25' stop-color='%232563eb'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='800' height='480' fill='url(%23g)'/%3E%3Ccircle cx='640' cy='120' r='120' fill='%23ffffff22'/%3E%3Crect x='80' y='320' width='300' height='12' fill='%23ffffff66'/%3E%3Crect x='80' y='350' width='220' height='10' fill='%23ffffff55'/%3E%3C/text%3E%3C/svg%3E",
barbers: [
{ id: 'b1', name: 'João', specialties: ['Fade', 'Navalha'], schedule: [{ day: '2025-01-01', slots: ['09:00', '10:00', '11:00'] }] },
{ id: 'b2', name: 'Carlos', specialties: ['Barba', 'Clássico'], schedule: [{ day: '2025-01-01', slots: ['14:00', '15:00'] }] },
],
services: [
{ id: 'sv1', name: 'Corte Fade', price: 60, duration: 45, barberIds: ['b1'] },
{ id: 'sv2', name: 'Barba Completa', price: 40, duration: 30, barberIds: ['b2'] },
],
products: [
{ id: 'p1', name: 'Pomada Matte', price: 35, stock: 8 },
{ id: 'p2', name: 'Óleo para Barba', price: 45, stock: 5 },
],
},
{
id: 's4',
name: 'Barbearia Bairro',
address: 'Av. Verde, 45',
rating: 4.5,
imageUrl:
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='800' height='480'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0%25' stop-color='%230f172a'/%3E%3Cstop offset='100%25' stop-color='%230ea5e9'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='800' height='480' fill='url(%23g)'/%3E%3Crect x='60' y='90' width='260' height='180' rx='24' fill='%23ffffff1f'/%3E%3Crect x='380' y='260' width='300' height='12' fill='%23ffffff66'/%3E%3Crect x='380' y='290' width='180' height='10' fill='%23ffffff55'/%3E%3C/svg%3E",
barbers: [
{ id: 'b3', name: 'Miguel', specialties: ['Clássico', 'Fade'], schedule: [{ day: '2025-01-01', slots: ['09:30', '10:30'] }] },
],
services: [{ id: 'sv3', name: 'Corte Clássico', price: 50, duration: 40, barberIds: ['b3'] }],
products: [{ id: 'p3', name: 'Shampoo Masculino', price: 30, stock: 10 }],
services: [{ id: 'sv3', name: 'Corte Clássico', price: 12, duration: 40, barberIds: ['b3'] }],
products: [{ id: 'p3', name: 'Shampoo', price: 8, stock: 10 }],
},
];

View File

@@ -4,8 +4,9 @@
@layer base {
:root {
--brand-gold: #d97706;
--brand-gold-light: #fbbf24;
--brand-primary: #4f46e5;
--brand-primary-light: #818cf8;
--brand-primary-dark: #3730a3;
--obsidian: #0f172a;
--obsidian-light: #1e293b;
--slate-950: #020617;
@@ -19,7 +20,7 @@
body {
@apply bg-[#f8fafc] text-slate-900 font-sans antialiased;
background-image:
radial-gradient(at 0% 0%, rgba(217, 119, 6, 0.03) 0px, transparent 50%),
radial-gradient(at 0% 0%, rgba(79, 70, 229, 0.03) 0px, transparent 50%),
radial-gradient(at 100% 0%, rgba(15, 23, 42, 0.03) 0px, transparent 50%);
font-feature-settings: 'cv02', 'cv03', 'cv04', 'cv11';
min-height: 100vh;
@@ -39,7 +40,7 @@
}
::-webkit-scrollbar-thumb {
@apply bg-slate-300 rounded-full hover:bg-slate-400;
@apply bg-indigo-200 rounded-full hover:bg-indigo-300;
}
}
@@ -52,12 +53,9 @@
@apply bg-white/80 backdrop-blur-md border border-white/20 shadow-xl shadow-slate-200/50;
}
.premium-shadow {
box-shadow: 0 10px 40px -10px rgba(15, 23, 42, 0.1);
}
.gold-gradient {
@apply bg-gradient-to-br from-amber-500 via-amber-600 to-amber-700;
.indigo-gradient {
@apply bg-gradient-to-br from-indigo-500 via-indigo-600 to-indigo-700;
}
.obsidian-gradient {

View File

@@ -1,6 +1,6 @@
export const brand = {
primary: 'amber-500',
primaryDark: 'amber-600',
primary: 'indigo-600',
primaryDark: 'indigo-700',
bg: 'slate-50',
card: 'white',
text: 'slate-900',

View File

@@ -67,9 +67,9 @@ export default function AuthLogin() {
return (
<div className="min-h-[80vh] flex items-center justify-center px-6 py-12">
<Card className="w-full max-w-[440px] p-10 space-y-8 glass-card border-none rounded-[2rem] premium-shadow animate-in fade-in zoom-in duration-500">
<Card className="w-full max-w-[440px] p-10 space-y-8 glass-card border-none rounded-[2rem] animate-in fade-in zoom-in duration-500">
<div className="text-center space-y-4">
<Link to="/" className="inline-flex p-4 bg-slate-900 rounded-2xl text-amber-500 shadow-xl mb-2 hover:scale-105 transition-transform">
<Link to="/" className="inline-flex p-4 bg-slate-900 rounded-2xl text-indigo-400 shadow-xl mb-2 hover:scale-105 transition-transform">
<LogIn size={32} />
</Link>
<div className="space-y-1">
@@ -93,7 +93,7 @@ export default function AuthLogin() {
onChange={(e) => setEmail(e.target.value)}
placeholder="exemplo@email.com"
required
className="rounded-xl border-slate-200/60 focus:ring-amber-500/20"
className="rounded-xl border-slate-200/60 focus:ring-indigo-500/20"
/>
<Input
@@ -103,14 +103,14 @@ export default function AuthLogin() {
onChange={(e) => setPassword(e.target.value)}
placeholder="••••••••"
required
className="rounded-xl border-slate-200/60 focus:ring-amber-500/20"
className="rounded-xl border-slate-200/60 focus:ring-indigo-500/20"
/>
</div>
<Button type="submit" className="w-full h-12 bg-slate-900 hover:bg-slate-800 text-amber-500 font-bold rounded-xl shadow-lg shadow-slate-200 transition-all active:scale-[0.98]" disabled={loading}>
<Button type="submit" className="w-full h-12 bg-slate-900 hover:bg-slate-800 text-indigo-400 font-bold rounded-xl shadow-lg shadow-slate-200 transition-all active:scale-[0.98]" disabled={loading}>
{loading ? (
<div className="flex items-center gap-2">
<div className="w-4 h-4 border-2 border-amber-500 border-t-transparent rounded-full animate-spin" />
<div className="w-4 h-4 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin" />
<span>A entrar...</span>
</div>
) : 'Entrar na Conta'}
@@ -120,7 +120,7 @@ export default function AuthLogin() {
<div className="text-center pt-6 border-t border-slate-100">
<p className="text-sm text-slate-500 font-medium">
Ainda não tem conta?{' '}
<Link to="/registo" className="text-amber-600 font-bold hover:text-amber-700 underline-offset-4 hover:underline transition-all">
<Link to="/registo" className="text-indigo-600 font-bold hover:text-indigo-700 underline-offset-4 hover:underline transition-all">
Criar conta grátis
</Link>
</p>

View File

@@ -109,9 +109,9 @@ export default function AuthRegister() {
return (
<div className="min-h-[80vh] flex items-center justify-center px-6 py-12">
<Card className="w-full max-w-[500px] p-10 space-y-8 glass-card border-none rounded-[2rem] premium-shadow animate-in fade-in zoom-in duration-500">
<Card className="w-full max-w-[500px] p-10 space-y-8 glass-card border-none rounded-[2rem] animate-in fade-in zoom-in duration-500">
<div className="text-center space-y-4">
<div className="inline-flex p-4 bg-slate-900 rounded-2xl text-amber-500 shadow-xl mb-2">
<div className="inline-flex p-4 bg-slate-900 rounded-2xl text-indigo-400 shadow-xl mb-2">
<UserPlus size={32} />
</div>
<div className="space-y-1">
@@ -142,15 +142,15 @@ export default function AuthRegister() {
setError('')
}}
className={`p-4 rounded-2xl border-2 transition-all group ${role === r
? 'border-slate-900 bg-slate-900 text-amber-500 shadow-lg'
? 'border-slate-900 bg-slate-900 text-indigo-400 shadow-lg'
: 'border-slate-100 bg-slate-50/50 hover:border-slate-200 text-slate-500'
}`}
>
<div className="flex flex-col items-center gap-2">
{r === 'cliente' ? (
<User size={20} className={role === r ? 'text-amber-500' : 'text-slate-400 group-hover:text-slate-600'} />
<User size={20} className={role === r ? 'text-indigo-400' : 'text-slate-400 group-hover:text-slate-600'} />
) : (
<Scissors size={20} className={role === r ? 'text-amber-500' : 'text-slate-400 group-hover:text-slate-600'} />
<Scissors size={20} className={role === r ? 'text-indigo-400' : 'text-slate-400 group-hover:text-slate-600'} />
)}
<span className="text-sm font-bold uppercase tracking-tight">
{r === 'cliente' ? 'Cliente' : 'Barbearia'}
@@ -168,7 +168,7 @@ export default function AuthRegister() {
onChange={(e) => setName(e.target.value)}
placeholder="Ex: João Silva"
required
className="rounded-xl border-slate-200 focus:ring-amber-500/20"
className="rounded-xl border-slate-200 focus:ring-indigo-500/20"
/>
<Input
@@ -178,7 +178,7 @@ export default function AuthRegister() {
onChange={(e) => setEmail(e.target.value)}
placeholder="exemplo@email.com"
required
className="rounded-xl border-slate-200 focus:ring-amber-500/20"
className="rounded-xl border-slate-200 focus:ring-indigo-500/20"
/>
<Input
@@ -188,7 +188,7 @@ export default function AuthRegister() {
onChange={(e) => setPassword(e.target.value)}
placeholder="••••••••"
required
className="rounded-xl border-slate-200 focus:ring-amber-500/20"
className="rounded-xl border-slate-200 focus:ring-indigo-500/20"
/>
{role === 'barbearia' && (
@@ -203,10 +203,10 @@ export default function AuthRegister() {
)}
</div>
<Button type="submit" className="w-full h-12 bg-slate-900 hover:bg-slate-800 text-amber-500 font-bold rounded-xl shadow-lg shadow-slate-200 transition-all active:scale-[0.98]" disabled={loading}>
<Button type="submit" className="w-full h-12 bg-slate-900 hover:bg-slate-800 text-indigo-400 font-bold rounded-xl shadow-lg shadow-slate-200 transition-all active:scale-[0.98]" disabled={loading}>
{loading ? (
<div className="flex items-center gap-2">
<div className="w-4 h-4 border-2 border-amber-500 border-t-transparent rounded-full animate-spin" />
<div className="w-4 h-4 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin" />
<span>A processar...</span>
</div>
) : 'Criar minha conta'}
@@ -216,7 +216,7 @@ export default function AuthRegister() {
<div className="text-center pt-6 border-t border-slate-100">
<p className="text-sm text-slate-500 font-medium">
tem uma conta?{' '}
<Link to="/login" className="text-amber-600 font-bold hover:text-amber-700 underline-offset-4 hover:underline transition-all">
<Link to="/login" className="text-indigo-600 font-bold hover:text-indigo-700 underline-offset-4 hover:underline transition-all">
Fazer Login
</Link>
</p>

View File

@@ -99,16 +99,16 @@ export default function Booking() {
return (
<div className="max-w-4xl mx-auto space-y-10 py-4 pb-20">
<header className="space-y-4 text-center">
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-amber-100 text-amber-700 text-[10px] font-black uppercase tracking-widest">
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-indigo-100 text-indigo-700 text-[10px] font-black uppercase tracking-widest">
<Calendar size={10} fill="currentColor" />
<span>Reserva Exclusiva</span>
</div>
<div className="space-y-1">
<h1 className="text-4xl md:text-5xl font-black text-slate-900 tracking-tighter uppercase italic">
Agendar em <span className="text-amber-600 block md:inline">{shop.name}</span>
Reservar em <span className="text-indigo-600 block md:inline">{shop.name}</span>
</h1>
<div className="flex items-center justify-center gap-2 text-slate-500 font-medium">
<MapPin size={14} className="text-amber-600" />
<MapPin size={14} className="text-indigo-600" />
<p className="text-sm">{shop.address}</p>
</div>
</div>
@@ -127,9 +127,9 @@ export default function Booking() {
disabled={!s.completed && s.id > step}
className={`w-12 h-12 rounded-2xl flex items-center justify-center border-2 transition-all duration-500 scale-100 active:scale-90 ${
s.active
? 'bg-slate-900 border-slate-900 text-amber-500 shadow-2xl shadow-slate-300 -translate-y-2'
? 'bg-slate-900 border-slate-900 text-indigo-400 shadow-2xl shadow-slate-300 -translate-y-2'
: s.completed
? 'bg-amber-500 border-amber-500 text-white'
? 'bg-indigo-600 border-indigo-600 text-white'
: 'bg-white border-slate-200 text-slate-400'
}`}
>
@@ -162,8 +162,8 @@ export default function Booking() {
{step === 1 && (
<div className="space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500">
<div className="space-y-2 text-center md:text-left">
<h3 className="text-3xl font-black text-slate-900 tracking-tighter uppercase italic">1. Selecione o <span className="text-amber-600">Serviço</span></h3>
<p className="text-slate-500 font-medium italic">O primeiro passo para a sua transformação de elite.</p>
<h3 className="text-3xl font-black text-slate-900 tracking-tighter uppercase italic">1. Selecione o <span className="text-indigo-600">Serviço</span></h3>
<p className="text-slate-500 font-medium italic">O primeiro passo para o seu agendamento.</p>
</div>
<div className="grid md:grid-cols-2 gap-6">
{shop.services.map((s) => (
@@ -176,12 +176,12 @@ export default function Booking() {
className={`group p-6 rounded-[2rem] border-2 text-left transition-all duration-300 flex flex-col gap-4 ${
serviceId === s.id
? 'border-slate-900 bg-slate-900 text-white shadow-2xl translate-y-[-4px]'
: 'border-slate-50 bg-slate-50 hover:border-amber-200 hover:bg-amber-50/50'
: 'border-slate-50 bg-slate-50 hover:border-indigo-200 hover:bg-indigo-50/50'
}`}
>
<div className="flex items-start justify-between">
<div className="space-y-1">
<div className={`font-black text-xl tracking-tight uppercase italic ${serviceId === s.id ? 'text-amber-500' : 'text-slate-900 group-hover:text-amber-600'}`}>
<div className={`font-black text-xl tracking-tight uppercase italic ${serviceId === s.id ? 'text-indigo-500' : 'text-slate-900 group-hover:text-indigo-600'}`}>
{s.name}
</div>
<div className={`flex items-center gap-2 text-xs font-bold uppercase tracking-widest ${serviceId === s.id ? 'text-slate-400' : 'text-slate-500'}`}>
@@ -202,7 +202,7 @@ export default function Booking() {
{step === 2 && (
<div className="space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500">
<div className="space-y-2 text-center md:text-left">
<h3 className="text-3xl font-black text-slate-900 tracking-tighter uppercase italic">2. Escolha o <span className="text-amber-600">Mestre</span></h3>
<h3 className="text-3xl font-black text-slate-900 tracking-tighter uppercase italic">2. Escolha o <span className="text-indigo-600">Mestre</span></h3>
<p className="text-slate-500 font-medium italic">Selecione o artista que cuidará do seu visual.</p>
</div>
<div className="grid grid-cols-2 md:grid-cols-3 gap-6">
@@ -216,10 +216,10 @@ export default function Booking() {
className={`group p-6 rounded-[2.5rem] border-2 text-center transition-all duration-300 flex flex-col items-center gap-5 ${
barberId === b.id
? 'border-slate-900 bg-slate-900 text-white shadow-2xl translate-y-[-4px]'
: 'border-slate-50 bg-slate-50 hover:border-amber-200 hover:bg-amber-50/50'
: 'border-slate-50 bg-slate-50 hover:border-indigo-200 hover:bg-indigo-50/50'
}`}
>
<div className={`w-32 h-32 rounded-[2rem] overflow-hidden border-4 transition-all duration-500 ${barberId === b.id ? 'border-amber-500 rotate-3' : 'border-white group-hover:border-amber-100'}`}>
<div className={`w-32 h-32 rounded-[2rem] overflow-hidden border-4 transition-all duration-500 ${barberId === b.id ? 'border-indigo-500 rotate-3' : 'border-white group-hover:border-indigo-100'}`}>
{b.imageUrl ? (
<img src={b.imageUrl} alt={b.name} className="w-full h-full object-cover" />
) : (
@@ -229,9 +229,9 @@ export default function Booking() {
)}
</div>
<div className="space-y-1">
<p className={`font-black text-lg uppercase italic tracking-tight ${barberId === b.id ? 'text-amber-500' : 'text-slate-900 group-hover:text-amber-600'}`}>{b.name}</p>
<p className={`font-black text-lg uppercase italic tracking-tight ${barberId === b.id ? 'text-indigo-500' : 'text-slate-900 group-hover:text-indigo-600'}`}>{b.name}</p>
<p className={`text-[10px] font-black uppercase tracking-[0.2em] ${barberId === b.id ? 'text-slate-400' : 'text-slate-400'}`}>
{b.specialties[0] || 'Elite Barber'}
{b.specialties[0] || 'Barbeiro'}
</p>
</div>
</button>
@@ -243,11 +243,11 @@ export default function Booking() {
{step === 3 && (
<div className="space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500">
<div className="space-y-2 text-center md:text-left">
<h3 className="text-3xl font-black text-slate-900 tracking-tighter uppercase italic">3. Defina o <span className="text-amber-600">Momento</span></h3>
<h3 className="text-3xl font-black text-slate-900 tracking-tighter uppercase italic">3. Escolha o <span className="text-indigo-600">Horário</span></h3>
<p className="text-slate-500 font-medium italic">Seu tempo é valioso. Escolha a data perfeita.</p>
</div>
<div className="max-w-md mx-auto relative">
<div className="absolute left-6 top-1/2 -translate-y-1/2 text-amber-600 pointer-events-none z-10">
<div className="absolute left-6 top-1/2 -translate-y-1/2 text-indigo-600 pointer-events-none z-10">
<Calendar size={20} />
</div>
<Input
@@ -258,7 +258,7 @@ export default function Booking() {
if (e.target.value) setStep(4);
}}
min={new Date().toISOString().split('T')[0]}
className="h-16 pl-14 pr-6 bg-slate-50 border-none rounded-2xl text-lg font-black uppercase tracking-widest text-slate-900 focus:ring-2 focus:ring-amber-500/20 transition-all shadow-inner"
className="h-16 pl-14 pr-6 bg-slate-50 border-none rounded-2xl text-lg font-black uppercase tracking-widest text-slate-900 focus:ring-2 focus:ring-indigo-500/20 transition-all shadow-inner"
/>
</div>
</div>
@@ -267,7 +267,7 @@ export default function Booking() {
{step === 4 && (
<div className="space-y-10 animate-in fade-in slide-in-from-bottom-4 duration-500">
<div className="space-y-2 text-center md:text-left">
<h3 className="text-3xl font-black text-slate-900 tracking-tighter uppercase italic">4. Escolha o <span className="text-amber-600">Horário</span></h3>
<h3 className="text-3xl font-black text-slate-900 tracking-tighter uppercase italic">4. Escolha o <span className="text-indigo-600">Horário Privilegiado</span></h3>
<p className="text-slate-500 font-medium italic">A pontualidade é a cortesia dos reis.</p>
</div>
@@ -275,12 +275,12 @@ export default function Booking() {
{/* Left Side: Summary Sidebar */}
<div className="w-full md:w-80 space-y-4">
<div className="p-6 bg-slate-900 text-white rounded-[2rem] space-y-6 shadow-xl relative overflow-hidden">
<div className="absolute top-0 right-0 w-32 h-32 bg-amber-500/5 rounded-full blur-3xl -translate-y-1/2 translate-x-1/2" />
<div className="absolute top-0 right-0 w-32 h-32 bg-indigo-500/5 rounded-full blur-3xl -translate-y-1/2 translate-x-1/2" />
<div className="space-y-4 relative z-10">
<div className="space-y-1 border-b border-white/10 pb-4">
<p className="text-[10px] font-black text-slate-500 uppercase tracking-widest">Serviço</p>
<p className="text-lg font-black uppercase italic tracking-tight">{selectedService?.name}</p>
<p className="text-xl font-black text-amber-500 tracking-tighter">{currency(selectedService?.price || 0)}</p>
<p className="text-xl font-black text-indigo-500 tracking-tighter">{currency(selectedService?.price || 0)}</p>
</div>
<div className="space-y-1 border-b border-white/10 pb-4">
<p className="text-[10px] font-black text-slate-500 uppercase tracking-widest">Mestre</p>
@@ -306,8 +306,8 @@ export default function Booking() {
onClick={() => setSlot(h)}
className={`h-14 rounded-2xl border-2 text-sm font-black tracking-widest transition-all duration-300 ${
slot === h
? 'border-slate-900 bg-slate-900 text-amber-500 shadow-xl scale-105 z-10'
: 'border-slate-50 bg-slate-50 text-slate-600 hover:border-amber-200 hover:bg-amber-50'
? 'border-slate-900 bg-slate-900 text-indigo-400 shadow-xl scale-105 z-10'
: 'border-slate-50 bg-slate-50 text-slate-600 hover:border-indigo-200 hover:bg-indigo-50'
}`}
>
{h}
@@ -325,9 +325,9 @@ export default function Booking() {
<Button
onClick={submit}
size="lg"
className="w-full h-16 bg-slate-900 hover:bg-slate-800 text-amber-500 font-black rounded-2xl shadow-2xl transition-all active:scale-95 uppercase tracking-[0.2em] text-sm italic"
className="w-full h-16 bg-slate-900 hover:bg-slate-800 text-indigo-400 font-black rounded-2xl shadow-2xl transition-all active:scale-95 uppercase tracking-[0.2em] text-sm italic"
>
Confirmar Experiência de Elite
Confirmar Agendamento
</Button>
<p className="text-center mt-4 text-[10px] font-bold text-slate-400 uppercase tracking-widest">
Pagamento realizado após o serviço

View File

@@ -367,8 +367,8 @@ function DashboardInner({ shop }: { shop: BarberShop }) {
key={p}
onClick={() => setPeriod(p)}
className={`px-3 py-1.5 rounded-lg text-xs font-medium border transition-all ${period === p
? 'border-amber-500 bg-amber-50 text-amber-700 shadow-sm'
: 'border-slate-200 text-slate-700 hover:border-amber-300 hover:bg-amber-50/50'
? 'border-indigo-500 bg-indigo-50 text-indigo-700 shadow-sm'
: 'border-slate-200 text-slate-700 hover:border-indigo-300 hover:bg-indigo-50/50'
}`}
>
{p === 'mes' ? 'Mês' : p === 'hoje' ? 'Hoje' : p === 'semana' ? 'Semana' : 'Total'}
@@ -550,13 +550,13 @@ function DashboardInner({ shop }: { shop: BarberShop }) {
<h2 className="text-xl font-bold text-slate-900">Caixa de Pedidos</h2>
<p className="text-sm text-slate-600">Aprove ou recuse os pedidos pendentes e conclua os serviços de hoje.</p>
</div>
<Badge color="amber" variant="soft">{pendingAppts} Novos Pedidos</Badge>
<Badge color="indigo" variant="soft">{pendingAppts} Novos Pedidos</Badge>
</div>
{/* Secção de Pedidos Pendentes */}
<Card className="p-6 border-amber-200">
<Card className="p-6 border-indigo-200">
<div className="flex items-center gap-2 mb-4 pb-4 border-b border-slate-100">
<Clock className="text-amber-500" size={20} />
<Clock className="text-indigo-500" size={20} />
<h3 className="font-bold text-slate-900">Aguardam Aprovação</h3>
<Badge color="slate" variant="soft" className="ml-auto">{pendingAppts}</Badge>
</div>
@@ -569,7 +569,7 @@ function DashboardInner({ shop }: { shop: BarberShop }) {
const aptDate = new Date(a.date.replace(' ', 'T'));
return (
<div key={a.id} className="flex flex-col sm:flex-row sm:items-center justify-between p-4 bg-amber-50/50 border border-amber-100 rounded-lg gap-4">
<div key={a.id} className="flex flex-col sm:flex-row sm:items-center justify-between p-4 bg-indigo-50/50 border border-indigo-100 rounded-lg gap-4">
<div className="flex-1 grid grid-cols-1 sm:grid-cols-3 gap-4">
<div>
<p className="text-xs text-slate-500 mb-1">Cliente</p>
@@ -582,7 +582,7 @@ function DashboardInner({ shop }: { shop: BarberShop }) {
</div>
<div>
<p className="text-xs text-slate-500 mb-1">Data / Hora</p>
<p className="font-semibold text-amber-700">
<p className="font-semibold text-indigo-700">
{aptDate.toLocaleDateString('pt-PT', { day: 'numeric', month: 'short' })} às {aptDate.toLocaleTimeString('pt-PT', { hour: '2-digit', minute: '2-digit' })}
</p>
</div>
@@ -596,7 +596,7 @@ function DashboardInner({ shop }: { shop: BarberShop }) {
Recusar
</Button>
<Button
className="bg-amber-500 hover:bg-amber-600 text-white"
className="bg-indigo-600 hover:bg-indigo-700 text-white"
onClick={() => updateAppointmentStatus(a.id, 'confirmado')}
>
Aceitar
@@ -744,16 +744,16 @@ function DashboardInner({ shop }: { shop: BarberShop }) {
return sum + (prod?.price ?? 0) * item.qty;
}, 0);
return (
<div key={o.id} className="p-4 border border-slate-200 rounded-lg hover:border-amber-300 transition-colors">
<div key={o.id} className="p-4 border border-slate-200 rounded-lg hover:border-indigo-300 transition-colors">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<p className="font-bold text-amber-700">{currency(productTotal)}</p>
<Badge color={o.status === 'pendente' ? 'amber' : o.status === 'confirmado' ? 'green' : o.status === 'concluido' ? 'green' : 'red'}>
<p className="font-bold text-indigo-700">{currency(productTotal)}</p>
<Badge color={o.status === 'pendente' ? 'indigo' : o.status === 'confirmado' ? 'green' : o.status === 'concluido' ? 'green' : 'red'}>
{o.status === 'pendente' ? 'Pendente' : o.status === 'confirmado' ? 'Confirmado' : o.status === 'concluido' ? 'Concluído' : 'Cancelado'}
</Badge>
</div>
<select
className="text-sm border border-slate-300 rounded-lg px-3 py-2 focus:border-amber-500 focus:ring-2 focus:ring-amber-500/30"
className="text-sm border border-slate-300 rounded-lg px-3 py-2 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/30"
value={o.status}
onChange={(e) => updateOrderStatus(o.id, e.target.value as any)}
>
@@ -771,7 +771,7 @@ function DashboardInner({ shop }: { shop: BarberShop }) {
return (
<div key={item.refId} className="flex items-center justify-between text-sm bg-slate-50 rounded px-2 py-1">
<span className="text-slate-700">{prod?.name ?? 'Produto'} x{item.qty}</span>
<span className="text-amber-600 font-semibold">{currency((prod?.price ?? 0) * item.qty)}</span>
<span className="text-indigo-600 font-semibold">{currency((prod?.price ?? 0) * item.qty)}</span>
</div>
);
})}
@@ -805,7 +805,7 @@ function DashboardInner({ shop }: { shop: BarberShop }) {
<p className="text-sm text-slate-600">Duração: {s.duration} min</p>
</div>
<div className="flex items-center gap-4">
<span className="text-lg font-bold text-amber-600">{currency(s.price)}</span>
<span className="text-lg font-bold text-indigo-600">{currency(s.price)}</span>
<Button variant="danger" size="sm" onClick={() => deleteService(shop.id, s.id)}>
<Trash2 size={16} />
</Button>
@@ -839,8 +839,8 @@ function DashboardInner({ shop }: { shop: BarberShop }) {
<Badge color="slate" variant="soft">{shop.products.length} produtos</Badge>
</div>
{lowStock.length > 0 && (
<div className="mb-4 p-3 bg-amber-50 border border-amber-200 rounded-lg">
<p className="text-sm font-semibold text-amber-800 flex items-center gap-2">
<div className="mb-4 p-3 bg-indigo-50 border border-indigo-200 rounded-lg">
<p className="text-sm font-semibold text-indigo-800 flex items-center gap-2">
<AlertTriangle size={16} />
Atenção: {lowStock.length} {lowStock.length === 1 ? 'produto com stock baixo' : 'produtos com stock baixo'}
</p>
@@ -850,18 +850,18 @@ function DashboardInner({ shop }: { shop: BarberShop }) {
{shop.products.map((p) => (
<div
key={p.id}
className={`flex items-center justify-between p-4 border rounded-lg ${p.stock <= 3 ? 'border-amber-300 bg-amber-50' : 'border-slate-200'
className={`flex items-center justify-between p-4 border rounded-lg ${p.stock <= 3 ? 'border-indigo-300 bg-indigo-50' : 'border-slate-200'
}`}
>
<div className="flex-1">
<div className="flex items-center gap-2 mb-1">
<p className="font-bold text-slate-900">{p.name}</p>
{p.stock <= 3 && <Badge color="amber" variant="solid">Stock baixo</Badge>}
{p.stock <= 3 && <Badge color="indigo" variant="solid">Stock baixo</Badge>}
</div>
<p className="text-sm text-slate-600">Stock: {p.stock} unidades</p>
</div>
<div className="flex items-center gap-4">
<span className="text-lg font-bold text-amber-600">{currency(p.price)}</span>
<span className="text-lg font-bold text-indigo-600">{currency(p.price)}</span>
<div className="flex items-center gap-2">
<Button variant="outline" size="sm" onClick={() => updateProductStock(p, -1)}>
<Minus size={14} />

View File

@@ -63,7 +63,7 @@ export default function EventsCreate() {
<div className="max-w-xl mx-auto py-8">
<Card className="p-8 space-y-6">
<div className="flex items-center gap-3">
<div className="p-3 bg-gradient-to-br from-amber-500 to-amber-600 rounded-xl text-white shadow-lg">
<div className="p-3 bg-gradient-to-br from-indigo-500 to-indigo-600 rounded-xl text-white shadow-lg">
<CalendarPlus size={22} />
</div>
<div>

View File

@@ -57,12 +57,12 @@ export default function Explore() {
<section className="space-y-4 text-center md:text-left">
<div className="flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
<div className="space-y-1">
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-amber-100 text-amber-700 text-[10px] font-black uppercase tracking-widest mb-2">
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-indigo-100 text-indigo-700 text-[10px] font-black uppercase tracking-widest mb-2">
<Star size={10} fill="currentColor" />
<span>As melhores Barbearias</span>
<span>As Nossas Barbearias</span>
</div>
<h1 className="text-4xl md:text-5xl font-black text-slate-900 tracking-tighter">
Explorar <span className="text-amber-600">Espaços</span>
<h1 className="text-4xl md:text-5xl font-black text-slate-900 tracking-tighter uppercase italic">
Ver <span className="text-indigo-600">Barbearias</span>
</h1>
<p className="text-slate-500 font-medium max-w-md">Descubra barbearias exclusivas e reserve o seu próximo corte em segundos.</p>
</div>
@@ -91,14 +91,14 @@ export default function Explore() {
<Chip
active={filter === 'todas'}
onClick={() => setFilter('todas')}
className={`h-11 px-6 rounded-2xl font-bold uppercase tracking-tight transition-all ${filter === 'todas' ? '!bg-slate-900 !text-amber-500 border-none shadow-lg' : 'bg-slate-100 text-slate-500 hover:bg-slate-200'}`}
className={`h-11 px-6 rounded-2xl font-bold uppercase tracking-tight transition-all ${filter === 'todas' ? '!bg-slate-900 !text-indigo-400 border-none shadow-lg' : 'bg-slate-100 text-slate-500 hover:bg-slate-200'}`}
>
Todas
</Chip>
<Chip
active={filter === 'top'}
onClick={() => setFilter('top')}
className={`h-11 px-6 rounded-2xl font-bold uppercase tracking-tight transition-all ${filter === 'top' ? '!bg-slate-900 !text-amber-500 border-none shadow-lg' : 'bg-slate-100 text-slate-500 hover:bg-slate-200'}`}
className={`h-11 px-6 rounded-2xl font-bold uppercase tracking-tight transition-all ${filter === 'top' ? '!bg-slate-900 !text-indigo-400 border-none shadow-lg' : 'bg-slate-100 text-slate-500 hover:bg-slate-200'}`}
>
Top Avaliadas
</Chip>
@@ -106,7 +106,7 @@ export default function Explore() {
<select
value={sortBy}
onChange={(e) => setSortBy(e.target.value as typeof sortBy)}
className="h-11 rounded-2xl border-none bg-slate-100 px-4 text-sm font-bold text-slate-700 focus:ring-2 focus:ring-amber-500/20"
className="h-11 rounded-2xl border-none bg-slate-100 px-4 text-sm font-bold text-slate-700 focus:ring-2 focus:ring-indigo-500/20"
>
<option value="avaliacao">Melhor avaliação</option>
<option value="servicos">Mais serviços</option>
@@ -117,7 +117,7 @@ export default function Explore() {
{!useApp().shopsReady ? (
<div className="py-24 text-center">
<div className="inline-block w-12 h-12 border-4 border-slate-200 border-t-amber-600 rounded-full animate-spin mb-4" />
<div className="inline-block w-12 h-12 border-4 border-slate-200 border-t-indigo-600 rounded-full animate-spin mb-4" />
<p className="text-slate-500 font-bold uppercase tracking-widest text-xs">A carregar espaços...</p>
</div>
) : filtered.length === 0 ? (
@@ -129,7 +129,7 @@ export default function Explore() {
<p className="text-2xl font-black text-slate-900 tracking-tight">Nenhuma barbearia encontrada</p>
<p className="text-slate-500 font-medium">Tente ajustar o termo de pesquisa ou os filtros ativos.</p>
</div>
<Button variant="ghost" onClick={() => {setQuery(''); setFilter('todas');}} className="font-bold text-amber-600 hover:text-amber-700">
<Button variant="ghost" onClick={() => {setQuery(''); setFilter('todas');}} className="font-bold text-indigo-600 hover:text-indigo-700">
Limpar Tudo
</Button>
</Card>

View File

@@ -19,7 +19,7 @@ import { useApp } from '../context/AppContext';
import { mockShops } from '../data/mock';
export default function Landing() {
const { user } = useApp();
const { user, shops } = useApp();
const navigate = useNavigate();
useEffect(() => {
@@ -28,36 +28,36 @@ export default function Landing() {
navigate(target, { replace: true });
}, [user, navigate]);
const featuredShops = mockShops.slice(0, 3);
const featuredShops = shops.slice(0, 3);
return (
<div className="space-y-24 md:space-y-32 pb-24">
{/* Hero Section - Midnight Luxury Style */}
<section className="relative overflow-hidden rounded-[3rem] obsidian-gradient text-white px-8 py-20 md:px-16 md:py-32 shadow-[0_20px_50px_rgba(0,0,0,0.3)] border border-white/5">
<div className="absolute inset-0 bg-[url('https://www.transparenttextures.com/patterns/carbon-fibre.png')] opacity-20 pointer-events-none"></div>
<div className="absolute top-0 right-0 w-[500px] h-[500px] bg-amber-500/10 rounded-full blur-[120px] -translate-y-1/2 translate-x-1/2"></div>
<div className="absolute top-0 right-0 w-[500px] h-[500px] bg-indigo-500/10 rounded-full blur-[120px] -translate-y-1/2 translate-x-1/2"></div>
<div className="absolute bottom-0 left-0 w-[500px] h-[500px] bg-slate-500/10 rounded-full blur-[120px] translate-y-1/2 -translate-x-1/2"></div>
<div className="relative z-10 space-y-10 max-w-5xl">
<div className="inline-flex items-center gap-2 px-4 py-2 bg-white/5 backdrop-blur-md rounded-full text-[10px] font-black uppercase tracking-[0.3em] w-fit border border-white/10 animate-fade-in text-amber-500">
<div className="inline-flex items-center gap-2 px-4 py-2 bg-white/5 backdrop-blur-md rounded-full text-[10px] font-black uppercase tracking-[0.3em] w-fit border border-white/10 animate-fade-in text-indigo-400">
<Sparkles size={14} className="animate-pulse" />
<span>O Novo Standard do Cuidado Masculino</span>
<span>A Solução Completa para a Sua Barbearia</span>
</div>
<h1 className="text-6xl md:text-8xl font-black leading-[0.9] tracking-tighter text-balance uppercase italic">
Elegância em cada <br />
<span className="gold-gradient bg-clip-text text-transparent italic">Agendamento</span>
Gestão Simplificada da sua <br />
<span className="indigo-gradient bg-clip-text text-transparent italic">Barbearia</span>
</h1>
<p className="text-xl md:text-2xl text-slate-300 max-w-2xl leading-relaxed font-medium">
Transforme a rotina da sua barbearia com uma experiência digital digna de um cavalheiro.
Mobile-first, premium e inteligente.
Organize a sua barbearia com facilidade.
Simples, rápido e eficiente.
</p>
<div className="flex flex-wrap gap-6 pt-6">
<Button asChild size="lg" className="h-16 px-10 bg-white text-slate-950 hover:bg-amber-500 hover:text-white font-black uppercase tracking-widest text-xs transition-all duration-300 rounded-2xl shadow-2xl">
<Button asChild size="lg" className="h-16 px-10 bg-white text-slate-950 hover:bg-indigo-600 hover:text-white font-black uppercase tracking-widest text-xs transition-all duration-300 rounded-2xl shadow-2xl">
<Link to="/explorar" className="flex items-center gap-3">
Explorar Espaços
Ver Barbearias
<ArrowRight size={18} />
</Link>
</Button>
@@ -70,47 +70,30 @@ export default function Landing() {
<div className="grid grid-cols-3 gap-10 pt-12 border-t border-white/10 max-w-2xl">
<div className="space-y-1">
<div className="text-4xl md:text-5xl font-black tracking-tighter italic">500+</div>
<div className="text-[10px] font-black text-slate-400 uppercase tracking-widest">Espaços de Luxo</div>
<div className="text-[10px] font-black text-slate-400 uppercase tracking-widest">Lojas Registadas</div>
</div>
<div className="space-y-1">
<div className="text-4xl md:text-5xl font-black tracking-tighter italic">10K+</div>
<div className="text-[10px] font-black text-slate-400 uppercase tracking-widest">Cortes Marcados</div>
</div>
<div className="space-y-1">
<div className="text-4xl md:text-5xl font-black tracking-tighter gold-gradient bg-clip-text text-transparent italic">4.9</div>
<div className="text-[10px] font-black text-slate-400 uppercase tracking-widest">Rating de Elite</div>
<div className="text-4xl md:text-5xl font-black tracking-tighter indigo-gradient bg-clip-text text-transparent italic">4.9</div>
<div className="text-[10px] font-black text-slate-400 uppercase tracking-widest">Média de Avaliação</div>
</div>
</div>
</div>
</section>
{/* Hero Image Mockup (Place with a generated-like look) */}
<section className="relative -mt-32 px-6">
<div className="max-w-6xl mx-auto rounded-[3rem] overflow-hidden shadow-[0_50px_100px_rgba(0,0,0,0.5)] border-4 border-white/10 glass-card">
<div className="aspect-video bg-slate-900 flex items-center justify-center relative overflow-hidden">
<div className="absolute inset-0 bg-gradient-to-br from-slate-900 via-slate-800 to-slate-950 flex items-center justify-center opacity-80" />
<div className="relative z-10 text-center space-y-6">
<Scissors size={80} className="text-amber-500 mx-auto mb-4" />
<h3 className="text-2xl font-black text-white uppercase italic tracking-tighter">Smart Agenda Elite Edition</h3>
<div className="flex gap-4 justify-center">
<div className="w-12 h-1 bg-amber-500 rounded-full" />
<div className="w-12 h-1 bg-white/20 rounded-full" />
<div className="w-12 h-1 bg-white/20 rounded-full" />
</div>
</div>
</div>
</div>
</section>
{/* Features - Minimalist & Bold */}
<section className="max-w-7xl mx-auto px-6">
<div className="text-center md:text-left mb-16 flex flex-col md:flex-row md:items-end justify-between gap-6">
<div className="space-y-4">
<h2 className="text-5xl md:text-6xl font-black text-slate-900 tracking-tighter uppercase italic pr-8 border-l-[12px] border-amber-500 pl-8">
Ecossistema <br /> <span className="text-amber-600">Completo</span>
<h2 className="text-5xl md:text-6xl font-black text-slate-900 tracking-tighter uppercase italic pr-8 border-l-[12px] border-indigo-600 pl-8">
Tudo para o <br /> <span className="text-indigo-700">Seu Negócio</span>
</h2>
<p className="text-xl text-slate-500 font-medium max-w-xl">
Tudo o que a sua barbearia precisa para escalar com sofisticação.
Tudo o que precisa para gerir e fazer crescer a sua barbearia.
</p>
</div>
</div>
@@ -119,30 +102,27 @@ export default function Landing() {
{[
{
icon: Clock,
title: 'Gestão Cirúrgica',
desc: 'Controle de horários com precisão absoluta. Slot management inteligente e automação de reserva.',
color: 'bg-slate-950 shadow-[0_15px_35px_rgba(0,0,0,0.15)]'
title: 'Agenda Prática',
desc: 'Gerencie os horários dos seus barbeiros e evite conflitos de agenda de forma automática.',
},
{
icon: ShoppingBag,
title: 'Curadoria de Produtos',
desc: 'Venda produtos de elite diretamente no ecossistema. Gestão de stock e carrinho omnicanal.',
color: 'bg-white border-2 border-slate-50'
title: 'Venda de Produtos',
desc: 'Venda pomadas, óleos e outros produtos diretamente aos seus clientes através da plataforma.',
},
{
icon: BarChart3,
title: 'Analytics de Luxo',
desc: 'Relatórios detalhados de faturamento, performance de barbeiros e taxas de retenção.',
color: 'bg-white border-2 border-slate-50'
title: 'Controle de Ganhos',
desc: 'Saiba exatamente quanto a barbearia está faturando e quais os serviços mais procurados.',
},
].map((feature) => (
<Card key={feature.title} className={`p-10 space-y-6 rounded-[2.5rem] transition-all duration-500 hover:-translate-y-2 group ${feature.color}`}>
<div className={`w-16 h-16 rounded-2xl flex items-center justify-center shadow-inner ${feature.icon === Clock ? 'bg-amber-500 text-slate-900' : 'bg-slate-900 text-amber-500'}`}>
<Card key={feature.title} className="p-10 space-y-6 rounded-[2.5rem] transition-all duration-500 hover:-translate-y-2 group bg-white border-2 border-slate-50 shadow-sm hover:shadow-xl">
<div className="w-16 h-16 rounded-2xl flex items-center justify-center bg-slate-900 text-indigo-400 group-hover:bg-indigo-600 group-hover:text-white transition-colors duration-300">
<feature.icon size={32} />
</div>
<div className="space-y-3">
<h3 className={`text-2xl font-black tracking-tight uppercase italic ${feature.icon === Clock ? 'text-white' : 'text-slate-900'}`}>{feature.title}</h3>
<p className={`text-sm leading-relaxed font-medium ${feature.icon === Clock ? 'text-slate-400' : 'text-slate-500'}`}>{feature.desc}</p>
<h3 className="text-2xl font-black tracking-tight uppercase italic text-slate-900">{feature.title}</h3>
<p className="text-sm leading-relaxed font-medium text-slate-600">{feature.desc}</p>
</div>
</Card>
))}
@@ -155,23 +135,23 @@ export default function Landing() {
<div className="max-w-6xl mx-auto px-6 relative z-10">
<div className="text-center mb-20">
<h2 className="text-5xl md:text-6xl font-black text-white tracking-tighter uppercase italic mb-6">
A Jornada do <span className="gold-gradient bg-clip-text text-transparent">Cavalheiro</span>
Como <span className="indigo-gradient bg-clip-text text-transparent">Funciona</span>
</h2>
<div className="w-24 h-1 bg-amber-500 mx-auto rounded-full" />
<div className="w-24 h-1 bg-indigo-600 mx-auto rounded-full" />
</div>
<div className="grid md:grid-cols-3 gap-16 relative">
{[
{ step: '01', title: 'Descobrir', desc: 'Encontre os espaços mais exclusivos da cidade com avaliações reais.' },
{ step: '02', title: 'Personalizar', desc: 'Escolha o seu barbeiro de confiança e o seu horário preferido.' },
{ step: '03', title: 'Vivenciar', desc: 'Receba o tratamento de elite que você merece, sem esperas.' },
{ step: '01', title: 'Explorar', desc: 'Encontre as barbearias mais próximas e veja as fotos e avaliações reais.' },
{ step: '02', title: 'Agendar', desc: 'Escolha o seu barbeiro e o serviço. Marque o dia e hora que preferir.' },
{ step: '03', title: 'Cortar', desc: 'Apareça na barbearia à hora marcada e desfrute do serviço. Sem esperas.' },
].map((item, idx) => (
<div key={item.step} className="text-center space-y-8 relative group">
<div className="relative">
<div className="text-8xl font-black text-white/5 absolute -top-12 left-1/2 -translate-x-1/2 select-none group-hover:text-amber-500/10 transition-colors duration-700">
<div className="text-8xl font-black text-white/5 absolute -top-12 left-1/2 -translate-x-1/2 select-none group-hover:text-indigo-500/10 transition-colors duration-700">
{item.step}
</div>
<div className="w-20 h-20 mx-auto rounded-full obsidian-gradient border-2 border-white/10 flex items-center justify-center text-amber-500 text-2xl font-black italic shadow-[0_0_30px_rgba(245,158,11,0.2)]">
<div className="w-20 h-20 mx-auto rounded-full obsidian-gradient border-2 border-white/10 flex items-center justify-center text-indigo-400 text-2xl font-black italic shadow-[0_0_30px_rgba(79,70,229,0.2)]">
{item.step}
</div>
</div>
@@ -189,9 +169,9 @@ export default function Landing() {
<section className="max-w-7xl mx-auto px-6">
<div className="flex flex-col md:flex-row items-center justify-between mb-16 gap-6">
<div className="space-y-2 text-center md:text-left">
<h2 className="text-4xl md:text-5xl font-black text-slate-900 tracking-tighter uppercase italic">
Clubes <span className="text-amber-600">Membros</span>
</h2>
<h1 className="text-4xl md:text-5xl font-black text-slate-900 tracking-tighter uppercase italic">
Descobrir <span className="text-indigo-600">Barbearias</span>
</h1>
<p className="text-slate-500 font-bold uppercase tracking-[0.2em] text-xs">As melhores barbearias do país</p>
</div>
<Button asChild variant="ghost" className="h-14 px-8 rounded-2xl bg-slate-50 border border-slate-100 font-black text-slate-900 uppercase tracking-widest text-xs hover:bg-slate-100 transition-all">
@@ -212,21 +192,21 @@ export default function Landing() {
{/* Final CTA - Immersive Dark */}
<section className="px-6">
<div className="max-w-6xl mx-auto relative overflow-hidden rounded-[4rem] obsidian-gradient text-white px-8 py-20 md:px-20 md:py-24 shadow-2xl border border-white/5">
<div className="absolute top-0 right-0 w-[400px] h-[400px] bg-amber-500/10 rounded-full blur-[100px]" />
<div className="absolute top-0 right-0 w-[400px] h-[400px] bg-indigo-500/10 rounded-full blur-[100px]" />
<div className="absolute bottom-0 left-0 w-[400px] h-[400px] bg-slate-500/10 rounded-full blur-[100px]" />
<div className="relative text-center space-y-12 max-w-3xl mx-auto">
<h2 className="text-5xl md:text-7xl font-black tracking-tighter uppercase italic leading-[0.9]">
Faça Parte <br /> do <span className="gold-gradient bg-clip-text text-transparent">Legado</span>
Registe a <br /> sua <span className="indigo-gradient bg-clip-text text-transparent">Barbearia</span>
</h2>
<p className="text-xl text-slate-400 font-medium leading-relaxed">
Centenas de profissionais elevaram o seu negócio ao próximo nível.
A sua barbearia merece o melhor.
</p>
<div className="flex flex-wrap justify-center gap-6 pt-4">
<Button asChild size="lg" className="h-16 px-10 bg-white text-slate-950 hover:bg-amber-500 hover:text-white font-black uppercase tracking-widest text-xs rounded-2xl transition-all shadow-2xl">
<Button asChild size="lg" className="h-16 px-10 bg-white text-slate-950 hover:bg-indigo-600 hover:text-white font-black uppercase tracking-widest text-xs rounded-2xl transition-all shadow-2xl">
<Link to="/registo" className="flex items-center gap-3">
Criar Conta Grátis
Criar Conta
<ArrowRight size={18} />
</Link>
</Button>

View File

@@ -14,8 +14,8 @@ import { Calendar, ShoppingBag, User, Clock, Heart, Star, MapPin, CheckCircle2 }
import { supabase } from '../lib/supabase'
import { ReviewModal } from '../components/ReviewModal'
const statusColor: Record<string, 'amber' | 'green' | 'slate' | 'red'> = {
pendente: 'amber',
const statusColor: Record<string, 'indigo' | 'green' | 'slate' | 'red'> = {
pendente: 'indigo',
confirmado: 'green',
concluido: 'green',
cancelado: 'red',
@@ -90,7 +90,7 @@ export default function Profile() {
return (
<div className="flex items-center justify-center py-20">
<div className="text-center">
<div className="w-10 h-10 border-4 border-slate-200 border-t-amber-500 rounded-full animate-spin mx-auto mb-3" />
<div className="w-10 h-10 border-4 border-slate-200 border-t-indigo-500 rounded-full animate-spin mx-auto mb-3" />
<p className="text-slate-500 text-sm">A carregar perfil...</p>
</div>
</div>
@@ -100,15 +100,15 @@ export default function Profile() {
if (!authId) {
return (
<div className="text-center py-16">
<div className="w-16 h-16 bg-amber-100 rounded-2xl flex items-center justify-center mx-auto mb-4">
<User size={28} className="text-amber-600" />
<div className="w-16 h-16 bg-indigo-100 rounded-2xl flex items-center justify-center mx-auto mb-4">
<User size={28} className="text-indigo-600" />
</div>
<p className="text-slate-700 font-semibold mb-1">Sessão não encontrada</p>
<p className="text-slate-500 text-sm mb-4">Faz login para ver o teu perfil.</p>
<button
type="button"
onClick={() => navigate('/login', { replace: true })}
className="px-5 py-2 bg-amber-500 text-white text-sm font-semibold rounded-xl hover:bg-amber-600 transition-colors"
className="px-5 py-2 bg-indigo-600 text-white text-sm font-semibold rounded-xl hover:bg-slate-900 transition-colors"
>
Ir para login
</button>
@@ -131,20 +131,20 @@ export default function Profile() {
)}
<div className="max-w-4xl mx-auto space-y-12 pb-20">
{/* Profile Header - Luxury Style */}
{/* Cabeçalho do Perfil */}
<section className="relative overflow-hidden rounded-[3rem] obsidian-gradient text-white p-8 md:p-12 shadow-2xl border border-white/5">
<div className="absolute top-0 right-0 w-64 h-64 bg-amber-500/10 rounded-full blur-[80px] -translate-y-1/2 translate-x-1/2" />
<div className="absolute top-0 right-0 w-64 h-64 bg-indigo-500/10 rounded-full blur-[80px] -translate-y-1/2 translate-x-1/2" />
<div className="relative z-10 flex flex-col md:flex-row items-center gap-8 md:text-left text-center">
<div className="relative group">
<div className="absolute inset-0 bg-amber-500 blur-2xl opacity-20 group-hover:opacity-40 transition-opacity" />
<div className="w-24 h-24 bg-white/10 backdrop-blur-xl border-2 border-white/20 rounded-[2rem] flex items-center justify-center text-amber-500 shadow-2xl relative z-10 transition-transform duration-500 hover:rotate-6">
<div className="absolute inset-0 bg-indigo-500 blur-2xl opacity-20 group-hover:opacity-40 transition-opacity" />
<div className="w-24 h-24 bg-white/10 backdrop-blur-xl border-2 border-white/20 rounded-[2rem] flex items-center justify-center text-indigo-400 shadow-2xl relative z-10 transition-transform duration-500 hover:rotate-6">
<User size={48} />
</div>
</div>
<div className="space-y-3">
<div className="inline-flex items-center gap-2 px-3 py-1 bg-white/5 border border-white/10 rounded-full text-[10px] font-black uppercase tracking-[0.2em] text-amber-500">
<div className="inline-flex items-center gap-2 px-3 py-1 bg-white/5 border border-white/10 rounded-full text-[10px] font-black uppercase tracking-[0.2em] text-indigo-400">
<Star size={12} fill="currentColor" />
<span>Membro de Elite</span>
<span>Utilizador Registado</span>
</div>
<h1 className="text-4xl md:text-5xl font-black tracking-tighter uppercase italic leading-[0.9]">
{displayName}
@@ -169,7 +169,7 @@ export default function Profile() {
<Link key={shop.id} to={`/barbearia/${shop.id}`}>
<Card className="p-2 border-none glass-card rounded-[2rem] shadow-lg shadow-slate-200/50 hover:-translate-y-1 transition-all duration-300 group">
<div className="flex items-center gap-4 p-4">
<div className="w-16 h-16 rounded-2xl overflow-hidden border-2 border-slate-50 shadow-inner group-hover:border-amber-200 transition-colors">
<div className="w-16 h-16 rounded-2xl overflow-hidden border-2 border-slate-50 shadow-inner group-hover:border-indigo-200 transition-colors">
{shop.imageUrl ? (
<img src={shop.imageUrl} alt={shop.name} className="w-full h-full object-cover" />
) : (
@@ -179,10 +179,10 @@ export default function Profile() {
)}
</div>
<div className="flex-1 min-w-0">
<p className="font-black text-slate-900 uppercase italic tracking-tight group-hover:text-amber-600 transition-colors truncate">{shop.name}</p>
<p className="font-black text-slate-900 uppercase italic tracking-tight group-hover:text-indigo-600 transition-colors truncate">{shop.name}</p>
<div className="flex items-center gap-3 mt-1">
<div className="flex items-center gap-1 text-[10px] font-black text-amber-600 uppercase tracking-widest">
<Star size={10} className="fill-amber-500" />
<div className="flex items-center gap-1 text-[10px] font-black text-indigo-600 uppercase tracking-widest">
<Star size={10} className="fill-indigo-500" />
{shop.rating.toFixed(1)}
</div>
<div className="flex items-center gap-1 text-[10px] font-black text-slate-400 uppercase tracking-widest truncate">
@@ -204,7 +204,7 @@ export default function Profile() {
<section className="lg:col-span-3 space-y-6">
<div className="flex items-center justify-between px-2">
<div className="flex items-center gap-2 text-slate-900">
<Calendar size={16} className="text-amber-600" />
<Calendar size={16} className="text-indigo-600" />
<h2 className="text-sm font-black uppercase tracking-[0.3em]">Minha Agenda</h2>
</div>
</div>
@@ -214,7 +214,7 @@ export default function Profile() {
<Calendar size={64} className="mx-auto text-slate-100 mb-6" />
<h3 className="text-xl font-black text-slate-900 uppercase italic tracking-tight">Sem Reservas</h3>
<p className="text-slate-400 font-medium italic mt-2">Sua jornada de estilo ainda não começou.</p>
<Button asChild className="mt-8 h-12 px-8 bg-slate-900 text-amber-500 font-black rounded-2xl uppercase tracking-widest text-[10px] italic">
<Button asChild className="mt-8 h-12 px-8 bg-slate-900 text-indigo-400 font-black rounded-2xl uppercase tracking-widest text-[10px] italic">
<Link to="/explorar">Agendar Agora</Link>
</Button>
</Card>
@@ -246,7 +246,7 @@ export default function Profile() {
<div className="flex items-center justify-between pt-4 border-t border-slate-50">
{service && (
<div className="flex items-center gap-2 text-[10px] font-black text-slate-500 uppercase tracking-widest">
<Clock size={12} className="text-amber-600" />
<Clock size={12} className="text-indigo-600" />
{service.name} · {service.duration} MIN
</div>
)}
@@ -254,10 +254,10 @@ export default function Profile() {
{canReview && (
<button
onClick={() => setReviewTarget({ appointmentId: a.id, shopId: a.shopId, shopName: shop?.name ?? 'Barbearia' })}
className="flex items-center gap-2 px-4 py-2 bg-amber-500 hover:bg-slate-900 text-white hover:text-amber-500 rounded-xl transition-all duration-300 transform active:scale-95 shadow-lg shadow-amber-500/20"
className="flex items-center gap-2 px-4 py-2 bg-indigo-600 hover:bg-slate-900 text-white hover:text-indigo-400 rounded-xl transition-all duration-300 transform active:scale-95 shadow-lg shadow-indigo-500/20"
>
<Star size={12} className="fill-current" />
<span className="text-[10px] font-black uppercase tracking-widest">Avaliar Experiência</span>
<span className="text-[10px] font-black uppercase tracking-widest">Avaliar Atendimento</span>
</button>
)}
@@ -280,7 +280,7 @@ export default function Profile() {
<section className="lg:col-span-2 space-y-6">
<div className="flex items-center justify-between px-2">
<div className="flex items-center gap-2 text-slate-900">
<ShoppingBag size={16} className="text-amber-600" />
<ShoppingBag size={16} className="text-indigo-600" />
<h2 className="text-sm font-black uppercase tracking-[0.3em]">Pedidos</h2>
</div>
</div>
@@ -298,7 +298,7 @@ export default function Profile() {
<div className="space-y-3">
<div className="flex items-center justify-between">
<h3 className="font-black text-slate-900 uppercase italic tracking-tight truncate max-w-[120px]">{shop?.name}</h3>
<div className="text-lg font-black text-amber-600 tracking-tighter">{currency(o.total)}</div>
<div className="text-lg font-black text-indigo-600 tracking-tighter">{currency(o.total)}</div>
</div>
<div className="flex items-center justify-between pt-2 border-t border-slate-50">
<div className="text-[9px] font-black text-slate-400 uppercase tracking-widest">

View File

@@ -77,12 +77,12 @@ export default function ShopDetails() {
<div className="absolute bottom-10 left-10 space-y-3">
<div className="flex items-center gap-2 bg-slate-900/40 backdrop-blur-md border border-white/20 w-fit px-3 py-1 rounded-full">
<Star size={14} className="fill-amber-500 text-amber-500" />
<Star size={14} className="fill-indigo-500 text-indigo-500" />
<span className="text-white text-xs font-black tracking-widest">{(shop.rating || 0).toFixed(1)} EXCELENTE</span>
</div>
<h1 className="text-4xl md:text-5xl font-black text-white tracking-tighter">{shop.name}</h1>
<div className="flex items-center gap-2 text-white/90">
<MapPin size={16} className="text-amber-500" />
<MapPin size={16} className="text-indigo-600" />
<p className="text-base font-medium">{shop.address}</p>
</div>
</div>