import React, { useState, useEffect, useRef } from "react";
import { createRoot } from "react-dom/client";
import { GoogleGenAI } from "@google/genai";
// --- Configuration ---
// Nota: Em um app real, o número do salão viria de uma config.
const SALON_PHONE_NUMBER = "5512997335155"; // Exemplo
const API_KEY = process.env.API_KEY;
const ADMIN_PASSWORD = "242024Julio";
// --- Data Models ---
interface Service {
id: string;
name: string;
price: string;
duration: string;
description: string;
image: string; // Emoji or Icon class for simplicity
}
interface Product {
id: string;
name: string;
price: number;
description: string;
image: string; // URL da imagem
}
const SERVICES: Service[] = [
{
id: "corte-fem",
name: "Corte & Estilo",
price: "A partir de R$180,00",
duration: "60 min",
description: "Lavagem, corte visagista e finalização.",
image: "fa-scissors",
},
{
id: "mechas",
name: "Mechas & Luzes",
price: "A partir de R$350,00",
duration: "180 min",
description: "Técnicas de balayage, ombré ou luzes tradicionais.",
image: "fa-wand-magic-sparkles",
},
{
id: "hidratacao",
name: "Hidratação Profunda",
price: "A partir de R$80,00",
duration: "45 min",
description: "Reconstrução capilar com produtos premium.",
image: "fa-droplet",
},
{
id: "manicure",
name: "Manicure & Pedicure",
price: "A partir de R$80,00",
duration: "60 min",
description: "Cuidado completo para mãos ou pés.",
image: "fa-hand-sparkles",
},
];
const INITIAL_PRODUCTS: Product[] = [
{
id: "p1",
name: "Kit Shampoo & Condicionador Premium",
price: 189.90,
description: "Manutenção diária para cabelos coloridos. Protege a cor e hidrata.",
image: "https://images.unsplash.com/photo-1535585209827-a15fcdbc4c2d?q=80&w=2070&auto=format&fit=crop"
},
{
id: "p2",
name: "Óleo Reparador de Pontas",
price: 85.00,
description: "Nutrição intensa e brilho instantâneo sem pesar.",
image: "https://images.unsplash.com/photo-1620916566398-39f1143ab7be?q=80&w=1887&auto=format&fit=crop"
},
{
id: "p3",
name: "Máscara de Reconstrução",
price: 120.00,
description: "Recupera a fibra capilar danificada em 5 minutos.",
image: "https://images.unsplash.com/photo-1556228720-19cb731de486?q=80&w=1931&auto=format&fit=crop"
}
];
const PROFESSIONALS = ["Gabriele Mira", "Suellen", "Qualquer Disponível"];
// --- Components ---
function Notification({ message, type, onClose }: { message: string, type: 'error' | 'success', onClose: () => void }) {
useEffect(() => {
const timer = setTimeout(() => {
onClose();
}, 2000);
return () => clearTimeout(timer);
}, [onClose]);
return (
{message}
);
}
function Header({ setView, currentView }) {
return (
setView("home")}
style={{ cursor: "pointer", display: "flex", alignItems: "center", gap: "10px" }}
>
Studio Gabriele Mira
);
}
function Hero({ onBookNow }) {
return (
Realce sua beleza natural
Experiência premium em cuidados capilares e estética. Agende seu horário e transforme seu visual hoje mesmo.
);
}
function ServiceList({ onSelectService }) {
return (
Nossos Serviços
{SERVICES.map((service) => (
{service.name}
{service.description}
{service.price} | {service.duration}
))}
);
}
function ProductForm({ product, onSave, onCancel }) {
const [formData, setFormData] = useState({
id: product ? product.id : Date.now().toString(),
name: product ? product.name : "",
price: product ? product.price : 0,
description: product ? product.description : "",
image: product ? product.image : ""
});
const handleChange = (e) => {
const value = e.target.name === 'price' ? parseFloat(e.target.value) : e.target.value;
setFormData({ ...formData, [e.target.name]: value });
};
return (
{product ? "Editar Produto" : "Novo Produto"}
);
}
function Store({ products, onAddProduct, onEditProduct, onDeleteProduct, onBack, showNotification }) {
const [isAdminMode, setIsAdminMode] = useState(false);
const [showLogin, setShowLogin] = useState(false);
const [passwordAttempt, setPasswordAttempt] = useState("");
const [editingProduct, setEditingProduct] = useState(null);
const [isAdding, setIsAdding] = useState(false);
const handleBuy = (product: Product) => {
const message = `Olá! Gostaria de comprar o produto:
*${product.id}*
*${product.name}*
Valor: R$ ${product.price.toFixed(2)}
Podemos combinar a entrega/retirada?`;
const url = `https://wa.me/${SALON_PHONE_NUMBER}?text=${encodeURIComponent(message)}`;
window.open(url, '_blank');
};
const handleAdminLogin = () => {
if (passwordAttempt.trim() === ADMIN_PASSWORD) {
setIsAdminMode(true);
setShowLogin(false);
setPasswordAttempt("");
showNotification("Acesso Admin concedido!", "success");
} else {
showNotification("Senha incorreta. Tente novamente.", "error");
}
};
const handleLogout = () => {
setIsAdminMode(false);
setIsAdding(false);
setEditingProduct(null);
setPasswordAttempt("");
};
if (isAdding || editingProduct) {
return (
{
if (editingProduct) {
onEditProduct(product);
setEditingProduct(null);
showNotification("Produto atualizado com sucesso!", "success");
} else {
onAddProduct(product);
setIsAdding(false);
showNotification("Produto adicionado com sucesso!", "success");
}
}}
onCancel={() => {
setEditingProduct(null);
setIsAdding(false);
}}
/>
);
}
return (
Gabriele Mira Boutique
{isAdminMode && (
)}
{products.map((product) => (

{ (e.target as HTMLImageElement).src = "https://placehold.co/600x400?text=Sem+Imagem"; }}
/>
{isAdminMode && (
)}
{product.name}
{product.description}
R$ {product.price.toFixed(2)}
))}
{showLogin ? (
setPasswordAttempt(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleAdminLogin()}
placeholder="Senha admin"
autoFocus
style={{ padding: "6px", borderRadius: "4px", border: "1px solid #ccc", outline: "none", width: "100px" }}
/>
) : !isAdminMode ? (
) : (
)}
);
}
function AIConsultant({ onBack }) {
const [messages, setMessages] = useState<{role: 'user' | 'model', text: string}[]>([
{ role: 'model', text: 'Olá! Sou a Gabi, sua assistente virtual. Tem dúvidas sobre qual corte combina com seu rosto ou como cuidar dos seus fios? Pergunte-me!' }
]);
const [input, setInput] = useState("");
const [loading, setLoading] = useState(false);
const scrollRef = useRef(null);
useEffect(() => {
if (scrollRef.current) {
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
}
}, [messages]);
const handleSend = async () => {
if (!input.trim()) return;
const userMsg = input;
setInput("");
setMessages(prev => [...prev, { role: 'user', text: userMsg }]);
setLoading(true);
try {
const ai = new GoogleGenAI({ apiKey: API_KEY });
// Correct usage: define model name in generateContent, pass systemInstruction in config.
const result = await ai.models.generateContent({
model: "gemini-2.5-flash",
contents: [{ role: 'user', parts: [{ text: userMsg }] }],
config: {
systemInstruction: "Você é a Gabi, uma especialista em cabelos, visagismo e estética do Studio Gabriele Mira. Responda de forma curta, elegante, amigável e incentive a pessoa a agendar um serviço no salão se for apropriado. Use emojis com moderação.",
}
});
// Correct usage: access .text property directly
const responseText = result.text || "";
setMessages(prev => [...prev, { role: 'model', text: responseText }]);
} catch (error) {
setMessages(prev => [...prev, { role: 'model', text: "Desculpe, tive um problema técnico momentâneo. Tente novamente." }]);
console.error(error);
} finally {
setLoading(false);
}
};
return (
Pergunte à Gabi
{messages.map((msg, idx) => (
{msg.text}
))}
{loading &&
Digitando...
}
setInput(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSend()}
placeholder="Ex: Qual corte dá volume?"
style={{ flex: 1, padding: "12px", borderRadius: "25px", border: "1px solid #ddd", outline: "none" }}
/>
);
}
function BookingForm({ service, onBack, showNotification }) {
const [formData, setFormData] = useState({
name: "",
phone: "",
date: "",
time: "",
professional: PROFESSIONALS[0]
});
const handleChange = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const handleWhatsAppRedirect = () => {
if (!formData.name || !formData.date || !formData.time) {
showNotification("Por favor, preencha todos os campos obrigatórios.", "error");
return;
}
// Validate Business Hours (08:00 - 18:00)
const [hours, minutes] = formData.time.split(':').map(Number);
if (hours < 8 || hours > 18 || (hours === 18 && minutes > 0)) {
showNotification("O horário de funcionamento é das 08:00 às 18:00. Por favor, escolha um horário dentro deste período.", "error");
return;
}
// Validate 10-minute intervals
if (minutes % 10 !== 0) {
showNotification("Por favor, selecione um horário em intervalos de 10 minutos (ex: 14:10, 14:20).", "error");
return;
}
const message = `Oi Gabi! Gostaria de agendar um horário.
*Serviço:* ${service.name} (${service.price})
*Cliente:* ${formData.name}
*Data:* ${formData.date}
*Horário:* ${formData.time}
*Profissional:* ${formData.professional}
Aguardo confirmação!`;
const url = `https://wa.me/${SALON_PHONE_NUMBER}?text=${encodeURIComponent(message)}`;
window.open(url, '_blank');
};
return (
Agendar Horário
{service.name} - {service.price}
);
}
function Footer() {
return (
);
}
// --- Main App Component ---
function App() {
const [currentView, setCurrentView] = useState("home"); // home, booking, ai-consultant, store
const [selectedService, setSelectedService] = useState(null);
const [products, setProducts] = useState(INITIAL_PRODUCTS);
const [notification, setNotification] = useState<{message: string, type: 'error' | 'success'} | null>(null);
const showNotification = (message: string, type: 'error' | 'success' = 'error') => {
setNotification({ message, type });
};
const handleSelectService = (service: Service) => {
setSelectedService(service);
setCurrentView("booking");
};
const handleAddProduct = (newProduct: Product) => {
setProducts([...products, newProduct]);
};
const handleEditProduct = (updatedProduct: Product) => {
setProducts(products.map(p => p.id === updatedProduct.id ? updatedProduct : p));
};
const handleDeleteProduct = (productId: string) => {
setProducts(products.filter(p => p.id !== productId));
};
const renderContent = () => {
if (currentView === "booking" && selectedService) {
return (
setCurrentView("home")}
showNotification={showNotification}
/>
);
}
if (currentView === "ai-consultant") {
return setCurrentView("home")} />;
}
if (currentView === "store") {
return (
setCurrentView("home")}
showNotification={showNotification}
/>
);
}
// Default to home (also handles 'booking' without selectedService fallback case implicitly)
return (
<>
{
const element = document.getElementById('services-section');
element?.scrollIntoView({ behavior: 'smooth' });
}} />
>
);
};
return (
{notification && (
setNotification(null)}
/>
)}
{renderContent()}
);
}
const root = createRoot(document.getElementById("root")!);
root.render();