Files
Investidor-V2/resources/views/PortalInvestidor.jsx
T
nala 250ac35fd1
tests / ci (push) Canceled after 0s
first commit
2026-09-28 10:01:09 +02:00

928 lines
30 KiB
React
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useMemo, useState } from "react";
import {
Search,
Leaf,
Zap,
Hotel,
Truck,
Waves,
Banknote,
ArrowRight,
ArrowLeft,
MapPin,
CheckCircle2,
X,
} from "lucide-react";
/* ------------------------------------------------------------------ */
/* Dados de exemplo (substituir depois por chamadas à API Laravel) */
/* ------------------------------------------------------------------ */
const SECTORS = [
{
slug: "agronegocio",
name: "Agronegócio",
icon: Leaf,
description:
"Produção, agroprocessamento, irrigação e cadeias de valor agrícolas.",
},
{
slug: "energia",
name: "Energia e Mineração",
icon: Zap,
description:
"Energias renováveis, transição energética e mineração responsável.",
},
{
slug: "turismo",
name: "Turismo",
icon: Hotel,
description: "Hotelaria, turismo costeiro, cultural e ecológico.",
},
{
slug: "infraestruturas",
name: "Infraestruturas",
icon: Truck,
description: "Logística, transportes, parques industriais e energia.",
},
{
slug: "economia-azul",
name: "Economia Azul",
icon: Waves,
description: "Pesca, aquacultura, portos e logística marítima.",
},
{
slug: "mercado-financeiro",
name: "Mercado Financeiro",
icon: Banknote,
description: "Fintech, capital de risco e instrumentos financeiros.",
},
];
const PROVINCES = [
"Maputo Cidade",
"Maputo Província",
"Gaza",
"Inhambane",
"Sofala",
"Manica",
"Tete",
"Zambézia",
"Nampula",
"Cabo Delgado",
"Niassa",
];
const MATURITY_LEVELS = [
"Ideia identificada",
"Pré-estudo disponível",
"Estudo de viabilidade em curso",
"Projecto estruturado",
"Pronto para investimento",
"Em negociação ou implementação",
];
const OPPORTUNITIES = [
{
reference: "INV-MZ-AGR-001",
title: "Unidade de processamento agrícola em Nampula",
sector: "agronegocio",
province: "Nampula",
maturity: 3,
amount: 2500000,
financing: "Capital e parceria técnica",
impact: "120 empregos directos e substituição de importações.",
summary:
"Instalação de uma unidade de processamento para agregar valor à produção agrícola local, reduzir perdas pós-colheita e melhorar o acesso a mercados.",
featured: true,
},
{
reference: "INV-MZ-ENE-002",
title: "Mini-rede solar para polo produtivo",
sector: "energia",
province: "Zambézia",
maturity: 2,
amount: 1200000,
financing: "PPP ou financiamento misto",
impact: "Acesso a energia para 40 unidades produtivas.",
summary:
"Solução de energia renovável para reforçar o fornecimento eléctrico a uma zona produtiva com potencial agrícola e comercial.",
featured: true,
},
{
reference: "INV-MZ-TUR-003",
title: "Eco-lodge costeiro com integração comunitária",
sector: "turismo",
province: "Inhambane",
maturity: 1,
amount: 1800000,
financing: "Capital privado ou joint venture",
impact: "Emprego local e conservação da faixa costeira.",
summary:
"Desenvolvimento de uma unidade turística com participação comunitária, focada em sustentabilidade e experiências autênticas.",
featured: true,
},
{
reference: "INV-MZ-INF-004",
title: "Centro logístico provincial",
sector: "infraestruturas",
province: "Sofala",
maturity: 3,
amount: 8500000,
financing: "Concessão ou PPP",
impact: "Redução de custos logísticos regionais.",
summary:
"Infraestrutura de armazenamento e distribuição para apoiar a comercialização de produtos regionais e reduzir perdas na cadeia.",
featured: false,
},
{
reference: "INV-MZ-BLUE-005",
title: "Processamento de pescado e cadeia fria",
sector: "economia-azul",
province: "Zambézia",
maturity: 1,
amount: 900000,
financing: "Capital, crédito ou parceria operacional",
impact: "Redução de perdas pós-captura na pesca artesanal.",
summary:
"Reforço da capacidade de conservação, processamento e distribuição de pescado para pequenos operadores locais.",
featured: false,
},
{
reference: "INV-MZ-FIN-006",
title: "Plataforma de crédito para PME agrícolas",
sector: "mercado-financeiro",
province: "Maputo Cidade",
maturity: 2,
amount: 3000000,
financing: "Fundo de investimento ou parceiro financeiro",
impact: "Acesso a financiamento para 300+ pequenas empresas.",
summary:
"Estrutura de financiamento dedicada a pequenas e médias empresas do sector agrícola com dificuldade de acesso a crédito bancário.",
featured: false,
},
];
const money = (amount) =>
new Intl.NumberFormat("pt-MZ", {
style: "currency",
currency: "USD",
maximumFractionDigits: 0,
}).format(amount);
const sectorOf = (slug) => SECTORS.find((s) => s.slug === slug);
/* ------------------------------------------------------------------ */
/* Componentes */
/* ------------------------------------------------------------------ */
function TopBar({ onNavigate, requestCount }) {
return (
<header className="sticky top-0 z-30 border-b border-slate-200 bg-white/95 backdrop-blur">
<div className="mx-auto flex max-w-6xl items-center justify-between px-5 py-4">
<button
onClick={() => onNavigate("home")}
className="flex items-baseline gap-1 font-black tracking-tight text-slate-900"
>
<span className="text-lg">INVESTE</span>
<span className="text-lg text-emerald-700">Moçambique</span>
</button>
<nav className="hidden gap-8 text-sm font-semibold text-slate-600 md:flex">
<button
onClick={() => onNavigate("home")}
className="hover:text-slate-900"
>
Início
</button>
<button
onClick={() => onNavigate("opportunities")}
className="hover:text-slate-900"
>
Oportunidades
</button>
<button
onClick={() => onNavigate("interest")}
className="hover:text-slate-900"
>
Manifestar interesse
</button>
</nav>
<div className="flex items-center gap-3 text-xs text-slate-500">
<span className="hidden sm:inline">
{requestCount} pedido{requestCount === 1 ? "" : "s"} recebido
{requestCount === 1 ? "" : "s"}
</span>
<button
onClick={() => onNavigate("interest")}
className="rounded-lg bg-slate-900 px-4 py-2 text-sm font-bold text-white hover:bg-slate-700"
>
Manifestar interesse
</button>
</div>
</div>
</header>
);
}
function Hero({ query, setQuery, onSearch, onNavigate }) {
return (
<section className="border-b border-slate-200 bg-[#0B2038] text-white">
<div className="mx-auto grid max-w-6xl gap-10 px-5 py-16 md:grid-cols-[1.2fr,1fr] md:py-24">
<div>
<p className="text-xs font-bold uppercase tracking-[0.2em] text-emerald-300">
Portal do Investidor
</p>
<h1 className="mt-4 max-w-xl text-4xl font-black leading-[1.05] tracking-tight md:text-5xl">
Oportunidades reais de investimento em Moçambique,
<span className="text-amber-400"> organizadas por sector.</span>
</h1>
<p className="mt-5 max-w-lg text-slate-300">
Consulte projectos por província e estágio de maturidade,
compreenda os requisitos legais e fiscais, e manifeste
interesse directamente junto da entidade promotora.
</p>
<div className="mt-8 flex flex-wrap gap-3">
<button
onClick={() => onNavigate("opportunities")}
className="inline-flex items-center gap-2 rounded-lg bg-amber-400 px-5 py-3 font-bold text-[#0B2038] hover:bg-amber-300"
>
Explorar oportunidades <ArrowRight size={16} />
</button>
<button
onClick={() => onNavigate("interest")}
className="rounded-lg border border-white/30 px-5 py-3 font-semibold text-white hover:bg-white/10"
>
Manifestar interesse
</button>
</div>
</div>
<form
onSubmit={onSearch}
className="h-fit rounded-2xl border border-white/10 bg-white/10 p-5"
>
<label className="mb-2 block text-sm font-semibold text-slate-200">
Pesquisa rápida
</label>
<div className="flex gap-2">
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Ex.: agronegócio, Nampula, energia..."
className="w-full rounded-lg px-3 py-2.5 text-sm text-slate-900 outline-none ring-2 ring-transparent focus:ring-amber-400"
/>
<button
type="submit"
className="rounded-lg bg-amber-400 px-4 text-[#0B2038]"
>
<Search size={18} />
</button>
</div>
<div className="mt-4 flex flex-wrap gap-3 border-t border-white/10 pt-4 text-center text-xs text-slate-300">
<div>
<p className="text-xl font-black text-white">
{OPPORTUNITIES.length}
</p>
oportunidades
</div>
<div className="ml-auto">
<p className="text-xl font-black text-white">
{SECTORS.length}
</p>
sectores
</div>
<div className="ml-auto">
<p className="text-xl font-black text-white">
{PROVINCES.length}
</p>
províncias
</div>
</div>
</form>
</div>
</section>
);
}
function SectorGrid({ onSelectSector }) {
return (
<section className="mx-auto max-w-6xl px-5 py-16">
<h2 className="text-2xl font-black text-slate-900">
Sectores estratégicos
</h2>
<p className="mt-2 max-w-2xl text-slate-600">
Explore as áreas prioritárias do programa de investimento nacional.
</p>
<div className="mt-8 grid gap-5 sm:grid-cols-2 lg:grid-cols-3">
{SECTORS.map((sector) => {
const Icon = sector.icon;
const count = OPPORTUNITIES.filter(
(o) => o.sector === sector.slug
).length;
return (
<button
key={sector.slug}
onClick={() => onSelectSector(sector.slug)}
className="group rounded-2xl border border-slate-200 bg-white p-6 text-left shadow-sm transition hover:-translate-y-0.5 hover:border-emerald-300 hover:shadow-md"
>
<div className="flex h-11 w-11 items-center justify-center rounded-xl bg-emerald-50 text-emerald-700">
<Icon size={22} />
</div>
<h3 className="mt-4 font-bold text-slate-900">
{sector.name}
</h3>
<p className="mt-2 text-sm leading-relaxed text-slate-600">
{sector.description}
</p>
<p className="mt-4 text-sm font-semibold text-emerald-700 group-hover:underline">
{count} oportunidade{count === 1 ? "" : "s"} →
</p>
</button>
);
})}
</div>
</section>
);
}
function OpportunityCard({ opportunity, onView, onInterest }) {
const sector = sectorOf(opportunity.sector);
return (
<article className="flex h-full flex-col rounded-2xl border border-slate-200 bg-white p-6 shadow-sm">
<div className="flex flex-wrap gap-2">
{opportunity.featured && (
<span className="rounded-full bg-amber-100 px-3 py-1 text-xs font-bold text-amber-800">
Destaque
</span>
)}
<span className="rounded-full bg-emerald-50 px-3 py-1 text-xs font-semibold text-emerald-700">
{sector.name}
</span>
</div>
<p className="mt-3 text-xs font-semibold uppercase tracking-wide text-slate-400">
{opportunity.reference}
</p>
<h3 className="mt-1 text-lg font-bold text-slate-900">
{opportunity.title}
</h3>
<p className="mt-2 flex items-center gap-1 text-sm text-slate-500">
<MapPin size={14} /> {opportunity.province}
</p>
<dl className="mt-4 space-y-2 border-t border-slate-100 pt-4 text-sm">
<div className="flex justify-between">
<dt className="text-slate-500">Investimento</dt>
<dd className="font-semibold text-slate-800">
{money(opportunity.amount)}
</dd>
</div>
<div className="flex justify-between">
<dt className="text-slate-500">Maturidade</dt>
<dd className="font-semibold text-slate-800">
{MATURITY_LEVELS[opportunity.maturity]}
</dd>
</div>
</dl>
<div className="mt-6 flex gap-2">
<button
onClick={() => onView(opportunity)}
className="flex-1 rounded-lg bg-slate-900 py-2.5 text-sm font-bold text-white hover:bg-slate-700"
>
Ver ficha
</button>
<button
onClick={() => onInterest(opportunity)}
className="flex-1 rounded-lg border border-slate-300 py-2.5 text-sm font-bold text-slate-700 hover:bg-slate-50"
>
Manifestar interesse
</button>
</div>
</article>
);
}
function OpportunitiesView({ filters, setFilters, onView, onInterest }) {
const results = useMemo(() => {
return OPPORTUNITIES.filter((o) => {
const matchesQuery = filters.query
? (o.title + o.province + sectorOf(o.sector).name)
.toLowerCase()
.includes(filters.query.toLowerCase())
: true;
const matchesSector = filters.sector ? o.sector === filters.sector : true;
const matchesProvince = filters.province
? o.province === filters.province
: true;
const matchesMaturity =
filters.maturity !== "" ? o.maturity === Number(filters.maturity) : true;
return matchesQuery && matchesSector && matchesProvince && matchesMaturity;
});
}, [filters]);
return (
<section className="mx-auto max-w-6xl px-5 py-12">
<h1 className="text-3xl font-black text-slate-900">
Oportunidades de investimento
</h1>
<p className="mt-2 text-slate-600">
{results.length} oportunidade{results.length === 1 ? "" : "s"}{" "}
encontrada{results.length === 1 ? "" : "s"}.
</p>
<div className="mt-6 grid gap-3 rounded-2xl border border-slate-200 bg-white p-4 sm:grid-cols-2 lg:grid-cols-4">
<input
value={filters.query}
onChange={(e) => setFilters({ ...filters, query: e.target.value })}
placeholder="Pesquisar..."
className="rounded-lg border border-slate-200 px-3 py-2 text-sm"
/>
<select
value={filters.sector}
onChange={(e) => setFilters({ ...filters, sector: e.target.value })}
className="rounded-lg border border-slate-200 px-3 py-2 text-sm"
>
<option value="">Todos os sectores</option>
{SECTORS.map((s) => (
<option key={s.slug} value={s.slug}>
{s.name}
</option>
))}
</select>
<select
value={filters.province}
onChange={(e) =>
setFilters({ ...filters, province: e.target.value })
}
className="rounded-lg border border-slate-200 px-3 py-2 text-sm"
>
<option value="">Todas as províncias</option>
{PROVINCES.map((p) => (
<option key={p} value={p}>
{p}
</option>
))}
</select>
<select
value={filters.maturity}
onChange={(e) =>
setFilters({ ...filters, maturity: e.target.value })
}
className="rounded-lg border border-slate-200 px-3 py-2 text-sm"
>
<option value="">Toda a maturidade</option>
{MATURITY_LEVELS.map((label, index) => (
<option key={label} value={index}>
{label}
</option>
))}
</select>
</div>
{results.length === 0 ? (
<div className="mt-10 rounded-2xl border border-dashed border-slate-300 bg-slate-50 p-10 text-center text-slate-600">
Nenhuma oportunidade corresponde aos filtros seleccionados.
</div>
) : (
<div className="mt-8 grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{results.map((o) => (
<OpportunityCard
key={o.reference}
opportunity={o}
onView={onView}
onInterest={onInterest}
/>
))}
</div>
)}
</section>
);
}
function OpportunityDetail({ opportunity, onBack, onInterest }) {
const sector = sectorOf(opportunity.sector);
return (
<section className="mx-auto max-w-3xl px-5 py-12">
<button
onClick={onBack}
className="mb-6 inline-flex items-center gap-2 text-sm font-semibold text-slate-600 hover:text-slate-900"
>
<ArrowLeft size={16} /> Voltar às oportunidades
</button>
<p className="text-xs font-semibold uppercase tracking-wide text-slate-400">
{opportunity.reference}
</p>
<h1 className="mt-1 text-3xl font-black text-slate-900">
{opportunity.title}
</h1>
<p className="mt-2 flex items-center gap-2 text-sm text-slate-500">
<MapPin size={14} /> {opportunity.province} · {sector.name}
</p>
<p className="mt-6 leading-relaxed text-slate-700">
{opportunity.summary}
</p>
<dl className="mt-8 grid gap-4 rounded-2xl border border-slate-200 bg-white p-6 sm:grid-cols-2">
<div>
<dt className="text-xs uppercase text-slate-400">Investimento estimado</dt>
<dd className="mt-1 font-bold text-slate-900">
{money(opportunity.amount)}
</dd>
</div>
<div>
<dt className="text-xs uppercase text-slate-400">Maturidade</dt>
<dd className="mt-1 font-bold text-slate-900">
{MATURITY_LEVELS[opportunity.maturity]}
</dd>
</div>
<div>
<dt className="text-xs uppercase text-slate-400">Financiamento pretendido</dt>
<dd className="mt-1 font-bold text-slate-900">
{opportunity.financing}
</dd>
</div>
<div>
<dt className="text-xs uppercase text-slate-400">Impacto esperado</dt>
<dd className="mt-1 font-bold text-slate-900">{opportunity.impact}</dd>
</div>
</dl>
<div className="mt-6 rounded-xl bg-amber-50 p-4 text-sm text-amber-900">
A informação legal, fiscal e financeira apresentada é orientadora e
deve ser confirmada junto das entidades competentes antes da tomada
de decisão.
</div>
<button
onClick={() => onInterest(opportunity)}
className="mt-8 rounded-lg bg-slate-900 px-6 py-3 font-bold text-white hover:bg-slate-700"
>
Manifestar interesse nesta oportunidade
</button>
</section>
);
}
function InterestForm({ preselected, onSubmitted }) {
const [form, setForm] = useState({
name: "",
email: "",
phone: "",
investorType: "",
country: "Moçambique",
sectorInterest: preselected ? sectorOf(preselected.sector).name : "",
opportunityReference: preselected ? preselected.reference : "",
investmentRange: "",
message: "",
privacyConsent: false,
});
const [errors, setErrors] = useState({});
const [result, setResult] = useState(null);
function update(field, value) {
setForm((prev) => ({ ...prev, [field]: value }));
}
function validate() {
const next = {};
if (form.name.trim().length < 3) next.name = "Indique o nome completo.";
if (!/^\S+@\S+\.\S+$/.test(form.email)) next.email = "Indique um email válido.";
if (!form.investorType) next.investorType = "Seleccione o tipo de investidor.";
if (!form.sectorInterest.trim())
next.sectorInterest = "Indique o sector de interesse.";
if (form.message.trim().length < 20)
next.message = "A mensagem deve ter pelo menos 20 caracteres.";
if (!form.privacyConsent)
next.privacyConsent = "É necessário aceitar a política de privacidade.";
setErrors(next);
return Object.keys(next).length === 0;
}
function handleSubmit(event) {
event.preventDefault();
if (!validate()) return;
const reference = `IR-${new Date().getFullYear()}-${String(
Math.floor(Math.random() * 900000) + 100000
)}`;
setResult({ reference });
onSubmitted?.();
}
if (result) {
return (
<section className="mx-auto max-w-xl px-5 py-16 text-center">
<CheckCircle2 className="mx-auto text-emerald-600" size={48} />
<h1 className="mt-4 text-2xl font-black text-slate-900">
Manifestação de interesse registada
</h1>
<p className="mt-2 text-slate-600">
A sua referência é <strong>{result.reference}</strong>. A equipa
do portal irá analisar o pedido e poderá solicitar informação
adicional.
</p>
</section>
);
}
return (
<section className="mx-auto max-w-xl px-5 py-12">
<h1 className="text-3xl font-black text-slate-900">
Manifestar interesse
</h1>
<p className="mt-2 text-slate-600">
Preencha os dados abaixo para que a equipa do portal possa analisar
o seu interesse e entrar em contacto.
</p>
{preselected && (
<div className="mt-5 rounded-xl border border-emerald-200 bg-emerald-50 p-4 text-sm text-emerald-900">
Oportunidade seleccionada: <strong>{preselected.title}</strong> (
{preselected.reference})
</div>
)}
<form onSubmit={handleSubmit} className="mt-6 space-y-5">
<Field label="Nome completo" error={errors.name} required>
<input
className="input"
value={form.name}
onChange={(e) => update("name", e.target.value)}
/>
</Field>
<Field label="Email" error={errors.email} required>
<input
type="email"
className="input"
value={form.email}
onChange={(e) => update("email", e.target.value)}
/>
</Field>
<Field label="Telefone" error={errors.phone}>
<input
className="input"
value={form.phone}
onChange={(e) => update("phone", e.target.value)}
/>
</Field>
<Field label="Tipo de investidor" error={errors.investorType} required>
<select
className="input"
value={form.investorType}
onChange={(e) => update("investorType", e.target.value)}
>
<option value="">Seleccione</option>
<option value="individual">Investidor individual</option>
<option value="empresa">Empresa</option>
<option value="fundo">Fundo de investimento</option>
<option value="entidade_publica">Entidade pública</option>
<option value="outro">Outro</option>
</select>
</Field>
<Field label="País" error={errors.country}>
<input
className="input"
value={form.country}
onChange={(e) => update("country", e.target.value)}
/>
</Field>
<Field
label="Sector de interesse"
error={errors.sectorInterest}
required
>
<select
className="input"
value={form.sectorInterest}
onChange={(e) => update("sectorInterest", e.target.value)}
>
<option value="">Seleccione</option>
{SECTORS.map((s) => (
<option key={s.slug} value={s.name}>
{s.name}
</option>
))}
</select>
</Field>
<Field label="Faixa de investimento" error={errors.investmentRange}>
<select
className="input"
value={form.investmentRange}
onChange={(e) => update("investmentRange", e.target.value)}
>
<option value="">Seleccione</option>
<option value="ate-100000">Até USD 100.000</option>
<option value="100000-500000">USD 100.000 – 500.000</option>
<option value="500000-1000000">USD 500.000 – 1.000.000</option>
<option value="acima-1000000">Acima de USD 1.000.000</option>
</select>
</Field>
<Field label="Mensagem" error={errors.message} required>
<textarea
rows={4}
className="input"
value={form.message}
onChange={(e) => update("message", e.target.value)}
placeholder="Descreva o seu interesse ou a informação pretendida."
/>
</Field>
<label className="flex items-start gap-3 text-sm text-slate-600">
<input
type="checkbox"
checked={form.privacyConsent}
onChange={(e) => update("privacyConsent", e.target.checked)}
className="mt-1"
/>
Aceito que os meus dados sejam usados para efeitos de contacto e
análise da manifestação de interesse.
</label>
{errors.privacyConsent && (
<p className="text-sm text-red-600">{errors.privacyConsent}</p>
)}
<button
type="submit"
className="w-full rounded-lg bg-slate-900 py-3 font-bold text-white hover:bg-slate-700"
>
Enviar manifestação de interesse
</button>
</form>
</section>
);
}
function Field({ label, required, error, children }) {
return (
<div>
<label className="mb-1 block text-sm font-semibold text-slate-700">
{label} {required && <span className="text-red-600">*</span>}
</label>
{children}
{error && <p className="mt-1 text-sm text-red-600">{error}</p>}
</div>
);
}
function Footer() {
return (
<footer className="border-t border-slate-200 bg-slate-950 py-10 text-slate-400">
<div className="mx-auto max-w-6xl px-5 text-sm">
<p className="font-bold text-white">INVESTE Moçambique</p>
<p className="mt-2 max-w-md">
Protótipo do Portal do Investidor — dados de demonstração,
preparado para ligação futura à API real (Laravel + React).
</p>
</div>
</footer>
);
}
/* ------------------------------------------------------------------ */
/* App */
/* ------------------------------------------------------------------ */
export default function PortalInvestidorApp() {
const [view, setView] = useState("home");
const [selected, setSelected] = useState(null);
const [requestCount, setRequestCount] = useState(0);
const [heroQuery, setHeroQuery] = useState("");
const [filters, setFilters] = useState({
query: "",
sector: "",
province: "",
maturity: "",
});
function goToOpportunities() {
setView("opportunities");
window.scrollTo({ top: 0, behavior: "smooth" });
}
function handleHeroSearch(event) {
event.preventDefault();
setFilters((f) => ({ ...f, query: heroQuery }));
goToOpportunities();
}
function handleSelectSector(slug) {
setFilters((f) => ({ ...f, sector: slug, query: "" }));
goToOpportunities();
}
function handleView(opportunity) {
setSelected(opportunity);
setView("detail");
window.scrollTo({ top: 0, behavior: "smooth" });
}
function handleInterest(opportunity) {
setSelected(opportunity || null);
setView("interest");
window.scrollTo({ top: 0, behavior: "smooth" });
}
return (
<div className="min-h-screen bg-slate-50 font-sans text-slate-900">
<TopBar onNavigate={setView} requestCount={requestCount} />
{view === "home" && (
<>
<Hero
query={heroQuery}
setQuery={setHeroQuery}
onSearch={handleHeroSearch}
onNavigate={setView}
/>
<SectorGrid onSelectSector={handleSelectSector} />
<section className="border-t border-slate-200 bg-white py-16">
<div className="mx-auto max-w-6xl px-5">
<div className="mb-8 flex items-end justify-between">
<h2 className="text-2xl font-black text-slate-900">
Oportunidades em destaque
</h2>
<button
onClick={goToOpportunities}
className="text-sm font-bold text-emerald-700 hover:underline"
>
Ver todas →
</button>
</div>
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{OPPORTUNITIES.filter((o) => o.featured).map((o) => (
<OpportunityCard
key={o.reference}
opportunity={o}
onView={handleView}
onInterest={handleInterest}
/>
))}
</div>
</div>
</section>
</>
)}
{view === "opportunities" && (
<OpportunitiesView
filters={filters}
setFilters={setFilters}
onView={handleView}
onInterest={handleInterest}
/>
)}
{view === "detail" && selected && (
<OpportunityDetail
opportunity={selected}
onBack={() => setView("opportunities")}
onInterest={handleInterest}
/>
)}
{view === "interest" && (
<InterestForm
preselected={selected}
onSubmitted={() => setRequestCount((c) => c + 1)}
/>
)}
<Footer />
<style>{`
.input {
width: 100%;
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
padding: 0.6rem 0.75rem;
font-size: 0.9rem;
}
.input:focus {
outline: none;
box-shadow: 0 0 0 2px #10b981;
}
`}</style>
</div>
);
}