Lighten the in-app workspace and put Clientes right after Dashboard.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
c1addaaf8e
commit
7e929a5b6a
@ -64,7 +64,7 @@ export default function ArquivosPage() {
|
||||
return (
|
||||
<section>
|
||||
<h1 className="font-display text-4xl font-extrabold uppercase">Anexos da operação</h1>
|
||||
{error && !open ? <p className="mt-4 text-sm text-red-300">{error}</p> : null}
|
||||
{error && !open ? <p className="mt-4 text-sm text-red-600">{error}</p> : null}
|
||||
|
||||
<RegistryTable
|
||||
countLabel={`${rows.length} arquivo${rows.length === 1 ? "" : "s"}`}
|
||||
@ -78,11 +78,11 @@ export default function ArquivosPage() {
|
||||
}
|
||||
>
|
||||
{rows.map((row) => (
|
||||
<tr key={row.id} className="border-t border-white/10">
|
||||
<tr key={row.id} className="border-t border-[#E4EBE6]">
|
||||
<td className="px-5 py-3 font-medium">{row.name}</td>
|
||||
<td className="px-5 py-3 text-[#E8F4E5]/70">{row.type}</td>
|
||||
<td className="px-5 py-3 text-[#E8F4E5]/70">{formatSize(row.sizeBytes)}</td>
|
||||
<td className="px-5 py-3 text-[#E8F4E5]/70">{new Date(row.createdAt).toLocaleString("pt-BR")}</td>
|
||||
<td className="px-5 py-3 text-[#66756C]">{row.type}</td>
|
||||
<td className="px-5 py-3 text-[#66756C]">{formatSize(row.sizeBytes)}</td>
|
||||
<td className="px-5 py-3 text-[#66756C]">{new Date(row.createdAt).toLocaleString("pt-BR")}</td>
|
||||
<td className="px-5 py-3 text-right">
|
||||
<a href={`/api/files/${row.id}/download`} className="btn-ghost cursor-pointer px-3 py-1.5 text-[11px]">
|
||||
Baixar
|
||||
@ -96,7 +96,7 @@ export default function ArquivosPage() {
|
||||
<Modal onClose={closeModal}>
|
||||
<form className="flex flex-col gap-3" onSubmit={onSubmit}>
|
||||
<h2 className="font-display text-2xl font-bold uppercase">Anexar arquivo</h2>
|
||||
{error ? <p className="text-sm text-red-300">{error}</p> : null}
|
||||
{error ? <p className="text-sm text-red-600">{error}</p> : null}
|
||||
<input
|
||||
className="text-sm file:mr-3 file:rounded-lg file:border-0 file:bg-[#82C85C] file:px-3 file:py-2 file:text-xs file:font-semibold file:text-black"
|
||||
type="file"
|
||||
|
||||
@ -108,7 +108,7 @@ export default function ClientesPage() {
|
||||
return (
|
||||
<section>
|
||||
<h1 className="font-display text-4xl font-extrabold uppercase">Quem opera com você</h1>
|
||||
{error && !open ? <p className="mt-4 text-sm text-red-300">{error}</p> : null}
|
||||
{error && !open ? <p className="mt-4 text-sm text-red-600">{error}</p> : null}
|
||||
|
||||
<RegistryTable
|
||||
countLabel={`${rows.length} cliente${rows.length === 1 ? "" : "s"}`}
|
||||
@ -124,17 +124,17 @@ export default function ClientesPage() {
|
||||
}
|
||||
>
|
||||
{rows.map((row) => (
|
||||
<tr key={row.id} className="border-t border-white/10">
|
||||
<tr key={row.id} className="border-t border-[#E4EBE6]">
|
||||
<td className="px-5 py-3 font-medium">{row.name}</td>
|
||||
<td className="px-5 py-3 text-[#E8F4E5]/70">{row.document || "-"}</td>
|
||||
<td className="px-5 py-3 text-[#E8F4E5]/70">{[row.city, row.state].filter(Boolean).join(" / ") || "-"}</td>
|
||||
<td className="px-5 py-3 text-[#E8F4E5]/70">{row.email || row.phone || "-"}</td>
|
||||
<td className="px-5 py-3 text-[#66756C]">{row.document || "-"}</td>
|
||||
<td className="px-5 py-3 text-[#66756C]">{[row.city, row.state].filter(Boolean).join(" / ") || "-"}</td>
|
||||
<td className="px-5 py-3 text-[#66756C]">{row.email || row.phone || "-"}</td>
|
||||
{canWrite ? (
|
||||
<td className="px-5 py-3">
|
||||
<button className="text-[#82C85C]" type="button" onClick={() => openEdit(row)}>
|
||||
Editar
|
||||
</button>
|
||||
<button className="ml-3 text-[#E8F4E5]/50" type="button" onClick={() => void onDelete(row.id)}>
|
||||
<button className="ml-3 text-[#7A877F]" type="button" onClick={() => void onDelete(row.id)}>
|
||||
Remover
|
||||
</button>
|
||||
</td>
|
||||
@ -147,7 +147,7 @@ export default function ClientesPage() {
|
||||
<Modal onClose={closeModal}>
|
||||
<form className="grid gap-3" onSubmit={onSubmit}>
|
||||
<h2 className="font-display text-2xl font-bold uppercase">{editing ? "Editar cliente" : "Novo cliente"}</h2>
|
||||
{error ? <p className="text-sm text-red-300">{error}</p> : null}
|
||||
{error ? <p className="text-sm text-red-600">{error}</p> : null}
|
||||
<input className={fieldClass} placeholder="Nome" value={form.name} onChange={(event) => setForm({ ...form, name: event.target.value })} required />
|
||||
<input className={fieldClass} placeholder="CNPJ" value={form.document} onChange={(event) => setForm({ ...form, document: event.target.value })} />
|
||||
<input className={fieldClass} placeholder="E-mail" type="email" value={form.email} onChange={(event) => setForm({ ...form, email: event.target.value })} />
|
||||
|
||||
@ -86,13 +86,13 @@ export default function EquipePage() {
|
||||
}
|
||||
|
||||
if (me.role === "OPERATOR") {
|
||||
return <p className="text-[#E8F4E5]/60">Seu perfil de Operador não gerencia a equipe.</p>;
|
||||
return <p className="text-[#66756C]">Seu perfil de Operador não gerencia a equipe.</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<section>
|
||||
<h1 className="font-display text-4xl font-extrabold uppercase">Pessoas da operação</h1>
|
||||
{error && !open ? <p className="mt-4 text-sm text-red-300">{error}</p> : null}
|
||||
{error && !open ? <p className="mt-4 text-sm text-red-600">{error}</p> : null}
|
||||
|
||||
<RegistryTable
|
||||
countLabel={`${members.length} pessoa${members.length === 1 ? "" : "s"}`}
|
||||
@ -108,13 +108,13 @@ export default function EquipePage() {
|
||||
}
|
||||
>
|
||||
{members.map((member) => (
|
||||
<tr key={member.id} className="border-t border-white/10">
|
||||
<tr key={member.id} className="border-t border-[#E4EBE6]">
|
||||
<td className="px-5 py-3 font-medium">{member.name}</td>
|
||||
<td className="px-5 py-3 text-[#E8F4E5]/70">{member.email}</td>
|
||||
<td className="px-5 py-3 text-[#66756C]">{member.email}</td>
|
||||
<td className="px-5 py-3">
|
||||
{canManage ? (
|
||||
<select
|
||||
className="rounded-full border border-white/10 bg-black px-3 py-1.5 text-sm"
|
||||
className="rounded-full border border-[#E4EBE6] bg-white px-3 py-1.5 text-sm"
|
||||
value={member.role}
|
||||
onChange={(event) => void onRole(member.id, event.target.value as Member["role"])}
|
||||
>
|
||||
@ -128,7 +128,7 @@ export default function EquipePage() {
|
||||
<span className="text-[#82C85C]">{roles.find((item) => item.id === member.role)?.label}</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-5 py-3 text-[#E8F4E5]/70">
|
||||
<td className="px-5 py-3 text-[#66756C]">
|
||||
{member.lastLoginAt ? new Date(member.lastLoginAt).toLocaleString("pt-BR") : "Nunca"}
|
||||
</td>
|
||||
</tr>
|
||||
@ -139,11 +139,11 @@ export default function EquipePage() {
|
||||
<Modal onClose={closeModal}>
|
||||
<form className="grid gap-3" onSubmit={onCreate}>
|
||||
<h2 className="font-display text-2xl font-bold uppercase">Novo usuário</h2>
|
||||
{error ? <p className="text-sm text-red-300">{error}</p> : null}
|
||||
{error ? <p className="text-sm text-red-600">{error}</p> : null}
|
||||
<input className={fieldClass} placeholder="Nome" value={name} onChange={(event) => setName(event.target.value)} required />
|
||||
<input className={fieldClass} placeholder="E-mail" type="email" value={email} onChange={(event) => setEmail(event.target.value)} required />
|
||||
<input className={fieldClass} placeholder="Senha" type="password" value={password} onChange={(event) => setPassword(event.target.value)} required minLength={8} />
|
||||
<select className="rounded-xl border border-white/10 bg-black px-4 py-3" value={role} onChange={(event) => setRole(event.target.value as typeof role)}>
|
||||
<select className="rounded-xl border border-[#E4EBE6] bg-white px-4 py-3" value={role} onChange={(event) => setRole(event.target.value as typeof role)}>
|
||||
{roles.map((item) => (
|
||||
<option key={item.id} value={item.id}>
|
||||
{item.label}
|
||||
|
||||
@ -29,18 +29,18 @@ export default function HistoricoPage() {
|
||||
return (
|
||||
<section>
|
||||
<h1 className="font-display text-4xl font-extrabold uppercase">Simulações</h1>
|
||||
{error ? <p className="mt-4 text-sm text-red-300">{error}</p> : null}
|
||||
{error ? <p className="mt-4 text-sm text-red-600">{error}</p> : null}
|
||||
|
||||
<ul className="mt-8 divide-y divide-white/10 rounded-2xl border border-white/10">
|
||||
<ul className="mt-8 divide-y divide-[#E4EBE6] rounded-2xl border border-[#E4EBE6] bg-white">
|
||||
{rows.length === 0 ? (
|
||||
<li className="px-5 py-8 text-[#E8F4E5]/50">Nenhuma simulação ainda.</li>
|
||||
<li className="px-5 py-8 text-[#7A877F]">Nenhuma simulação ainda.</li>
|
||||
) : (
|
||||
rows.map((row) => (
|
||||
<li key={row.id} className="px-5 py-4">
|
||||
<p className="font-medium">
|
||||
{row.originLabel} → {row.destinationLabel}
|
||||
</p>
|
||||
<p className="text-sm text-[#E8F4E5]/55">
|
||||
<p className="text-sm text-[#66756C]">
|
||||
{row.bestCarrierName} · {row.quotedPrice !== null ? money(row.quotedPrice) : "-"} · {row.distanceKm} km
|
||||
{row.customer ? ` · ${row.customer.name}` : ""} ·{" "}
|
||||
{new Date(row.createdAt).toLocaleString("pt-BR")}
|
||||
|
||||
@ -39,11 +39,11 @@ export default function DashboardPage() {
|
||||
}, []);
|
||||
|
||||
if (!data && !error) {
|
||||
return <p className="text-[#E8F4E5]/50">Carregando indicadores…</p>;
|
||||
return <p className="text-[#7A877F]">Carregando indicadores…</p>;
|
||||
}
|
||||
|
||||
if (error || !data) {
|
||||
return <p className="text-sm text-red-300">{error || "Não foi possível carregar o painel."}</p>;
|
||||
return <p className="text-sm text-red-600">{error || "Não foi possível carregar o painel."}</p>;
|
||||
}
|
||||
|
||||
const cards = [
|
||||
@ -76,19 +76,19 @@ export default function DashboardPage() {
|
||||
<section>
|
||||
<ul className="grid gap-3 sm:grid-cols-2 xl:grid-cols-4">
|
||||
{cards.map((card) => (
|
||||
<li key={card.label} className="rounded-2xl border border-white/10 px-5 py-5">
|
||||
<p className="text-xs uppercase tracking-wider text-[#E8F4E5]/45">{card.label}</p>
|
||||
<li key={card.label} className="rounded-2xl border border-[#E4EBE6] bg-white px-5 py-5">
|
||||
<p className="text-xs uppercase tracking-wider text-[#7A877F]">{card.label}</p>
|
||||
<p className="font-display mt-3 text-3xl font-extrabold">{card.value}</p>
|
||||
<p className="mt-2 text-xs text-[#E8F4E5]/50">{card.hint}</p>
|
||||
<p className="mt-2 text-xs text-[#7A877F]">{card.hint}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<div className="mt-8 rounded-2xl border border-white/10 px-5 py-5">
|
||||
<div className="mt-8 rounded-2xl border border-[#E4EBE6] bg-white px-5 py-5">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.22em] text-[#82C85C]">Leitura</p>
|
||||
<ol className="mt-4 grid gap-3">
|
||||
{data.insights.map((insight) => (
|
||||
<li key={insight} className="text-sm text-[#E8F4E5]/80">
|
||||
<li key={insight} className="text-sm text-[#3A463F]">
|
||||
{insight}
|
||||
</li>
|
||||
))}
|
||||
@ -96,13 +96,13 @@ export default function DashboardPage() {
|
||||
</div>
|
||||
|
||||
<div className="mt-8 grid gap-6 xl:grid-cols-2">
|
||||
<div className="overflow-hidden rounded-2xl border border-white/10">
|
||||
<div className="overflow-hidden rounded-2xl border border-[#E4EBE6] bg-white">
|
||||
<div className="px-5 py-4">
|
||||
<p className="text-xs uppercase tracking-wider text-[#E8F4E5]/45">Quem vence a cotação</p>
|
||||
<p className="text-xs uppercase tracking-wider text-[#7A877F]">Quem vence a cotação</p>
|
||||
<h2 className="font-display mt-1 text-xl font-bold uppercase">Transportadoras</h2>
|
||||
</div>
|
||||
<table className="w-full text-left text-sm">
|
||||
<thead className="border-t border-white/10 bg-white/5 text-xs uppercase tracking-wider text-[#E8F4E5]/45">
|
||||
<thead className="border-t border-[#E4EBE6] bg-[#F3F6F4] text-xs uppercase tracking-wider text-[#7A877F]">
|
||||
<tr>
|
||||
<th className="px-5 py-3 font-medium">Nome</th>
|
||||
<th className="px-5 py-3 font-medium">Vitórias</th>
|
||||
@ -111,19 +111,19 @@ export default function DashboardPage() {
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.carriers.length === 0 ? (
|
||||
<tr className="border-t border-white/10">
|
||||
<td className="px-5 py-6 text-[#E8F4E5]/45" colSpan={3}>
|
||||
<tr className="border-t border-[#E4EBE6]">
|
||||
<td className="px-5 py-6 text-[#7A877F]" colSpan={3}>
|
||||
Sem cotações no período.
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
data.carriers.map((carrier) => (
|
||||
<tr key={carrier.name} className="border-t border-white/10">
|
||||
<tr key={carrier.name} className="border-t border-[#E4EBE6]">
|
||||
<td className="px-5 py-3">{carrier.name}</td>
|
||||
<td className="px-5 py-3 text-[#E8F4E5]/70">
|
||||
<td className="px-5 py-3 text-[#66756C]">
|
||||
{carrier.wins} · {carrier.sharePct}%
|
||||
</td>
|
||||
<td className="px-5 py-3 text-[#E8F4E5]/70">{money(carrier.averageWin)}</td>
|
||||
<td className="px-5 py-3 text-[#66756C]">{money(carrier.averageWin)}</td>
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
@ -131,13 +131,13 @@ export default function DashboardPage() {
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div className="overflow-hidden rounded-2xl border border-white/10">
|
||||
<div className="overflow-hidden rounded-2xl border border-[#E4EBE6] bg-white">
|
||||
<div className="px-5 py-4">
|
||||
<p className="text-xs uppercase tracking-wider text-[#E8F4E5]/45">Onde o frete pesa</p>
|
||||
<p className="text-xs uppercase tracking-wider text-[#7A877F]">Onde o frete pesa</p>
|
||||
<h2 className="font-display mt-1 text-xl font-bold uppercase">Rotas mais caras</h2>
|
||||
</div>
|
||||
<table className="w-full text-left text-sm">
|
||||
<thead className="border-t border-white/10 bg-white/5 text-xs uppercase tracking-wider text-[#E8F4E5]/45">
|
||||
<thead className="border-t border-[#E4EBE6] bg-[#F3F6F4] text-xs uppercase tracking-wider text-[#7A877F]">
|
||||
<tr>
|
||||
<th className="px-5 py-3 font-medium">Rota</th>
|
||||
<th className="px-5 py-3 font-medium">Vezes</th>
|
||||
@ -146,17 +146,17 @@ export default function DashboardPage() {
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.routes.length === 0 ? (
|
||||
<tr className="border-t border-white/10">
|
||||
<td className="px-5 py-6 text-[#E8F4E5]/45" colSpan={3}>
|
||||
<tr className="border-t border-[#E4EBE6]">
|
||||
<td className="px-5 py-6 text-[#7A877F]" colSpan={3}>
|
||||
Sem rotas no período.
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
data.routes.map((route) => (
|
||||
<tr key={route.label} className="border-t border-white/10">
|
||||
<tr key={route.label} className="border-t border-[#E4EBE6]">
|
||||
<td className="px-5 py-3">{route.label}</td>
|
||||
<td className="px-5 py-3 text-[#E8F4E5]/70">{route.count}</td>
|
||||
<td className="px-5 py-3 text-[#E8F4E5]/70">{money(route.averageCost)}</td>
|
||||
<td className="px-5 py-3 text-[#66756C]">{route.count}</td>
|
||||
<td className="px-5 py-3 text-[#66756C]">{money(route.averageCost)}</td>
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
|
||||
@ -21,7 +21,7 @@ type Simulation = {
|
||||
};
|
||||
|
||||
const money = (value: number) => value.toLocaleString("pt-BR", { style: "currency", currency: "BRL" });
|
||||
const fieldClass = "rounded-xl border border-white/10 bg-white/5 px-4 py-3";
|
||||
const fieldClass = "rounded-xl border border-[#E4EBE6] bg-white px-4 py-3";
|
||||
|
||||
export default function SimularPage() {
|
||||
const [customers, setCustomers] = useState<Customer[]>([]);
|
||||
@ -78,15 +78,15 @@ export default function SimularPage() {
|
||||
return (
|
||||
<section>
|
||||
<h1 className="font-display text-4xl font-extrabold uppercase">Calcular frete</h1>
|
||||
<p className="mt-4 max-w-xl text-[#E8F4E5]/65">
|
||||
<p className="mt-4 max-w-xl text-[#66756C]">
|
||||
Informe origem, destino e a carga. A Sinka compara as transportadoras e mostra o melhor custo.
|
||||
</p>
|
||||
{error ? <p className="mt-4 text-sm text-red-300">{error}</p> : null}
|
||||
{error ? <p className="mt-4 text-sm text-red-600">{error}</p> : null}
|
||||
|
||||
<div className={`mt-8 grid items-start gap-6 ${loading ? "lg:grid-cols-[minmax(0,28rem)_minmax(0,1fr)]" : "max-w-xl"}`}>
|
||||
<form className="grid gap-3" onSubmit={onSubmit}>
|
||||
<select
|
||||
className="rounded-xl border border-white/10 bg-black px-4 py-3"
|
||||
className="rounded-xl border border-[#E4EBE6] bg-white px-4 py-3"
|
||||
value={form.customerId}
|
||||
onChange={(event) => setForm({ ...form, customerId: event.target.value })}
|
||||
>
|
||||
@ -118,21 +118,21 @@ export default function SimularPage() {
|
||||
{loading ? (
|
||||
<aside className="flex min-h-[18rem] flex-col items-center justify-center px-6 py-8">
|
||||
<img src="/brand/freight-truck.webp" alt="" className="h-44 w-auto max-w-full object-contain" />
|
||||
<p className="mt-6 text-sm text-[#E8F4E5]/70">Calculando a melhor rota…</p>
|
||||
<p className="mt-6 text-sm text-[#66756C]">Calculando a melhor rota…</p>
|
||||
</aside>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{result ? (
|
||||
<div className="mt-10">
|
||||
<p className="text-sm text-[#E8F4E5]/60">
|
||||
<p className="text-sm text-[#66756C]">
|
||||
{result.originLabel} → {result.destinationLabel} · {result.distanceKm} km
|
||||
{result.distanceSource === "fallback" ? " (distância estimada)" : ""}
|
||||
</p>
|
||||
<h2 className="font-display mt-2 text-2xl font-bold uppercase">
|
||||
Melhor opção: {result.bestCarrierName} · {result.quotedPrice !== null ? money(result.quotedPrice) : "-"}
|
||||
</h2>
|
||||
<ul className="mt-6 divide-y divide-white/10 rounded-2xl border border-white/10">
|
||||
<ul className="mt-6 divide-y divide-[#E4EBE6] rounded-2xl border border-[#E4EBE6] bg-white">
|
||||
{result.quotes.map((quote, index) => (
|
||||
<li key={quote.carrierId} className="flex items-center justify-between px-5 py-4">
|
||||
<span>
|
||||
|
||||
@ -120,13 +120,13 @@ export default function TransportadorasPage() {
|
||||
}
|
||||
|
||||
if (me && !me.permissions.includes("carriers:write")) {
|
||||
return <p className="text-[#E8F4E5]/60">Seu perfil não gerencia transportadoras.</p>;
|
||||
return <p className="text-[#66756C]">Seu perfil não gerencia transportadoras.</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<section>
|
||||
<h1 className="font-display text-4xl font-extrabold uppercase">Tabelas de frete</h1>
|
||||
{error && !open ? <p className="mt-4 text-sm text-red-300">{error}</p> : null}
|
||||
{error && !open ? <p className="mt-4 text-sm text-red-600">{error}</p> : null}
|
||||
|
||||
<RegistryTable
|
||||
countLabel={`${rows.length} transportadora${rows.length === 1 ? "" : "s"}`}
|
||||
@ -140,18 +140,18 @@ export default function TransportadorasPage() {
|
||||
}
|
||||
>
|
||||
{rows.map((row) => (
|
||||
<tr key={row.id} className="border-t border-white/10">
|
||||
<tr key={row.id} className="border-t border-[#E4EBE6]">
|
||||
<td className="px-5 py-3 font-medium">{row.name}</td>
|
||||
<td className="px-5 py-3 text-[#E8F4E5]/70">{row.active ? "Ativa" : "Inativa"}</td>
|
||||
<td className="px-5 py-3 text-[#E8F4E5]/70">{money(Number(row.baseFee))}</td>
|
||||
<td className="px-5 py-3 text-[#E8F4E5]/70">{money(Number(row.pricePerKg))}</td>
|
||||
<td className="px-5 py-3 text-[#E8F4E5]/70">{money(Number(row.pricePerKm))}</td>
|
||||
<td className="px-5 py-3 text-[#E8F4E5]/70">{(Number(row.adValoremRate) * 100).toFixed(2)}%</td>
|
||||
<td className="px-5 py-3 text-[#66756C]">{row.active ? "Ativa" : "Inativa"}</td>
|
||||
<td className="px-5 py-3 text-[#66756C]">{money(Number(row.baseFee))}</td>
|
||||
<td className="px-5 py-3 text-[#66756C]">{money(Number(row.pricePerKg))}</td>
|
||||
<td className="px-5 py-3 text-[#66756C]">{money(Number(row.pricePerKm))}</td>
|
||||
<td className="px-5 py-3 text-[#66756C]">{(Number(row.adValoremRate) * 100).toFixed(2)}%</td>
|
||||
<td className="px-5 py-3">
|
||||
<button className="text-[#82C85C]" type="button" onClick={() => openEdit(row)}>
|
||||
Editar
|
||||
</button>
|
||||
<button className="ml-3 text-[#E8F4E5]/50" type="button" onClick={() => void onDelete(row.id)}>
|
||||
<button className="ml-3 text-[#7A877F]" type="button" onClick={() => void onDelete(row.id)}>
|
||||
Remover
|
||||
</button>
|
||||
</td>
|
||||
@ -163,7 +163,7 @@ export default function TransportadorasPage() {
|
||||
<Modal onClose={closeModal}>
|
||||
<form className="grid gap-3" onSubmit={onSubmit}>
|
||||
<h2 className="font-display text-2xl font-bold uppercase">{editing ? "Editar" : "Nova transportadora"}</h2>
|
||||
{error ? <p className="text-sm text-red-300">{error}</p> : null}
|
||||
{error ? <p className="text-sm text-red-600">{error}</p> : null}
|
||||
<input className={fieldClass} placeholder="Nome" value={form.name} onChange={(event) => setForm({ ...form, name: event.target.value })} required />
|
||||
<input className={fieldClass} placeholder="CNPJ" value={form.document} onChange={(event) => setForm({ ...form, document: event.target.value })} />
|
||||
<input className={fieldClass} placeholder="Telefone" value={form.phone} onChange={(event) => setForm({ ...form, phone: event.target.value })} />
|
||||
|
||||
@ -149,12 +149,12 @@ function TenantRowActions({
|
||||
</button>
|
||||
{menu ? (
|
||||
<div
|
||||
className="fixed z-50 min-w-40 overflow-hidden rounded-xl border border-white/10 bg-black py-1 shadow-xl"
|
||||
className="fixed z-50 min-w-40 overflow-hidden rounded-xl border border-[#E4EBE6] bg-white py-1 shadow-xl"
|
||||
style={{ top: menu.top, right: menu.right }}
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
<button
|
||||
className="block w-full px-4 py-2 text-left text-sm text-[#E8F4E5] hover:bg-white/5"
|
||||
className="block w-full px-4 py-2 text-left text-sm text-[#1B241E] hover:bg-[#F3F6F4]"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setMenu(null);
|
||||
@ -165,7 +165,7 @@ function TenantRowActions({
|
||||
</button>
|
||||
<a
|
||||
href={`/admin/chat/${tenant.slug}`}
|
||||
className="flex items-center justify-between px-4 py-2 text-sm text-[#E8F4E5] hover:bg-white/5"
|
||||
className="flex items-center justify-between px-4 py-2 text-sm text-[#1B241E] hover:bg-[#F3F6F4]"
|
||||
>
|
||||
Chat
|
||||
{chatCount ? (
|
||||
@ -176,7 +176,7 @@ function TenantRowActions({
|
||||
</a>
|
||||
<a
|
||||
href={`/${tenant.slug}/login`}
|
||||
className="block px-4 py-2 text-sm text-[#E8F4E5] hover:bg-white/5"
|
||||
className="block px-4 py-2 text-sm text-[#1B241E] hover:bg-[#F3F6F4]"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
@ -298,7 +298,7 @@ export default function PlatformClientesPage() {
|
||||
return (
|
||||
<section>
|
||||
<h1 className="font-display text-4xl font-extrabold uppercase">Clientes</h1>
|
||||
{error && !open ? <p className="mt-4 text-sm text-red-300">{error}</p> : null}
|
||||
{error && !open ? <p className="mt-4 text-sm text-red-600">{error}</p> : null}
|
||||
|
||||
<RegistryTable
|
||||
scroll={false}
|
||||
@ -313,10 +313,10 @@ export default function PlatformClientesPage() {
|
||||
}
|
||||
>
|
||||
{tenants.map((tenant) => (
|
||||
<tr key={tenant.id} className="border-t border-white/10">
|
||||
<tr key={tenant.id} className="border-t border-[#E4EBE6]">
|
||||
<td className="truncate px-5 py-3 font-medium">{tenant.name}</td>
|
||||
<td className="truncate px-5 py-3 text-[#E8F4E5]/70">/{tenant.slug}</td>
|
||||
<td className="truncate px-5 py-3 text-[#E8F4E5]/70">{tenant.status === "ACTIVE" ? "Ativo" : "Suspenso"}</td>
|
||||
<td className="truncate px-5 py-3 text-[#66756C]">/{tenant.slug}</td>
|
||||
<td className="truncate px-5 py-3 text-[#66756C]">{tenant.status === "ACTIVE" ? "Ativo" : "Suspenso"}</td>
|
||||
<td className="overflow-hidden px-5 py-3">
|
||||
<TenantRowActions
|
||||
tenant={tenant}
|
||||
@ -333,7 +333,7 @@ export default function PlatformClientesPage() {
|
||||
{created ? (
|
||||
<div>
|
||||
<h2 className="font-display text-2xl font-bold uppercase">Cliente criado</h2>
|
||||
<p className="mt-3 text-sm text-[#E8F4E5]/65">Guarde o acesso do administrador:</p>
|
||||
<p className="mt-3 text-sm text-[#66756C]">Guarde o acesso do administrador:</p>
|
||||
<p className="mt-4 text-sm">
|
||||
Endereço:{" "}
|
||||
<a className="text-[#82C85C]" href={created.access.path} target="_blank" rel="noopener noreferrer">
|
||||
@ -349,7 +349,7 @@ export default function PlatformClientesPage() {
|
||||
) : (
|
||||
<form className="grid gap-3" onSubmit={onCreate}>
|
||||
<h2 className="font-display text-2xl font-bold uppercase">Novo cliente</h2>
|
||||
{error ? <p className="text-sm text-red-300">{error}</p> : null}
|
||||
{error ? <p className="text-sm text-red-600">{error}</p> : null}
|
||||
<input
|
||||
className={fieldClass}
|
||||
placeholder="Nome"
|
||||
@ -383,13 +383,13 @@ export default function PlatformClientesPage() {
|
||||
{usersOf ? (
|
||||
<Modal wide onClose={closeUsers}>
|
||||
<h2 className="font-display text-2xl font-bold uppercase">Usuários · {usersOf.name}</h2>
|
||||
<p className="mt-2 text-sm text-[#E8F4E5]/60">
|
||||
<p className="mt-2 text-sm text-[#66756C]">
|
||||
Contas de {usersOf.name}. Se a senha estiver vazia, gere uma nova.
|
||||
</p>
|
||||
{usersError ? <p className="mt-3 text-sm text-red-300">{usersError}</p> : null}
|
||||
{usersError ? <p className="mt-3 text-sm text-red-600">{usersError}</p> : null}
|
||||
<div className="mt-5 overflow-x-auto">
|
||||
<table className="w-full text-left text-sm">
|
||||
<thead className="text-xs uppercase tracking-wider text-[#E8F4E5]/50">
|
||||
<thead className="text-xs uppercase tracking-wider text-[#7A877F]">
|
||||
<tr>
|
||||
<th className="pb-3 pr-6 font-medium">Nome</th>
|
||||
<th className="pb-3 pr-6 font-medium">Usuário</th>
|
||||
@ -401,20 +401,20 @@ export default function PlatformClientesPage() {
|
||||
<tbody>
|
||||
{users.length === 0 ? (
|
||||
<tr>
|
||||
<td className="py-6 text-[#E8F4E5]/50" colSpan={5}>
|
||||
<td className="py-6 text-[#7A877F]" colSpan={5}>
|
||||
Nenhum usuário neste cliente.
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
users.map((user) => (
|
||||
<tr key={user.id} className="border-t border-white/10">
|
||||
<tr key={user.id} className="border-t border-[#E4EBE6]">
|
||||
<td className="py-3 pr-6 font-medium whitespace-nowrap">{user.name}</td>
|
||||
<td className="py-3 pr-6 text-[#E8F4E5]/80 whitespace-nowrap">{user.email}</td>
|
||||
<td className="py-3 pr-6 text-[#3A463F] whitespace-nowrap">{user.email}</td>
|
||||
<td className="py-3 pr-6 font-mono whitespace-nowrap text-[#82C85C]">{user.password || "-"}</td>
|
||||
<td className="py-3 pr-6 whitespace-nowrap text-[#E8F4E5]/70">{roleLabel[user.role]}</td>
|
||||
<td className="py-3 pr-6 whitespace-nowrap text-[#66756C]">{roleLabel[user.role]}</td>
|
||||
<td className="py-3 whitespace-nowrap">
|
||||
<button
|
||||
className="text-xs text-[#E8F4E5]/70"
|
||||
className="text-xs text-[#66756C]"
|
||||
type="button"
|
||||
disabled={issuing === user.id}
|
||||
onClick={() => void issuePassword(user.id)}
|
||||
|
||||
@ -25,25 +25,25 @@ export default function PlatformIntegracoesPage() {
|
||||
return (
|
||||
<section>
|
||||
<h1 className="font-display text-4xl font-extrabold uppercase">Integrações</h1>
|
||||
{error ? <p className="mt-4 text-sm text-red-300">{error}</p> : null}
|
||||
{loading ? <p className="mt-4 text-sm text-[#E8F4E5]/50">Verificando integrações…</p> : null}
|
||||
{error ? <p className="mt-4 text-sm text-red-600">{error}</p> : null}
|
||||
{loading ? <p className="mt-4 text-sm text-[#7A877F]">Verificando integrações…</p> : null}
|
||||
|
||||
<ul className="mt-8 grid gap-3 md:grid-cols-2">
|
||||
{items.map((item) => {
|
||||
const active = item.status === "active";
|
||||
return (
|
||||
<li key={item.id} className="rounded-2xl border border-white/10 px-5 py-5">
|
||||
<li key={item.id} className="rounded-2xl border border-[#E4EBE6] bg-white px-5 py-5">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<h2 className="text-lg font-semibold">{item.name}</h2>
|
||||
<span
|
||||
className={`shrink-0 rounded-full px-2.5 py-1 text-[11px] font-semibold uppercase tracking-wider ${
|
||||
active ? "bg-[#82C85C]/15 text-[#82C85C]" : "bg-red-400/15 text-red-300"
|
||||
active ? "bg-[#82C85C]/15 text-[#82C85C]" : "bg-red-400/15 text-red-600"
|
||||
}`}
|
||||
>
|
||||
{active ? "Ativo" : "Inativo"}
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-2 text-sm text-[#E8F4E5]/60">{item.description}</p>
|
||||
<p className="mt-2 text-sm text-[#66756C]">{item.description}</p>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
|
||||
@ -28,7 +28,7 @@ export default function PlatformPage() {
|
||||
|
||||
return (
|
||||
<section>
|
||||
{error ? <p className="mb-4 text-sm text-red-300">{error}</p> : null}
|
||||
{error ? <p className="mb-4 text-sm text-red-600">{error}</p> : null}
|
||||
|
||||
<ul className="grid gap-3 sm:grid-cols-2 xl:grid-cols-4">
|
||||
{[
|
||||
@ -53,10 +53,10 @@ export default function PlatformPage() {
|
||||
hint: "no chat de suporte",
|
||||
},
|
||||
].map((card) => (
|
||||
<li key={card.label} className="rounded-2xl border border-white/10 px-5 py-5">
|
||||
<p className="text-xs uppercase tracking-wider text-[#E8F4E5]/45">{card.label}</p>
|
||||
<li key={card.label} className="rounded-2xl border border-[#E4EBE6] bg-white px-5 py-5">
|
||||
<p className="text-xs uppercase tracking-wider text-[#7A877F]">{card.label}</p>
|
||||
<p className="font-display mt-3 text-3xl font-extrabold">{card.value}</p>
|
||||
<p className="mt-2 text-xs text-[#E8F4E5]/50">{card.hint}</p>
|
||||
<p className="mt-2 text-xs text-[#7A877F]">{card.hint}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
@ -32,6 +32,12 @@ body {
|
||||
font-family: var(--font-sans-face), system-ui, sans-serif;
|
||||
}
|
||||
|
||||
html:has(.sinka-app),
|
||||
body:has(.sinka-app) {
|
||||
background: #f4f7f5;
|
||||
color: #1b241e;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
@ -165,6 +171,16 @@ h3,
|
||||
color: #e8f4e5;
|
||||
}
|
||||
|
||||
.sinka-app .btn-ghost {
|
||||
border-color: #c5d9c0;
|
||||
color: #1b241e;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.sinka-app .btn-ghost:hover {
|
||||
background: #f3f6f4;
|
||||
}
|
||||
|
||||
.btn-google {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
@ -18,14 +18,14 @@ export function RegistryTable({
|
||||
scroll?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div className="mt-8 overflow-hidden rounded-2xl border border-white/10">
|
||||
<div className="mt-8 overflow-hidden rounded-2xl border border-[#E4EBE6] bg-white">
|
||||
<div className="flex items-center justify-between gap-3 px-5 py-3">
|
||||
<p className="text-sm text-[#E8F4E5]/60">{countLabel}</p>
|
||||
<p className="text-sm text-[#66756C]">{countLabel}</p>
|
||||
{action ?? <span />}
|
||||
</div>
|
||||
<div className={`border-t border-white/10 ${scroll ? "overflow-x-auto" : "overflow-hidden"}`}>
|
||||
<div className={`border-t border-[#E4EBE6] ${scroll ? "overflow-x-auto" : "overflow-hidden"}`}>
|
||||
<table className={`w-full table-fixed text-left text-sm ${scroll ? "min-w-[36rem]" : ""}`}>
|
||||
<thead className="bg-white/5 text-xs uppercase tracking-wider text-[#E8F4E5]/50">
|
||||
<thead className="bg-[#F3F6F4] text-xs uppercase tracking-wider text-[#7A877F]">
|
||||
<tr>
|
||||
{columns.map((column) => (
|
||||
<th key={column} className="px-5 py-3 font-medium">
|
||||
@ -37,7 +37,7 @@ export function RegistryTable({
|
||||
<tbody>
|
||||
{isEmpty ? (
|
||||
<tr>
|
||||
<td className="px-5 py-8 text-[#E8F4E5]/50" colSpan={columns.length}>
|
||||
<td className="px-5 py-8 text-[#7A877F]" colSpan={columns.length}>
|
||||
{empty ?? "Nenhum registro."}
|
||||
</td>
|
||||
</tr>
|
||||
@ -61,9 +61,9 @@ export function Modal({
|
||||
wide?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 px-4" onClick={onClose}>
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 px-4" onClick={onClose}>
|
||||
<div
|
||||
className={`max-h-[90dvh] w-full overflow-y-auto rounded-2xl border border-white/10 bg-black p-6 ${
|
||||
className={`max-h-[90dvh] w-full overflow-y-auto rounded-2xl border border-[#E4EBE6] bg-white p-6 ${
|
||||
wide ? "max-w-5xl" : "max-w-md"
|
||||
}`}
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
@ -74,4 +74,4 @@ export function Modal({
|
||||
);
|
||||
}
|
||||
|
||||
export const fieldClass = "rounded-xl border border-white/10 bg-white/5 px-4 py-3";
|
||||
export const fieldClass = "rounded-xl border border-[#E4EBE6] bg-white px-4 py-3 outline-none focus:border-[#82C85C]";
|
||||
|
||||
@ -6,6 +6,26 @@ import { usePathname, useRouter } from "next/navigation";
|
||||
import { useEffect, useState, type ReactNode } from "react";
|
||||
import { api, type SessionUser } from "@/lib/api";
|
||||
|
||||
function BrandLoading() {
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 z-[60] flex items-center justify-center bg-[#163027]"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
aria-label="Carregando"
|
||||
>
|
||||
<Image
|
||||
src="/brand/sinka-logo.webp"
|
||||
alt="Sinka"
|
||||
width={180}
|
||||
height={58}
|
||||
className="h-8 w-auto animate-pulse"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function RestrictedShell({
|
||||
kind,
|
||||
basePath,
|
||||
@ -20,6 +40,7 @@ export function RestrictedShell({
|
||||
const [user, setUser] = useState<SessionUser | null>(null);
|
||||
const [error, setError] = useState("");
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
const [navigating, setNavigating] = useState(false);
|
||||
const loginPath = kind === "platform" ? "/admin/login" : `${basePath}/login`;
|
||||
|
||||
useEffect(() => {
|
||||
@ -49,18 +70,27 @@ export function RestrictedShell({
|
||||
|
||||
useEffect(() => {
|
||||
setMenuOpen(false);
|
||||
setNavigating(false);
|
||||
}, [pathname]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!navigating) {
|
||||
return;
|
||||
}
|
||||
const timeout = window.setTimeout(() => setNavigating(false), 8000);
|
||||
return () => window.clearTimeout(timeout);
|
||||
}, [navigating]);
|
||||
|
||||
async function logout() {
|
||||
await api("/api/auth/logout", { method: "POST" }).catch(() => undefined);
|
||||
router.replace(loginPath);
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return (
|
||||
<main className="flex min-h-dvh items-center justify-center bg-black text-[#E8F4E5]/60">
|
||||
{error || "Carregando…"}
|
||||
</main>
|
||||
return error ? (
|
||||
<main className="flex min-h-dvh items-center justify-center bg-[#F4F7F5] text-[#66756C]">{error}</main>
|
||||
) : (
|
||||
<BrandLoading />
|
||||
);
|
||||
}
|
||||
|
||||
@ -73,17 +103,17 @@ export function RestrictedShell({
|
||||
]
|
||||
: [
|
||||
{ href: basePath, label: "Dashboard" },
|
||||
{ href: `${basePath}/clientes`, label: "Clientes" },
|
||||
{ href: `${basePath}/simular`, label: "Simular" },
|
||||
{ href: `${basePath}/historico`, label: "Histórico" },
|
||||
{ href: `${basePath}/chat`, label: "Chat" },
|
||||
{ href: `${basePath}/arquivos`, label: "Arquivos" },
|
||||
{ href: `${basePath}/clientes`, label: "Clientes" },
|
||||
...(user.permissions.includes("carriers:write")
|
||||
? [{ href: `${basePath}/transportadoras`, label: "Transportadoras" }]
|
||||
: []),
|
||||
...(user.permissions.includes("users:manage") || user.role === "MANAGER"
|
||||
? [{ href: `${basePath}/equipe`, label: "Equipe" }]
|
||||
: []),
|
||||
{ href: `${basePath}/arquivos`, label: "Arquivos" },
|
||||
{ href: `${basePath}/chat`, label: "Chat" },
|
||||
];
|
||||
|
||||
function isActive(href: string) {
|
||||
@ -96,10 +126,19 @@ export function RestrictedShell({
|
||||
return href === basePath ? pathname === basePath : pathname.startsWith(href);
|
||||
}
|
||||
|
||||
function startNavigation(href: string) {
|
||||
const current = pathname.replace(/\/$/, "") || "/";
|
||||
const target = href.replace(/\/$/, "") || "/";
|
||||
if (current === target) {
|
||||
return;
|
||||
}
|
||||
setNavigating(true);
|
||||
}
|
||||
|
||||
const nav = (
|
||||
<>
|
||||
<div className="px-5 pb-6 pt-7">
|
||||
<Link href={basePath} className="inline-block">
|
||||
<Link href={basePath} className="inline-block" onClick={() => startNavigation(basePath)}>
|
||||
<Image src="/brand/sinka-logo.webp" alt="Sinka" width={140} height={46} className="h-6 w-auto" />
|
||||
</Link>
|
||||
<p className="mt-5 text-[11px] font-semibold uppercase tracking-[0.22em] text-[#82C85C]">
|
||||
@ -111,10 +150,11 @@ export function RestrictedShell({
|
||||
<Link
|
||||
key={link.href}
|
||||
href={link.href}
|
||||
onClick={() => startNavigation(link.href)}
|
||||
className={`rounded-lg px-3 py-3 text-base ${
|
||||
isActive(link.href)
|
||||
? "bg-[#82C85C]/12 font-medium text-[#82C85C]"
|
||||
: "text-[#E8F4E5]/70 hover:bg-white/5 hover:text-[#E8F4E5]"
|
||||
? "bg-[#82C85C]/18 font-medium text-[#82C85C]"
|
||||
: "text-white/70 hover:bg-white/10 hover:text-white"
|
||||
}`}
|
||||
>
|
||||
{link.label}
|
||||
@ -122,10 +162,10 @@ export function RestrictedShell({
|
||||
))}
|
||||
</nav>
|
||||
<div className="mt-auto border-t border-white/10 px-5 py-5">
|
||||
<p className="truncate text-sm font-medium">{user.name}</p>
|
||||
<p className="truncate text-sm font-medium text-white">{user.name}</p>
|
||||
<p className="mt-0.5 text-xs text-[#82C85C]">{user.roleLabel}</p>
|
||||
<button
|
||||
className="btn-ghost mt-4 w-full justify-center px-3 py-2 text-xs"
|
||||
className="btn-ghost mt-4 w-full justify-center px-3 py-2 text-xs !border-white/25 !bg-transparent !text-white hover:!bg-white/10"
|
||||
type="button"
|
||||
onClick={() => void logout()}
|
||||
>
|
||||
@ -136,13 +176,13 @@ export function RestrictedShell({
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="min-h-dvh bg-black text-[#E8F4E5] lg:flex">
|
||||
<header className="flex items-center justify-between border-b border-white/10 px-4 py-3 lg:hidden">
|
||||
<Link href={basePath}>
|
||||
<div className="sinka-app min-h-dvh bg-[#F4F7F5] text-[#1B241E] lg:flex">
|
||||
<header className="flex items-center justify-between border-b border-white/10 bg-[#163027] px-4 py-3 lg:hidden">
|
||||
<Link href={basePath} onClick={() => startNavigation(basePath)}>
|
||||
<Image src="/brand/sinka-logo.webp" alt="Sinka" width={120} height={40} className="h-6 w-auto" />
|
||||
</Link>
|
||||
<button
|
||||
className="rounded-lg border border-white/15 px-3 py-1.5 text-xs uppercase tracking-wider text-[#E8F4E5]/80"
|
||||
className="rounded-lg border border-white/20 px-3 py-1.5 text-xs uppercase tracking-wider text-white/80"
|
||||
type="button"
|
||||
onClick={() => setMenuOpen((open) => !open)}
|
||||
>
|
||||
@ -152,7 +192,7 @@ export function RestrictedShell({
|
||||
|
||||
{menuOpen ? (
|
||||
<button
|
||||
className="fixed inset-0 z-30 bg-black/60 lg:hidden"
|
||||
className="fixed inset-0 z-30 bg-black/40 lg:hidden"
|
||||
type="button"
|
||||
aria-label="Fechar menu"
|
||||
onClick={() => setMenuOpen(false)}
|
||||
@ -160,7 +200,7 @@ export function RestrictedShell({
|
||||
) : null}
|
||||
|
||||
<aside
|
||||
className={`fixed inset-y-0 left-0 z-40 flex w-72 flex-col border-r border-white/10 bg-black transition-transform lg:static lg:translate-x-0 ${
|
||||
className={`fixed inset-y-0 left-0 z-40 flex w-72 flex-col bg-[#163027] text-white transition-transform lg:static lg:translate-x-0 ${
|
||||
menuOpen ? "translate-x-0" : "-translate-x-full"
|
||||
}`}
|
||||
>
|
||||
@ -168,6 +208,7 @@ export function RestrictedShell({
|
||||
</aside>
|
||||
|
||||
<main className="min-w-0 flex-1 px-5 py-8 lg:px-10 lg:py-10">{children}</main>
|
||||
{navigating ? <BrandLoading /> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -99,12 +99,12 @@ export function SupportChat({
|
||||
</a>
|
||||
) : null}
|
||||
<h1 className="font-display text-4xl font-extrabold uppercase">{heading}</h1>
|
||||
{error ? <p className="mt-4 text-sm text-red-300">{error}</p> : null}
|
||||
{error ? <p className="mt-4 text-sm text-red-600">{error}</p> : null}
|
||||
|
||||
<div className="mt-6 flex min-h-[22rem] flex-1 flex-col overflow-hidden rounded-2xl border border-white/10">
|
||||
<div className="mt-6 flex min-h-[22rem] flex-1 flex-col overflow-hidden rounded-2xl border border-[#E4EBE6] bg-white">
|
||||
<div className="flex-1 space-y-3 overflow-y-auto px-5 py-4">
|
||||
{messages.length === 0 ? (
|
||||
<p className="text-sm text-[#E8F4E5]/45">{ready ? "Nenhuma mensagem ainda." : "Conectando…"}</p>
|
||||
<p className="text-sm text-[#7A877F]">{ready ? "Nenhuma mensagem ainda." : "Conectando…"}</p>
|
||||
) : (
|
||||
messages.map((message) => (
|
||||
<article
|
||||
@ -112,7 +112,7 @@ export function SupportChat({
|
||||
className={`max-w-[80%] rounded-2xl px-4 py-3 text-sm ${
|
||||
message.authorKind === "platform"
|
||||
? "ml-auto bg-[#82C85C] text-black"
|
||||
: "bg-white/8 text-[#E8F4E5]"
|
||||
: "bg-[#F3F6F4] text-[#1B241E]"
|
||||
}`}
|
||||
>
|
||||
<p className="text-[11px] uppercase tracking-wider opacity-70">{message.authorName}</p>
|
||||
@ -122,9 +122,9 @@ export function SupportChat({
|
||||
)}
|
||||
<div ref={bottomRef} />
|
||||
</div>
|
||||
<form className="flex gap-2 border-t border-white/10 p-3" onSubmit={onSubmit}>
|
||||
<form className="flex gap-2 border-t border-[#E4EBE6] p-3" onSubmit={onSubmit}>
|
||||
<input
|
||||
className="flex-1 rounded-xl border border-white/10 bg-white/5 px-4 py-3"
|
||||
className="flex-1 rounded-xl border border-[#E4EBE6] bg-white px-4 py-3"
|
||||
placeholder="Escrever mensagem"
|
||||
value={text}
|
||||
onChange={(event) => setText(event.target.value)}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user