feat: set up basic project structure and initial styling
This commit initializes the project with a basic Next.js setup, including: - Integration of `tailwindcss` and `tw-animate-css` for styling. - Definition of a comprehensive color palette using CSS variables for both light and dark themes, supporting a theming system. - Implementation of a custom `aurora` keyframe animation. - Configuration of global CSS styles for `border` and `body` elements. - Setup of `next.config.ts` with image optimization, performance enhancements, and security headers (X-Frame-Options, X-Content-Type-Options, Referrer-Policy). - Update of `package.json` to include new dependencies for UI components (`class-variance-authority`, `clsx`, `lucide-react`, `motion`, `tailwind-merge`, `tailwindcss-animate`) and development tools (`@biomejs/biome`, `@tailwindcss/postcss`, `tw-animate-css`). - Refinement of `app/layout.tsx` to include comprehensive metadata for SEO (title, description, keywords, authors, Open Graph, Twitter cards, robots), preconnect/dns-prefetch links for external resources, favicon, manifest, and mobile app meta tags. - Restructuring of `app/page.tsx` to use dynamic imports for various sections (Hero, About, Skills, Experience, Projects, Contact, WorkModalities) to improve performance, and integration of `Squares` component for background effects. This foundational commit prepares the application for further development by establishing a robust and performant base.
This commit is contained in:
1 parent
ca9e70a53e
commit
90cf3c73ad
37 files changed
+3898
-118
No files matched your search
@@ -0,0 +1,769 @@
|
||||
"use client";
|
||||
|
||||
import { useState, ChangeEvent, FormEvent, useEffect } from "react";
|
||||
import { usePortfolioData } from "@/lib/usePortfolioData";
|
||||
import { motion } from "motion/react";
|
||||
|
||||
interface ContactFormProps {
|
||||
formType: "simple" | "advanced";
|
||||
setFormType: (type: "simple" | "advanced") => void;
|
||||
}
|
||||
|
||||
export function ContactForm({ formType, setFormType }: ContactFormProps) {
|
||||
const { data } = usePortfolioData();
|
||||
const [contactMethod, setContactMethod] = useState<"whatsapp" | "email">(
|
||||
"whatsapp"
|
||||
);
|
||||
const [userName, setUserName] = useState<string>("");
|
||||
const [userEmail, setUserEmail] = useState<string>("");
|
||||
const [userPhone, setUserPhone] = useState<string>("");
|
||||
const [userCompany, setUserCompany] = useState<string>("");
|
||||
const [howFound, setHowFound] = useState<string>("");
|
||||
const [projectType, setProjectType] = useState<string>("");
|
||||
const [otherProjectType, setOtherProjectType] = useState<string>(""); // Novo estado para "Outro" tipo de projeto
|
||||
const [desiredDeadline, setDesiredDeadline] = useState<string>("");
|
||||
const [budgetValue, setBudgetValue] = useState<string>(""); // Novo estado para o valor do orçamento
|
||||
const [technicalComplexity, setTechnicalComplexity] = useState<string>("");
|
||||
const [hasInternalTeam, setHasInternalTeam] = useState<string>("");
|
||||
const [customMessage, setCustomMessage] = useState<string>("");
|
||||
const [isDecisor, setIsDecisor] = useState<string>("");
|
||||
const [projectApproved, setProjectApproved] = useState<string>("");
|
||||
const [urgency, setUrgency] = useState<string>("");
|
||||
const [preferredModality, setPreferredModality] = useState<string>("");
|
||||
const [isExpanded, setIsExpanded] = useState<boolean>(false); // Novo estado para controlar a expansão do formulário avançado
|
||||
const [nameError, setNameError] = useState<string | null>(null);
|
||||
const [emailError, setEmailError] = useState<string | null>(null);
|
||||
const [phoneError, setPhoneError] = useState<string | null>(null);
|
||||
const [otherProjectTypeError, setOtherProjectTypeError] = useState<
|
||||
string | null
|
||||
>(null); // Novo estado de erro
|
||||
const [honeypot, setHoneypot] = useState<string>(""); // Estado para o campo honeypot
|
||||
const [startTime, setStartTime] = useState<number>(0); // Estado para o tempo de início do formulário
|
||||
const [nameFocused, setNameFocused] = useState<boolean>(false);
|
||||
const [emailFocused, setEmailFocused] = useState<boolean>(false);
|
||||
const [phoneFocused, setPhoneFocused] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
setStartTime(Date.now()); // Registra o tempo de carregamento do componente
|
||||
}, []);
|
||||
|
||||
if (!data) {
|
||||
return null; // Ou um componente de carregamento/erro
|
||||
}
|
||||
|
||||
const getGreetingMessageWithUserName = () => {
|
||||
const hour = new Date().getHours();
|
||||
|
||||
const baseMessage = (name: string) => {
|
||||
if (hour >= 8 && hour < 12) {
|
||||
// Manhã
|
||||
return `Bom dia Evandro, meu nome é ${name}. Vi seu portfólio e estou interessado nos seus serviços. Podemos conversar?`;
|
||||
} else if (hour >= 13 && hour < 18) {
|
||||
// Tarde
|
||||
return `Boa tarde Evandro, meu nome é ${name}. Vi seu portfólio e estou interessado nos seus serviços. Podemos conversar?`;
|
||||
} else {
|
||||
// Noite
|
||||
return `Olá Evandro, meu nome é ${name}. Vi seu portfólio e estou interessado nos seus serviços. Podemos conversar quando estiver disponível?`;
|
||||
}
|
||||
};
|
||||
return baseMessage(userName);
|
||||
};
|
||||
|
||||
const generateAdvancedMessage = () => {
|
||||
if (customMessage) return customMessage;
|
||||
|
||||
let message = `Olá Evandro,\n\n`;
|
||||
message += `Meu nome é ${userName}`;
|
||||
if (userCompany) {
|
||||
message += ` e sou de ${userCompany}`;
|
||||
}
|
||||
if (howFound) {
|
||||
message += `. Conheci seu trabalho via ${howFound}`;
|
||||
}
|
||||
message += `.\n\n`;
|
||||
|
||||
message += `Estou entrando em contato para discutir um projeto de `;
|
||||
if (projectType === "outro" && otherProjectType) {
|
||||
message += `${otherProjectType}`;
|
||||
} else if (projectType) {
|
||||
message += `${projectType}`;
|
||||
} else {
|
||||
message += `[Tipo de Projeto Não Especificado]`;
|
||||
}
|
||||
message += `.\n`;
|
||||
|
||||
if (desiredDeadline) {
|
||||
message += `O prazo desejado é ${desiredDeadline}.\n`;
|
||||
}
|
||||
if (budgetValue) {
|
||||
message += `O orçamento disponível é ${budgetValue}.\n`;
|
||||
}
|
||||
if (technicalComplexity) {
|
||||
message += `A complexidade técnica estimada é ${technicalComplexity}.\n`;
|
||||
}
|
||||
if (hasInternalTeam) {
|
||||
message += `Já possuímos uma equipe técnica ${hasInternalTeam}.\n`;
|
||||
}
|
||||
|
||||
message += `\nMeus contatos:\n`;
|
||||
if (userEmail) {
|
||||
message += `Email: ${userEmail}\n`;
|
||||
}
|
||||
if (userPhone) {
|
||||
message += `Telefone: ${userPhone}\n`;
|
||||
}
|
||||
|
||||
message += `\nAguardamos seu retorno para discutir os próximos passos.`;
|
||||
|
||||
return message;
|
||||
};
|
||||
|
||||
const handleNameChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setUserName(e.target.value);
|
||||
if (e.target.value.trim() !== "") setNameError(null);
|
||||
};
|
||||
|
||||
const handleEmailChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setUserEmail(e.target.value);
|
||||
if (e.target.value.trim() !== "") setEmailError(null);
|
||||
};
|
||||
|
||||
const handlePhoneChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setUserPhone(e.target.value);
|
||||
if (e.target.value.trim() !== "") setPhoneError(null);
|
||||
};
|
||||
|
||||
const handleNameFocus = () => setNameFocused(true);
|
||||
const handleEmailFocus = () => setEmailFocused(true);
|
||||
const handlePhoneFocus = () => setPhoneFocused(true);
|
||||
|
||||
const handleCompanyChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setUserCompany(e.target.value);
|
||||
};
|
||||
|
||||
const handleHowFoundChange = (e: ChangeEvent<HTMLSelectElement>) => {
|
||||
setHowFound(e.target.value);
|
||||
};
|
||||
|
||||
const handleProjectTypeChange = (e: ChangeEvent<HTMLSelectElement>) => {
|
||||
setProjectType(e.target.value);
|
||||
};
|
||||
|
||||
const handleDesiredDeadlineChange = (e: ChangeEvent<HTMLSelectElement>) => {
|
||||
setDesiredDeadline(e.target.value);
|
||||
};
|
||||
|
||||
const handleBudgetValueChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setBudgetValue(e.target.value);
|
||||
};
|
||||
|
||||
const handleTechnicalComplexityChange = (
|
||||
e: ChangeEvent<HTMLSelectElement>
|
||||
) => {
|
||||
setTechnicalComplexity(e.target.value);
|
||||
};
|
||||
|
||||
const handleHasInternalTeamChange = (e: ChangeEvent<HTMLSelectElement>) => {
|
||||
setHasInternalTeam(e.target.value);
|
||||
};
|
||||
|
||||
const handleMessageChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setCustomMessage(e.target.value);
|
||||
};
|
||||
|
||||
const generateWhatsAppUrl = () => {
|
||||
const message = encodeURIComponent(
|
||||
formType === "advanced"
|
||||
? generateAdvancedMessage()
|
||||
: customMessage || getGreetingMessageWithUserName()
|
||||
);
|
||||
const phone = data.contact.whatsapp.replace(/\D/g, ""); // Remove caracteres não numéricos
|
||||
return `https://wa.me/${phone}?text=${message}`;
|
||||
};
|
||||
|
||||
const generateEmailUrl = () => {
|
||||
const subject = encodeURIComponent("Proposta de Projeto - Portfólio");
|
||||
const body = encodeURIComponent(
|
||||
formType === "advanced"
|
||||
? generateAdvancedMessage()
|
||||
: `Nome: ${userName}\nEmail: ${userEmail}\nTelefone: ${userPhone}\nEmpresa/Posição: ${userCompany}\nComo conheceu: ${howFound}\n\nTipo de Projeto: ${projectType}\nPrazo Desejado: ${desiredDeadline}\nOrçamento Disponível: ${budgetValue}\nComplexidade Técnica: ${technicalComplexity}\nTem equipe interna: ${hasInternalTeam}\n\nMensagem: ${
|
||||
customMessage || getGreetingMessageWithUserName()
|
||||
}`
|
||||
);
|
||||
return `mailto:${data.contact.email}?subject=${subject}&body=${body}`;
|
||||
};
|
||||
|
||||
const handleSubmit = (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
let hasError = false;
|
||||
if (userName.trim() === "") {
|
||||
setNameError("Por favor, insira seu nome.");
|
||||
hasError = true;
|
||||
} else {
|
||||
setNameError(null);
|
||||
}
|
||||
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(userEmail)) {
|
||||
setEmailError("Por favor, insira um email válido.");
|
||||
hasError = true;
|
||||
} else {
|
||||
setEmailError(null);
|
||||
}
|
||||
|
||||
if (userPhone.trim() === "") {
|
||||
setPhoneError("Por favor, insira seu telefone.");
|
||||
hasError = true;
|
||||
} else {
|
||||
setPhoneError(null);
|
||||
}
|
||||
|
||||
if (projectType === "outro" && otherProjectType.trim() === "") {
|
||||
setOtherProjectTypeError("Por favor, especifique o tipo de projeto.");
|
||||
hasError = true;
|
||||
} else {
|
||||
setOtherProjectTypeError(null);
|
||||
}
|
||||
|
||||
// Validação Honeypot
|
||||
if (honeypot) {
|
||||
console.log("Honeypot preenchido, provável bot.");
|
||||
return; // Ignora a submissão
|
||||
}
|
||||
|
||||
// Validação de tempo de preenchimento (mínimo de 3 segundos)
|
||||
const timeElapsed = Date.now() - startTime;
|
||||
if (timeElapsed < 3000) {
|
||||
console.log("Formulário preenchido muito rápido, provável bot.");
|
||||
return; // Ignora a submissão
|
||||
}
|
||||
|
||||
if (hasError) return;
|
||||
|
||||
// Verificação de interação mínima
|
||||
if (!nameFocused || !emailFocused || !phoneFocused) {
|
||||
console.log("Campos principais não foram focados, provável bot.");
|
||||
// Opcional: exibir uma mensagem de erro para o usuário
|
||||
return;
|
||||
}
|
||||
|
||||
if (contactMethod === "whatsapp") {
|
||||
window.open(generateWhatsAppUrl(), "_blank");
|
||||
} else {
|
||||
window.open(generateEmailUrl(), "_blank");
|
||||
}
|
||||
};
|
||||
|
||||
const getCtaMessage = () => {
|
||||
if (projectType === "consultoria") {
|
||||
return "Agendar Consultoria Gratuita";
|
||||
}
|
||||
if (budgetValue && parseFloat(budgetValue.replace(/\D/g, "")) < 2000) {
|
||||
return "Discutir Soluções de Baixo Custo";
|
||||
}
|
||||
if (projectType && desiredDeadline && budgetValue) {
|
||||
return "Receber Proposta Personalizada";
|
||||
}
|
||||
return "Enviar Formulário";
|
||||
};
|
||||
|
||||
const getEstimatedValue = () => {
|
||||
let value = 0;
|
||||
// Lógica de estimativa baseada nas respostas
|
||||
switch (projectType) {
|
||||
case "web-app":
|
||||
value += 10000;
|
||||
break;
|
||||
case "landing-page":
|
||||
value += 2000;
|
||||
break;
|
||||
case "mvp":
|
||||
value += 5000;
|
||||
break;
|
||||
case "iot-app":
|
||||
value += 15000;
|
||||
break;
|
||||
case "integracao":
|
||||
value += 7000;
|
||||
break;
|
||||
case "manutencao":
|
||||
value += 1000;
|
||||
break;
|
||||
case "consultoria":
|
||||
value += 500;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (desiredDeadline) {
|
||||
case "1-3-weeks":
|
||||
value *= 1.2; // Urgência aumenta o valor
|
||||
break;
|
||||
case "1-2-months":
|
||||
value *= 1.1;
|
||||
break;
|
||||
case "6-plus-months":
|
||||
value *= 0.9; // Prazo longo pode reduzir o valor
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (technicalComplexity) {
|
||||
case "alta":
|
||||
value *= 1.3;
|
||||
break;
|
||||
case "muito-alta":
|
||||
value *= 1.5;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (hasInternalTeam === "nao") {
|
||||
value *= 1.1; // Sem equipe interna pode indicar mais trabalho
|
||||
}
|
||||
|
||||
// Ajustar pelo valor do orçamento, se aplicável
|
||||
if (budgetValue) {
|
||||
const parsedBudget = parseFloat(budgetValue.replace(/\D/g, ""));
|
||||
if (!isNaN(parsedBudget)) {
|
||||
// Se o valor estimado for maior que o orçamento fornecido, ajusta para o orçamento
|
||||
value = Math.min(value, parsedBudget);
|
||||
}
|
||||
}
|
||||
|
||||
return value > 0 ? `Estimativa: R$ ${value.toFixed(2)}` : "Estimativa: N/A";
|
||||
};
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 30 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.4 }}
|
||||
viewport={{ once: true }}
|
||||
className="space-y-4"
|
||||
>
|
||||
<h3 className="text-2xl font-semibold mb-6 text-blue-primary">
|
||||
Preencha o Formulário
|
||||
</h3>
|
||||
|
||||
<div className="flex justify-center gap-4 mb-8">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setFormType("simple")}
|
||||
className={`py-2 px-4 rounded-lg font-semibold transition-all duration-300 ${
|
||||
formType === "simple"
|
||||
? "bg-blue-primary text-white shadow-lg shadow-blue-primary/25 border-2 border-blue-500"
|
||||
: "bg-gray-700 text-gray-300 opacity-50 cursor-pointer"
|
||||
}`}
|
||||
>
|
||||
Formulário Simples
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setFormType("advanced")}
|
||||
className={`py-2 px-4 rounded-lg font-semibold transition-all duration-300 ${
|
||||
formType === "advanced"
|
||||
? "bg-blue-primary text-white shadow-lg shadow-blue-primary/25 border-2 border-blue-500"
|
||||
: "bg-gray-700 text-gray-300 opacity-50 cursor-pointer"
|
||||
}`}
|
||||
>
|
||||
Formulário Avançado
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{formType === "simple" ? (
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
{/* Campo Honeypot */}
|
||||
<div style={{ display: "none" }}>
|
||||
<label htmlFor="honeypot">Não preencha este campo</label>
|
||||
<input
|
||||
type="text"
|
||||
id="honeypot"
|
||||
name="honeypot"
|
||||
value={honeypot}
|
||||
onChange={(e) => setHoneypot(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Formulário Simples: Apenas Nome */}
|
||||
<div>
|
||||
<label
|
||||
htmlFor="simple-user-name"
|
||||
className="block text-sm font-medium text-gray-300 mb-2"
|
||||
>
|
||||
Seu Nome:
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="simple-user-name"
|
||||
className={`w-full p-3 rounded-lg bg-white/5 border ${
|
||||
nameError ? "border-red-500" : "border-white/10"
|
||||
} text-white placeholder-gray-500 focus:ring-2 focus:ring-blue-primary focus:border-transparent`}
|
||||
placeholder="Seu nome"
|
||||
value={userName}
|
||||
onChange={handleNameChange}
|
||||
onFocus={handleNameFocus}
|
||||
required
|
||||
/>
|
||||
{nameError && (
|
||||
<p className="text-red-400 text-xs mt-1">{nameError}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Botão de Contato para o formulário simples */}
|
||||
<motion.div whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }}>
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full py-4 px-6 rounded-lg font-semibold text-lg transition-all duration-300 bg-green-600 hover:bg-green-700 text-white shadow-lg shadow-green-600/25"
|
||||
onClick={() => window.open(generateWhatsAppUrl(), "_blank")}
|
||||
>
|
||||
Enviar Mensagem via WhatsApp
|
||||
</button>
|
||||
</motion.div>
|
||||
</form>
|
||||
) : (
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
{/* Campo Honeypot */}
|
||||
<div style={{ display: "none" }}>
|
||||
<label htmlFor="honeypot">Não preencha este campo</label>
|
||||
<input
|
||||
type="text"
|
||||
id="honeypot"
|
||||
name="honeypot"
|
||||
value={honeypot}
|
||||
onChange={(e) => setHoneypot(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Dados Básicos */}
|
||||
<div>
|
||||
<h4 className="text-xl font-semibold mb-4 text-gray-200">
|
||||
Dados Básicos
|
||||
</h4>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label
|
||||
htmlFor="user-name"
|
||||
className="block text-sm font-medium text-gray-300 mb-2"
|
||||
>
|
||||
Nome Completo:
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="user-name"
|
||||
className={`w-full p-3 rounded-lg bg-white/5 border ${
|
||||
nameError ? "border-red-500" : "border-white/10"
|
||||
} text-white placeholder-gray-500 focus:ring-2 focus:ring-blue-primary focus:border-transparent`}
|
||||
placeholder="Seu nome completo"
|
||||
value={userName}
|
||||
onChange={handleNameChange}
|
||||
onFocus={handleNameFocus}
|
||||
required
|
||||
/>
|
||||
{nameError && (
|
||||
<p className="text-red-400 text-xs mt-1">{nameError}</p>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
htmlFor="user-email"
|
||||
className="block text-sm font-medium text-gray-300 mb-2"
|
||||
>
|
||||
Email Corporativo:
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="user-email"
|
||||
className={`w-full p-3 rounded-lg bg-white/5 border ${
|
||||
emailError ? "border-red-500" : "border-white/10"
|
||||
} text-white placeholder-gray-500 focus:ring-2 focus:ring-blue-primary focus:border-transparent`}
|
||||
placeholder="[email protected]"
|
||||
value={userEmail}
|
||||
onChange={handleEmailChange}
|
||||
onFocus={handleEmailFocus}
|
||||
required
|
||||
/>
|
||||
{emailError && (
|
||||
<p className="text-red-400 text-xs mt-1">{emailError}</p>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
htmlFor="user-phone"
|
||||
className="block text-sm font-medium text-gray-300 mb-2"
|
||||
>
|
||||
WhatsApp/Telefone:
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
id="user-phone"
|
||||
className={`w-full p-3 rounded-lg bg-white/5 border ${
|
||||
phoneError ? "border-red-500" : "border-white/10"
|
||||
} text-white placeholder-gray-500 focus:ring-2 focus:ring-blue-primary focus:border-transparent`}
|
||||
placeholder="(XX) XXXXX-XXXX"
|
||||
value={userPhone}
|
||||
onChange={handlePhoneChange}
|
||||
onFocus={handlePhoneFocus}
|
||||
required
|
||||
/>
|
||||
{phoneError && (
|
||||
<p className="text-red-400 text-xs mt-1">{phoneError}</p>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
htmlFor="user-company"
|
||||
className="block text-sm font-medium text-gray-300 mb-2"
|
||||
>
|
||||
Empresa/Posição:
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="user-company"
|
||||
className="w-full p-3 rounded-lg bg-white/5 border border-white/10 text-white placeholder-gray-500 focus:ring-2 focus:ring-blue-primary focus:border-transparent"
|
||||
placeholder="Nome da empresa, sua posição"
|
||||
value={userCompany}
|
||||
onChange={handleCompanyChange}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
htmlFor="how-found"
|
||||
className="block text-sm font-medium text-gray-300 mb-2"
|
||||
>
|
||||
Como conheceu meu trabalho?
|
||||
</label>
|
||||
<select
|
||||
id="how-found"
|
||||
className="w-full p-3 rounded-lg bg-white/5 border border-white/10 text-white focus:ring-2 focus:ring-blue-primary focus:border-transparent"
|
||||
value={howFound}
|
||||
onChange={handleHowFoundChange}
|
||||
>
|
||||
<option value="">Selecione uma opção</option>
|
||||
<option value="linkedin">LinkedIn</option>
|
||||
<option value="github">GitHub</option>
|
||||
<option value="indicacao">Indicação</option>
|
||||
<option value="pesquisa">Pesquisa Online</option>
|
||||
<option value="outro">Outro</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tile de Expandir/Recolher */}
|
||||
<div
|
||||
onClick={() => setIsExpanded(!isExpanded)}
|
||||
className="p-4 rounded-lg bg-white/5 border border-white/10 text-white cursor-pointer hover:bg-white/10 transition-colors duration-300 mb-6 flex justify-between items-center"
|
||||
>
|
||||
<h4 className="text-xl font-semibold text-gray-200">
|
||||
{isExpanded
|
||||
? "Detalhes do Projeto (Recolher)"
|
||||
: "Detalhes do Projeto (Expandir)"}
|
||||
</h4>
|
||||
<svg
|
||||
className={`w-6 h-6 transition-transform duration-300 ${
|
||||
isExpanded ? "rotate-180" : ""
|
||||
}`}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M19 9l-7 7-7-7"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`overflow-hidden transition-all duration-500 ease-in-out ${
|
||||
isExpanded ? "max-h-[1000px] opacity-100" : "max-h-0 opacity-0"
|
||||
} space-y-6`}
|
||||
>
|
||||
{/* Conteúdo do Formulário Avançado */}
|
||||
{/* Sobre o Projeto */}
|
||||
<div>
|
||||
<h4 className="text-xl font-semibold mb-4 text-gray-200">
|
||||
Sobre o Projeto
|
||||
</h4>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label
|
||||
htmlFor="project-type"
|
||||
className="block text-sm font-medium text-gray-300 mb-2"
|
||||
>
|
||||
Tipo de Projeto:
|
||||
</label>
|
||||
<select
|
||||
id="project-type"
|
||||
className="w-full p-3 rounded-lg bg-white/5 border border-white/10 text-white focus:ring-2 focus:ring-blue-primary focus:border-transparent"
|
||||
value={projectType}
|
||||
onChange={handleProjectTypeChange}
|
||||
>
|
||||
<option value="">Selecione o tipo</option>
|
||||
<option value="web-app">Aplicação Web Completa</option>
|
||||
<option value="landing-page">Landing Page</option>
|
||||
<option value="mvp">MVP (Produto Mínimo Viável)</option>
|
||||
<option value="iot-app">Aplicação IoT</option>
|
||||
<option value="integracao">Integração de Sistemas</option>
|
||||
<option value="manutencao">Manutenção/Ajustes</option>
|
||||
<option value="consultoria">Consultoria/Mentoria</option>
|
||||
<option value="outro">Outro</option>
|
||||
</select>
|
||||
</div>
|
||||
{projectType === "outro" && (
|
||||
<div>
|
||||
<label
|
||||
htmlFor="other-project-type"
|
||||
className="block text-sm font-medium text-gray-300 mb-2"
|
||||
>
|
||||
Qual outro tipo de projeto?
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="other-project-type"
|
||||
className={`w-full p-3 rounded-lg bg-white/5 border ${
|
||||
otherProjectTypeError
|
||||
? "border-red-500"
|
||||
: "border-white/10"
|
||||
} text-white placeholder-gray-500 focus:ring-2 focus:ring-blue-primary focus:border-transparent`}
|
||||
placeholder="Ex: Desenvolvimento de Plugin, Automação de Processos"
|
||||
value={otherProjectType}
|
||||
onChange={(e) => {
|
||||
setOtherProjectType(e.target.value);
|
||||
if (e.target.value.trim() !== "")
|
||||
setOtherProjectTypeError(null);
|
||||
}}
|
||||
required
|
||||
/>
|
||||
{otherProjectTypeError && (
|
||||
<p className="text-red-400 text-xs mt-1">
|
||||
{otherProjectTypeError}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<label
|
||||
htmlFor="desired-deadline"
|
||||
className="block text-sm font-medium text-gray-300 mb-2"
|
||||
>
|
||||
Prazo Desejado:
|
||||
</label>
|
||||
<select
|
||||
id="desired-deadline"
|
||||
className="w-full p-3 rounded-lg bg-white/5 border border-white/10 text-white focus:ring-2 focus:ring-blue-primary focus:border-transparent"
|
||||
value={desiredDeadline}
|
||||
onChange={handleDesiredDeadlineChange}
|
||||
>
|
||||
<option value="">Selecione o prazo</option>
|
||||
<option value="1-3-weeks">1-3 Semanas</option>
|
||||
<option value="1-2-months">1-2 Meses</option>
|
||||
<option value="2-4-months">2-4 Meses</option>
|
||||
<option value="4-6-months">4-6 Meses</option>
|
||||
<option value="6-plus-months">Mais de 6 Meses</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
htmlFor="budget-value"
|
||||
className="block text-sm font-medium text-gray-300 mb-2"
|
||||
>
|
||||
Orçamento Disponível (R$):
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="budget-value"
|
||||
className="w-full p-3 rounded-lg bg-white/5 border border-white/10 text-white placeholder-gray-500 focus:ring-2 focus:ring-blue-primary focus:border-transparent"
|
||||
placeholder="Ex: 5000, 10000, A Combinar"
|
||||
value={budgetValue}
|
||||
onChange={handleBudgetValueChange}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
htmlFor="technical-complexity"
|
||||
className="block text-sm font-medium text-gray-300 mb-2"
|
||||
>
|
||||
Complexidade Técnica Estimada:
|
||||
</label>
|
||||
<select
|
||||
id="technical-complexity"
|
||||
className="w-full p-3 rounded-lg bg-white/5 border border-white/10 text-white focus:ring-2 focus:ring-blue-primary focus:border-transparent"
|
||||
value={technicalComplexity}
|
||||
onChange={handleTechnicalComplexityChange}
|
||||
>
|
||||
<option value="">Selecione a complexidade</option>
|
||||
<option value="baixa">Baixa</option>
|
||||
<option value="media">Média</option>
|
||||
<option value="alta">Alta</option>
|
||||
<option value="muito-alta">Muito Alta</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
htmlFor="has-internal-team"
|
||||
className="block text-sm font-medium text-gray-300 mb-2"
|
||||
>
|
||||
Já tem equipe técnica interna?
|
||||
</label>
|
||||
<select
|
||||
id="has-internal-team"
|
||||
className="w-full p-3 rounded-lg bg-white/5 border border-white/10 text-white focus:ring-2 focus:ring-blue-primary focus:border-transparent"
|
||||
value={hasInternalTeam}
|
||||
onChange={handleHasInternalTeamChange}
|
||||
>
|
||||
<option value="">Selecione</option>
|
||||
<option value="sim">Sim</option>
|
||||
<option value="nao">Não</option>
|
||||
<option value="parcial">Parcialmente</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Custom Message Input */}
|
||||
<div className="mb-6">
|
||||
<label
|
||||
htmlFor="custom-message"
|
||||
className="block text-sm font-medium text-gray-300 mb-2"
|
||||
>
|
||||
Mensagem Adicional (Opcional):
|
||||
</label>
|
||||
<textarea
|
||||
id="custom-message"
|
||||
rows={4}
|
||||
className="w-full p-3 rounded-lg bg-white/5 border border-white/10 text-white placeholder-gray-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
placeholder="Descreva seu projeto ou suas necessidades aqui..."
|
||||
value={customMessage}
|
||||
onChange={handleMessageChange}
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Contact Button */}
|
||||
<motion.div whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }}>
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full py-4 px-6 rounded-lg font-semibold text-lg transition-all duration-300 bg-blue-primary hover:bg-blue-primary/90 text-white shadow-lg shadow-blue-primary/25 border-2 border-blue-500"
|
||||
>
|
||||
{getCtaMessage()}
|
||||
</button>
|
||||
</motion.div>
|
||||
{projectType && desiredDeadline && budgetValue && (
|
||||
<p className="text-center text-lg font-semibold text-blue-400 mt-4">
|
||||
{getEstimatedValue()}
|
||||
</p>
|
||||
)}
|
||||
</form>
|
||||
)}
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user