// mb-account.jsx — Carteira, Minhas Compras, Perfil
function EmBreveOverlay({ icon, title, text }) {
return (
);
}
function CarteiraScreen({ wallet, onOpenCashback, featureEnabled }) {
if (!featureEnabled) return (
);
return (
BemCash
{/* balance card */}
Saldo disponível
{money(wallet.balance)}
{money(wallet.pending)} a liberar
{/* actions */}
{[["coin", "Usar saldo"], ["receipt", "Extrato"], ["headset", "Ajuda"]].map(([ic, lb]) => (
{lb}
))}
{/* how cashback works */}
Como funciona o BemCash?
BemCash, Turbo e onde usar seu saldo
{/* movements */}
Movimentações
{wallet.movements.map((m, i) => (
{m.value < 0 ? "− " : "+"}{money(Math.abs(m.value))}
))}
);
}
// Cashback explainer (sheet content)
function CashbackInfo() {
const items = [
{ ic: "coin", t: "BemCash", d: "Cada loja define um percentual de cashback nos produtos. Ao comprar, esse valor vira BemCash na sua conta." },
{ ic: "bolt", t: "Cashback Turbo", d: "Um percentual extra que o comerciante turbina para destacar a loja. Esse adicional sai da carteira digital exclusiva do comerciante." },
{ ic: "wallet", t: "Onde usar", d: "Use o saldo acumulado para abater o valor de qualquer pedido dentro do app, em entrega ou retirada." },
{ ic: "clock", t: "Liberação", d: "O cashback fica como “a liberar” durante o prazo de troca/devolução e depois vira saldo disponível." },
];
return (
Compre e receba de volta
BemCash + Turbo direto na sua conta
{items.map(it => (
))}
);
}
// ── Minhas Compras ───────────────────────────────────────────
function StatusChip({ label, accent, warn }) {
const bg = warn ? "#FEF3E2" : accent ? "var(--accent-50)" : "var(--bg)";
const color = warn ? "#B5751A" : accent ? "var(--accent)" : "var(--muted)";
return {label} ;
}
const ORDER_STEPS = [
{ status: "aguardando_pagamento", label: "Aguardando pagamento", icon: "coin" },
{ status: "pendente", label: "Pedido recebido", icon: "receipt" },
{ status: "em_preparo", label: "Em preparo", icon: "bag" },
{ status: "saiu_para_entrega", label: "Saiu para entrega", icon: "bike" },
{ status: "entregue", label: "Entregue", icon: "check" },
{ status: "concluido", label: "Finalizado", icon: "coin" },
];
const APPT_STEPS = [
{ status: "solicitado", label: "Agendamento recebido", icon: "clock" },
{ status: "confirmado", label: "Confirmado", icon: "check" },
{ status: "em_atendimento", label: "Em atendimento", icon: "health" },
{ status: "concluido", label: "Finalizado", icon: "coin" },
];
function StatusTimeline({ steps, currentStatus }) {
const currentIdx = steps.findIndex(s => s.status === currentStatus);
return (
{steps.map((step, i) => {
const done = i < currentIdx;
const active = i === currentIdx;
const future = i > currentIdx;
return (
{i < steps.length - 1 &&
}
{step.label}
{active &&
agora
}
);
})}
);
}
function OrderDetail({ order, onBack, onReview, onReorder }) {
const raw = order._raw || {};
const shortId = (raw.id || order.id || "").slice(0, 8);
const p = partnerById(order.partner) || { name: raw.company_name || "Parceiro", seg: "comer" };
const canReview = raw.status === "concluido" || raw.status === "entregue";
const awaitingPayment = raw.status === "aguardando_pagamento";
const checkoutUrl = order.checkoutUrl || raw.mp_checkout_url;
const items = raw.items || [];
return (
{/* partner */}
{p.name}
{order.date} · {order.mode}
{/* timeline */}
{/* items */}
{items.length > 0 && (
Itens
{items.map((it, i) => (
{it.quantity}x {it.name}
{money((parseFloat(it.unit_price) || 0) * (it.quantity || 1))}
))}
)}
{/* totals */}
{raw.subtotal != null &&
Subtotal {money(parseFloat(raw.subtotal) || 0)}
}
{raw.taxa_entrega != null &&
Entrega {parseFloat(raw.taxa_entrega) === 0 ? "Grátis" : money(parseFloat(raw.taxa_entrega) || 0)}
}
Total {money(order.total)}
{/* Awaiting payment — banner + action button */}
{awaitingPayment && (
Pagamento pendente
Seu pedido foi reservado mas ainda não foi pago. Conclua o pagamento para confirmar.
{checkoutUrl
?
{ window.location.href = checkoutUrl; }}>Ir para o Mercado Pago →
:
Link de pagamento indisponível. Entre em contato com o suporte.
}
)}
Pedir de novo
{canReview && Avaliar }
);
}
function ApptDetail({ appt, onBack, onReview }) {
const raw = appt._raw || {};
const p = partnerById(appt.partner) || { name: raw.company_name || "Prestador", seg: "saude" };
const canReview = raw.status === "concluido";
return (
{/* partner */}
{/* timeline */}
{/* details */}
{appt.date} · {appt.time}
{appt.local === "loja" ? "No estabelecimento" : "Na sua casa"}
{raw.service_price != null && (
{money(parseFloat(raw.service_price) || 0)}
)}
{canReview && (
Avaliar atendimento
)}
);
}
function ComprasScreen({ onReview, onReorder, onAgendaDetail, featureEnabled }) {
if (!featureEnabled) return (
);
const [seg, setSeg] = React.useState("pedidos");
const segs = [["pedidos", "Pedidos"], ["agenda", "Agendamentos"], ["contatos", "Contatos"]];
const [orderDetail, setOrderDetail] = React.useState(null);
const [apptDetail, setApptDetail] = React.useState(null);
// Dados reais da API
const [apiOrders, setApiOrders] = React.useState(null);
const [apiAppts, setApiAppts] = React.useState(null);
const [apiLeads, setApiLeads] = React.useState(null);
const [loading, setLoading] = React.useState(false);
React.useEffect(() => {
setLoading(true);
Promise.all([
API.orders.list().then(d => setApiOrders((d.orders || []).map(apiOrderToMock))).catch(() => {}),
API.appointments.list().then(d => setApiAppts((d.appointments || []).map(apiApptToMock))).catch(() => {}),
API.leads.list().then(d => setApiLeads(d.leads || [])).catch(() => {}),
]).finally(() => setLoading(false));
}, []);
const orders = apiOrders || ORDERS;
const appts = apiAppts || AGENDAMENTOS;
const leads = apiLeads || LEADS;
return (
Minhas Compras
{/* segmented */}
{segs.map(([id, lb]) => (
setSeg(id)} style={{ flex: 1, height: 38, borderRadius: 10, border: "none", cursor: "pointer", background: seg === id ? "var(--brand)" : "transparent", color: seg === id ? "#fff" : "var(--muted)", font: `${seg === id ? 700 : 600} 12.5px var(--ui)` }}>{lb}
))}
{loading &&
Carregando…
}
{/* PEDIDOS */}
{seg === "pedidos" && orders.map(o => {
const p = partnerById(o.partner) || { name: o._raw?.company_name || "Parceiro", seg: "comer" };
const toRate = !o.rated;
const isPendingPayment = o.rawStatus === "aguardando_pagamento";
return (
setOrderDetail(o)} style={{ background: "var(--surface)", borderRadius: 18, overflow: "hidden", boxShadow: "0 3px 12px rgba(16,18,45,.05)", cursor: "pointer" }}>
{isPendingPayment && (
Pagamento pendente — toque para continuar
)}
{p.name}
{o.items.join(", ")}
{o.date} • {o.mode} • {money(o.total)}
{o.rated &&
}
+{money(o.cashback + o.turbo)} de cashback{o.turbo > 0 ? " (com Turbo)" : ""}
);
})}
{/* AGENDAMENTOS */}
{seg === "agenda" && appts.map(a => {
const p = partnerById(a.partner) || { name: a._raw?.company_name || "Prestador", seg: "saude" };
const toRate = a.status === "Concluído" && !a.rated;
return (
setApptDetail(a)} style={{ background: "var(--surface)", borderRadius: 18, overflow: "hidden", boxShadow: "0 3px 12px rgba(16,18,45,.05)", cursor: "pointer" }}>
{p.name}
{a.service}
{a.date} · {a.time}
•
{a.local === "loja" ? "No local" : "Na sua casa"}
{a.rated &&
}
{a.upcoming && (
onReorder(a.partner)} style={{ flex: 1, height: 44 }}>Reagendar
onAgendaDetail(a)} style={{ flex: 1, height: 44 }}>Ver detalhes
)}
);
})}
{/* CONTATOS */}
{seg === "contatos" && leads.map(l => {
const p = partnerById(l.company_id) || { name: l.company_name || "Parceiro", seg: "morar" };
return (
{p.name}
{l.listing_title || l.title || ""}
{l.channel === "whatsapp" ? "WhatsApp" : "Ligação"} · {l.date || new Date(l.created_at).toLocaleDateString("pt-BR", { day: "2-digit", month: "short" })}
onReorder(l.partner)} style={{ height: 44 }}>Ver na vitrine
);
})}
{orderDetail && (
setOrderDetail(null)}
onReview={() => { setOrderDetail(null); onReview(orderDetail.partner, orderDetail.mode); }}
onReorder={() => { setOrderDetail(null); onReorder(orderDetail.partner); }}
/>
)}
{apptDetail && (
setApptDetail(null)}
onReview={() => { setApptDetail(null); onReview(apptDetail.partner, "agenda"); }}
/>
)}
);
}
// ── Perfil ───────────────────────────────────────────────────
function PerfilEmBreveModal({ icon, title, onClose }) {
return (
{ if (e.target === e.currentTarget) onClose(); }}>
{title}
Este recurso estará disponível em breve.
Enquanto isso, explore os parceiros disponíveis na aba Descobrir e aproveite as melhores opções da sua cidade!
Em breve
Fechar
);
}
function PerfilScreen({ wallet, user, onOpenCarteira, onOpen, onLogout, onOpenClub, clubMember }) {
const [addrCount, setAddrCount] = React.useState(null);
const [favCount, setFavCount] = React.useState(null);
const [canInstall, setCanInstall] = React.useState(false);
const [showIosHint, setShowIosHint] = React.useState(false);
const [emBreve, setEmBreve] = React.useState(null); // { icon, title }
React.useEffect(() => {
API.me.addresses().then(d => setAddrCount((d.addresses || []).length)).catch(() => {});
API.me.favorites().then(d => setFavCount((d.favorites || d.companies || []).length)).catch(() => {});
// PWA: verifica se pode instalar (Android/Chrome)
if (window.mbCanInstallPWA && window.mbCanInstallPWA()) setCanInstall(true);
const onInstallable = () => setCanInstall(true);
const onInstalled = () => setCanInstall(false);
window.addEventListener('pwa-installable', onInstallable);
window.addEventListener('pwa-installed', onInstalled);
// iOS: mostra dica se não estiver em modo standalone
const isIos = /iphone|ipad|ipod/i.test(navigator.userAgent);
if (isIos && window.mbIsStandalone && !window.mbIsStandalone()) setShowIosHint(true);
return () => {
window.removeEventListener('pwa-installable', onInstallable);
window.removeEventListener('pwa-installed', onInstalled);
};
}, []);
const rows = [
["pin", "Endereços", addrCount != null && addrCount > 0 ? `${addrCount} endereço${addrCount !== 1 ? "s" : ""}` : null, "enderecos"],
["card", "Formas de pagamento", "Pix · Cartão MP", "pagamento"],
["heart", "Favoritos", favCount != null && favCount > 0 ? `${favCount} favoritado${favCount !== 1 ? "s" : ""}` : null, "favoritos"],
["bell", "Notificações", null, "notificacoes"],
["ticket", "Cupons", null, "cupons"],
["gift", "Indique e ganhe", null, "indique"],
["headset", "Ajuda e suporte", null, "ajuda"],
["info", "Sobre o Morar Bem", null, "sobre"],
];
const displayName = user?.name || "Usuário";
const initial = displayName[0].toUpperCase();
const since = user?.created_at ? new Date(user.created_at).getFullYear() : new Date().getFullYear();
return (
<>
{initial}
{displayName}
{user?.region_name || "Aracaju"} · Cliente desde {since}
onOpen("editar")} />
{/* wallet quick */}
Saldo BemCash
{money(wallet.balance)}
{/* clube */}
setEmBreve({ icon: "star", title: "Clube Morar Bem" })} style={{ width: "calc(100% - 32px)", margin: "12px 16px 0", textAlign: "left", cursor: "pointer", border: "none", borderRadius: 18, padding: "14px 16px", background: "var(--turbo-grad)", color: "#fff", display: "flex", alignItems: "center", gap: 13, boxShadow: "0 6px 18px var(--turbo-shadow)" }}>
Clube Morar Bem
{clubMember ? "Você é membro · ver benefícios" : "Assine e economize todo mês"}
{clubMember && MEMBRO }
{rows.map(([ic, lb, val, id], i) => {
const emBreveIds = { cupons: { icon: "ticket", title: "Cupons" }, indique: { icon: "gift", title: "Indique e Ganhe" } };
const isEmBreve = !!emBreveIds[id];
return (
isEmBreve ? setEmBreve(emBreveIds[id]) : onOpen(id)}
style={{ width: "100%", display: "flex", alignItems: "center", gap: 13, padding: "14px 16px", background: "none", border: "none", borderTop: i ? "1px solid var(--line)" : "none", cursor: "pointer" }}>
{lb}
{isEmBreve
? Em breve
: val ? {val} : null}
);
})}
{/* Instalar PWA — Android/Chrome */}
{canInstall && (
{ await window.mbInstallPWA(); setCanInstall(false); }}
style={{ width: "calc(100% - 32px)", margin: "16px 16px 0", display: "flex", alignItems: "center", gap: 14, padding: "15px 16px", background: "var(--surface)", border: "1.5px solid var(--accent)", borderRadius: 18, cursor: "pointer", boxShadow: "0 4px 14px rgba(63,84,217,.15)" }}>
Instalar o app
Adiciona o Morar Bem na sua tela inicial
)}
{/* Instalar PWA — iOS / Safari */}
{showIosHint && (
Toque em
Compartilhar (
) no Safari e depois em
"Adicionar à Tela de Início" .
)}
Sair da conta
Guia Morar Bem · v1.0
{emBreve && setEmBreve(null)} />}
>
);
}
Object.assign(window, { CarteiraScreen, CashbackInfo, ComprasScreen, PerfilScreen, OrderDetail, ApptDetail, StatusTimeline });