feat: implement WhatsApp bot setup flow in Assistant dashboard
This commit is contained in:
@@ -85,6 +85,10 @@ export default function OrganizationEdit() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (wapView?.botSession === "ok") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
wapServerBotGetQR({
|
wapServerBotGetQR({
|
||||||
companyId: id,
|
companyId: id,
|
||||||
sessionUser: SessionInfo.userId,
|
sessionUser: SessionInfo.userId,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import ColorLensIcon from '@mui/icons-material/ColorLens';
|
|||||||
import PinDropIcon from '@mui/icons-material/PinDrop';
|
import PinDropIcon from '@mui/icons-material/PinDrop';
|
||||||
import ReviewsIcon from '@mui/icons-material/Reviews';
|
import ReviewsIcon from '@mui/icons-material/Reviews';
|
||||||
import NotificationsIcon from '@mui/icons-material/Notifications';
|
import NotificationsIcon from '@mui/icons-material/Notifications';
|
||||||
|
import WhatsAppIcon from '@mui/icons-material/WhatsApp';
|
||||||
import API from "@services/Api.Service";
|
import API from "@services/Api.Service";
|
||||||
|
|
||||||
|
|
||||||
@@ -81,10 +82,19 @@ const AVAILABLE_FLOWS: FlowDefinition[] = [
|
|||||||
flowId: "notifications",
|
flowId: "notifications",
|
||||||
title: "Notificaciones",
|
title: "Notificaciones",
|
||||||
description: "Configura canales de notificación, recordatorios y horario silencioso para la organización o un cliente.",
|
description: "Configura canales de notificación, recordatorios y horario silencioso para la organización o un cliente.",
|
||||||
category: "Negocio",
|
category: "Comunicación",
|
||||||
keywords: ["notificaciones", "canales", "whatsapp", "email", "sistema", "recordatorio", "silencioso", "cliente"],
|
keywords: ["notificaciones", "canales", "whatsapp", "email", "sistema", "recordatorio", "silencioso", "cliente"],
|
||||||
icon: NotificationsIcon
|
icon: NotificationsIcon
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "whatsapp-bot",
|
||||||
|
flowId: "whatsapp-bot",
|
||||||
|
title: "WhatsApp",
|
||||||
|
description: "Configura tu bot de WhatsApp para enviar notificaciones automáticas a tus clientes.",
|
||||||
|
category: "Comunicación",
|
||||||
|
keywords: ["whatsapp", "bot", "notificaciones", "wap", "mensajes", "automatico", "qr"],
|
||||||
|
icon: WhatsAppIcon
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "new-service",
|
id: "new-service",
|
||||||
flowId: "onboarding-service&action=new-service",
|
flowId: "onboarding-service&action=new-service",
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import Switch from "@components/Switch/Switch";
|
|||||||
import TextTime from "@components/TextTime/TextTime";
|
import TextTime from "@components/TextTime/TextTime";
|
||||||
import { NotificationChannel, ReminderRule } from "@models/NotificationPreferences.model";
|
import { NotificationChannel, ReminderRule } from "@models/NotificationPreferences.model";
|
||||||
import { OrganizationClientView } from "@models/Clients.model";
|
import { OrganizationClientView } from "@models/Clients.model";
|
||||||
|
import WhatsAppIcon from "@mui/icons-material/WhatsApp";
|
||||||
|
|
||||||
interface StepEngineProps {
|
interface StepEngineProps {
|
||||||
flowId: string;
|
flowId: string;
|
||||||
@@ -149,6 +150,13 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm
|
|||||||
const [isUpgradeRequired, setIsUpgradeRequired] = useState(false);
|
const [isUpgradeRequired, setIsUpgradeRequired] = useState(false);
|
||||||
const [inviteFlowNotice, setInviteFlowNotice] = useState("");
|
const [inviteFlowNotice, setInviteFlowNotice] = useState("");
|
||||||
|
|
||||||
|
// State for WhatsApp Bot Flow
|
||||||
|
const [wapView, setWapView] = useState<any>(null);
|
||||||
|
const [wapAction, setWapAction] = useState<string | null>(null);
|
||||||
|
const [wapPhase, setWapPhase] = useState<string>("intro");
|
||||||
|
const [wapQr, setWapQr] = useState<string | null>(null);
|
||||||
|
const [wapError, setWapError] = useState<string | null>(null);
|
||||||
|
|
||||||
// State for Setup Collaborator Flow
|
// State for Setup Collaborator Flow
|
||||||
const [collaboratorToSetup, setCollaboratorToSetup] = useState<any | null>(null);
|
const [collaboratorToSetup, setCollaboratorToSetup] = useState<any | null>(null);
|
||||||
const [setupMissingServices, setSetupMissingServices] = useState(false);
|
const [setupMissingServices, setSetupMissingServices] = useState(false);
|
||||||
@@ -319,11 +327,16 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm
|
|||||||
setNotifClients(res?.data || []);
|
setNotifClients(res?.data || []);
|
||||||
})
|
})
|
||||||
.catch(err => console.error("Error cargando clientes de la organización:", err));
|
.catch(err => console.error("Error cargando clientes de la organización:", err));
|
||||||
|
} else if (flowId === "whatsapp-bot") {
|
||||||
|
setWapPhase("intro");
|
||||||
|
setWapView(null);
|
||||||
|
setWapQr(null);
|
||||||
|
setWapError(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (flowId === "new-collaborator" || flowId === "setup-collaborator" || flowId === "public-visibility" || flowId === "notifications" || flowId.startsWith("onboarding") || flowId === "update-schedule" || flowId === "override-schedule" || flowId === "disable-schedule") {
|
if (flowId === "new-collaborator" || flowId === "setup-collaborator" || flowId === "public-visibility" || flowId === "notifications" || flowId === "whatsapp-bot" || flowId.startsWith("onboarding") || flowId === "update-schedule" || flowId === "override-schedule" || flowId === "disable-schedule") {
|
||||||
setIsResuming(true);
|
setIsResuming(true);
|
||||||
API.post<any[]>("companies/get-by-user", { sessionUser: SessionInfo.userId })
|
API.post<any[]>("companies/get-by-user", { sessionUser: SessionInfo.userId })
|
||||||
.then(response => {
|
.then(response => {
|
||||||
@@ -344,7 +357,7 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm
|
|||||||
.finally(() => setIsResuming(false));
|
.finally(() => setIsResuming(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flowId === "onboarding-service" || flowId === "onboarding-schedule" || flowId === "setup-collaborator" || flowId === "public-visibility" || flowId === "notifications" || (flowId === "onboarding-org" && action === "new-org")) {
|
if (flowId === "onboarding-service" || flowId === "onboarding-schedule" || flowId === "setup-collaborator" || flowId === "public-visibility" || flowId === "notifications" || flowId === "whatsapp-bot" || (flowId === "onboarding-org" && action === "new-org")) {
|
||||||
setCurrentStepIndex(0);
|
setCurrentStepIndex(0);
|
||||||
}
|
}
|
||||||
}, [flowId, SessionInfo.metrics.organizationsCount, SessionInfo.userId, router]);
|
}, [flowId, SessionInfo.metrics.organizationsCount, SessionInfo.userId, router]);
|
||||||
@@ -638,6 +651,543 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// --- WhatsApp Bot Flow Helpers ---
|
||||||
|
|
||||||
|
const getImageFromBase64 = (base64: string): string => {
|
||||||
|
const base64Data = base64.includes("base64,") ? base64.split(",")[1] : base64;
|
||||||
|
const byteCharacters = atob(base64Data);
|
||||||
|
const byteArray = new Uint8Array(byteCharacters.length);
|
||||||
|
for (let i = 0; i < byteCharacters.length; i++) {
|
||||||
|
byteArray[i] = byteCharacters.charCodeAt(i);
|
||||||
|
}
|
||||||
|
const blob = new Blob([byteArray], { type: "image/png" });
|
||||||
|
return URL.createObjectURL(blob);
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadWapView = async () => {
|
||||||
|
if (!createdCompanyId) return;
|
||||||
|
setWapAction("load");
|
||||||
|
setWapError(null);
|
||||||
|
try {
|
||||||
|
const view = await API.post<any>("views/wap-server-view", {
|
||||||
|
companyId: createdCompanyId,
|
||||||
|
sessionUser: SessionInfo.userId
|
||||||
|
});
|
||||||
|
setWapView(view);
|
||||||
|
|
||||||
|
const hasPlan = SessionInfo.organizationSubscriptions?.[createdCompanyId]?.plan?.wapNotifications;
|
||||||
|
if (!hasPlan) {
|
||||||
|
setWapPhase("plan-required");
|
||||||
|
} else if (!view?.serverId) {
|
||||||
|
setWapPhase("assign-server");
|
||||||
|
} else if (!view?.exists) {
|
||||||
|
setWapPhase("create-bot");
|
||||||
|
} else if (view?.runningState && !String(view.runningState).includes("Up")) {
|
||||||
|
setWapPhase("start-bot");
|
||||||
|
} else if (view?.runningState && String(view.runningState).includes("Up") && view?.botSession !== "ok") {
|
||||||
|
setWapPhase("scan-qr");
|
||||||
|
try {
|
||||||
|
const qrData = await API.post<string>("wapserver/qr", {
|
||||||
|
companyId: createdCompanyId,
|
||||||
|
sessionUser: SessionInfo.userId
|
||||||
|
});
|
||||||
|
setWapQr(qrData);
|
||||||
|
} catch {
|
||||||
|
setWapQr(null);
|
||||||
|
}
|
||||||
|
} else if (view?.runningState && String(view.runningState).includes("Up") && view?.botSession === "ok") {
|
||||||
|
setWapPhase("bot-ready");
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error("Error loading WAP view:", err);
|
||||||
|
setWapError(err?.message || "Error al verificar el estado del bot.");
|
||||||
|
setWapPhase("error");
|
||||||
|
} finally {
|
||||||
|
setWapAction(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleWapAssignServer = async () => {
|
||||||
|
if (!createdCompanyId || wapAction) return;
|
||||||
|
setWapAction("assign");
|
||||||
|
setWapError(null);
|
||||||
|
try {
|
||||||
|
await API.post<any>("wapserver/assign", {
|
||||||
|
companyId: createdCompanyId,
|
||||||
|
sessionUser: SessionInfo.userId
|
||||||
|
});
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||||
|
await loadWapView();
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error("Error assigning server:", err);
|
||||||
|
setWapError(err?.message || "Error al asignar el servidor.");
|
||||||
|
setWapPhase("error");
|
||||||
|
} finally {
|
||||||
|
setWapAction(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleWapCreateBot = async () => {
|
||||||
|
if (!createdCompanyId || wapAction) return;
|
||||||
|
setWapAction("create");
|
||||||
|
setWapError(null);
|
||||||
|
try {
|
||||||
|
await API.post<any>("wapserver/create", {
|
||||||
|
companyId: createdCompanyId,
|
||||||
|
sessionUser: SessionInfo.userId
|
||||||
|
});
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||||
|
await loadWapView();
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error("Error creating bot:", err);
|
||||||
|
setWapError(err?.message || "Error al crear el bot.");
|
||||||
|
setWapPhase("error");
|
||||||
|
} finally {
|
||||||
|
setWapAction(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleWapStartBot = async () => {
|
||||||
|
if (!createdCompanyId || wapAction) return;
|
||||||
|
setWapAction("start");
|
||||||
|
setWapError(null);
|
||||||
|
try {
|
||||||
|
await API.post<any>("wapserver/start", {
|
||||||
|
companyId: createdCompanyId,
|
||||||
|
sessionUser: SessionInfo.userId
|
||||||
|
});
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||||
|
await loadWapView();
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error("Error starting bot:", err);
|
||||||
|
setWapError(err?.message || "Error al iniciar el bot.");
|
||||||
|
setWapPhase("error");
|
||||||
|
} finally {
|
||||||
|
setWapAction(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleWapVerifyQr = async () => {
|
||||||
|
if (!createdCompanyId) return;
|
||||||
|
setWapAction("verify");
|
||||||
|
try {
|
||||||
|
const view = await API.post<any>("views/wap-server-view", {
|
||||||
|
companyId: createdCompanyId,
|
||||||
|
sessionUser: SessionInfo.userId
|
||||||
|
});
|
||||||
|
setWapView(view);
|
||||||
|
if (view?.botSession === "ok") {
|
||||||
|
setIsSuccess(true);
|
||||||
|
} else {
|
||||||
|
setWapError("Aún no se detectó la conexión. Asegurate de haber escaneado el QR y volvé a intentar.");
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
setWapError(err?.message || "Error al verificar la conexión.");
|
||||||
|
} finally {
|
||||||
|
setWapAction(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleWapStopBot = async () => {
|
||||||
|
if (!createdCompanyId || wapAction) return;
|
||||||
|
setWapAction("stop");
|
||||||
|
setWapError(null);
|
||||||
|
try {
|
||||||
|
await API.post<any>("wapserver/stop", {
|
||||||
|
companyId: createdCompanyId,
|
||||||
|
sessionUser: SessionInfo.userId
|
||||||
|
});
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||||
|
await loadWapView();
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error("Error stopping bot:", err);
|
||||||
|
setWapError(err?.message || "Error al detener el bot.");
|
||||||
|
setWapPhase("error");
|
||||||
|
} finally {
|
||||||
|
setWapAction(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleWapDeleteBot = async () => {
|
||||||
|
if (!createdCompanyId || wapAction) return;
|
||||||
|
setWapAction("delete");
|
||||||
|
setWapError(null);
|
||||||
|
try {
|
||||||
|
await API.post<any>("wapserver/delete", {
|
||||||
|
companyId: createdCompanyId,
|
||||||
|
sessionUser: SessionInfo.userId
|
||||||
|
});
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||||
|
await loadWapView();
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error("Error deleting bot:", err);
|
||||||
|
setWapError(err?.message || "Error al eliminar el bot.");
|
||||||
|
setWapPhase("error");
|
||||||
|
} finally {
|
||||||
|
setWapAction(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleWapQuitServer = async () => {
|
||||||
|
if (!createdCompanyId || wapAction) return;
|
||||||
|
setWapAction("quit");
|
||||||
|
setWapError(null);
|
||||||
|
try {
|
||||||
|
await API.post<any>("wapserver/quit", {
|
||||||
|
companyId: createdCompanyId,
|
||||||
|
sessionUser: SessionInfo.userId
|
||||||
|
});
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||||
|
await loadWapView();
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error("Error quitting server:", err);
|
||||||
|
setWapError(err?.message || "Error al desasignar el servidor.");
|
||||||
|
setWapPhase("error");
|
||||||
|
} finally {
|
||||||
|
setWapAction(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderWhatsAppBotFlow = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* Intro */}
|
||||||
|
<QuestionCard
|
||||||
|
isActive={wapPhase === "intro"}
|
||||||
|
title="Bot de WhatsApp"
|
||||||
|
description="Vamos a configurar tu bot de WhatsApp. Esto te permitirá enviar notificaciones automáticas a tus clientes. Primero verificamos el estado actual de tu organización."
|
||||||
|
onNext={() => {
|
||||||
|
loadWapView();
|
||||||
|
}}
|
||||||
|
nextLabel="Verificar estado"
|
||||||
|
topAccessory={
|
||||||
|
<WhatsAppIcon style={{ fontSize: 48, color: "#25D366" }} />
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{null}
|
||||||
|
</QuestionCard>
|
||||||
|
|
||||||
|
{/* Plan required */}
|
||||||
|
<QuestionCard
|
||||||
|
isActive={wapPhase === "plan-required"}
|
||||||
|
title="Plan no compatible"
|
||||||
|
description="Tu plan actual no incluye notificaciones por WhatsApp. Necesitás un plan que incluya esta funcionalidad para configurar el bot."
|
||||||
|
onNext={() => {
|
||||||
|
router.push(`/landing/upgrade-plan?org=${createdCompanyId}`);
|
||||||
|
}}
|
||||||
|
nextLabel="Ver planes"
|
||||||
|
topAccessory={
|
||||||
|
<div style={{ fontSize: 48 }}>📦</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div style={{
|
||||||
|
padding: "14px",
|
||||||
|
borderRadius: "12px",
|
||||||
|
background: "rgba(255,255,255,0.05)",
|
||||||
|
border: "1px solid rgba(255,255,255,0.1)",
|
||||||
|
color: "rgba(255,255,255,0.7)",
|
||||||
|
fontSize: "13px",
|
||||||
|
lineHeight: 1.5
|
||||||
|
}}>
|
||||||
|
Actualizá tu plan para desbloquear notificaciones por WhatsApp y mejorar la comunicación con tus clientes.
|
||||||
|
</div>
|
||||||
|
</QuestionCard>
|
||||||
|
|
||||||
|
{/* Assign server */}
|
||||||
|
<QuestionCard
|
||||||
|
isActive={wapPhase === "assign-server"}
|
||||||
|
title="Asignar servidor"
|
||||||
|
description="Tu organización necesita un servidor de WhatsApp. Vamos a asignar uno automáticamente."
|
||||||
|
onNext={handleWapAssignServer}
|
||||||
|
disableNext={wapAction !== null}
|
||||||
|
nextLabel={wapAction === "assign" ? "Asignando servidor..." : "Asignar servidor"}
|
||||||
|
topAccessory={
|
||||||
|
<div style={{ fontSize: 48 }}>🖥️</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{wapAction === "assign" && (
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: "12px", padding: "20px 0" }}>
|
||||||
|
<div style={{
|
||||||
|
width: 36, height: 36, border: "3px solid rgba(255,255,255,0.2)",
|
||||||
|
borderTopColor: "var(--wine-red)", borderRadius: "50%",
|
||||||
|
animation: "spin 1s linear infinite"
|
||||||
|
}} />
|
||||||
|
<span style={{ color: "rgba(255,255,255,0.7)", fontSize: "14px" }}>Asignando servidor...</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</QuestionCard>
|
||||||
|
|
||||||
|
{/* Create bot */}
|
||||||
|
<QuestionCard
|
||||||
|
isActive={wapPhase === "create-bot"}
|
||||||
|
title="Crear bot de WhatsApp"
|
||||||
|
description={`Servidor asignado: ${wapView?.serverName || "disponible"}. Ahora vamos a crear tu bot de WhatsApp.`}
|
||||||
|
onNext={handleWapCreateBot}
|
||||||
|
disableNext={wapAction !== null}
|
||||||
|
nextLabel={wapAction === "create" ? "Creando bot..." : "Crear bot de WhatsApp"}
|
||||||
|
topAccessory={
|
||||||
|
<div style={{ fontSize: 48 }}>🤖</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: "12px", width: "100%" }}>
|
||||||
|
{wapAction === "create" && (
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: "12px", padding: "20px 0" }}>
|
||||||
|
<div style={{
|
||||||
|
width: 36, height: 36, border: "3px solid rgba(255,255,255,0.2)",
|
||||||
|
borderTopColor: "var(--wine-red)", borderRadius: "50%",
|
||||||
|
animation: "spin 1s linear infinite"
|
||||||
|
}} />
|
||||||
|
<span style={{ color: "rgba(255,255,255,0.7)", fontSize: "14px" }}>Creando bot...</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleWapQuitServer}
|
||||||
|
disabled={wapAction !== null}
|
||||||
|
style={{
|
||||||
|
display: "flex", alignItems: "center", justifyContent: "center", gap: "8px",
|
||||||
|
padding: "12px 20px", borderRadius: "10px", width: "100%",
|
||||||
|
background: "rgba(255,255,255,0.05)", border: "1px solid rgba(255,255,255,0.15)",
|
||||||
|
color: "rgba(255,255,255,0.7)", cursor: "pointer", fontSize: "14px", fontWeight: 600,
|
||||||
|
fontFamily: "inherit", transition: "all 0.2s ease", marginTop: "4px"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{wapAction === "quit" ? (
|
||||||
|
<>
|
||||||
|
<div style={{
|
||||||
|
width: 18, height: 18, border: "2px solid rgba(255,255,255,0.2)",
|
||||||
|
borderTopColor: "var(--wine-red)", borderRadius: "50%",
|
||||||
|
animation: "spin 1s linear infinite"
|
||||||
|
}} />
|
||||||
|
Desasignando servidor...
|
||||||
|
</>
|
||||||
|
) : "Desasignar servidor"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</QuestionCard>
|
||||||
|
|
||||||
|
{/* Start bot */}
|
||||||
|
<QuestionCard
|
||||||
|
isActive={wapPhase === "start-bot"}
|
||||||
|
title="Iniciar bot"
|
||||||
|
description="Tu bot de WhatsApp está creado pero detenido. Vamos a iniciarlo."
|
||||||
|
onNext={handleWapStartBot}
|
||||||
|
disableNext={wapAction !== null}
|
||||||
|
nextLabel={wapAction === "start" ? "Iniciando bot..." : "Iniciar bot"}
|
||||||
|
topAccessory={
|
||||||
|
<div style={{ fontSize: 48 }}>▶️</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: "12px", width: "100%" }}>
|
||||||
|
{wapAction === "start" && (
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: "12px", padding: "20px 0" }}>
|
||||||
|
<div style={{
|
||||||
|
width: 36, height: 36, border: "3px solid rgba(255,255,255,0.2)",
|
||||||
|
borderTopColor: "var(--wine-red)", borderRadius: "50%",
|
||||||
|
animation: "spin 1s linear infinite"
|
||||||
|
}} />
|
||||||
|
<span style={{ color: "rgba(255,255,255,0.7)", fontSize: "14px" }}>Iniciando bot...</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleWapDeleteBot}
|
||||||
|
disabled={wapAction !== null}
|
||||||
|
style={{
|
||||||
|
display: "flex", alignItems: "center", justifyContent: "center", gap: "8px",
|
||||||
|
padding: "12px 20px", borderRadius: "10px", width: "100%",
|
||||||
|
background: "rgba(255,255,255,0.05)", border: "1px solid rgba(255,255,255,0.15)",
|
||||||
|
color: "rgba(255,255,255,0.7)", cursor: "pointer", fontSize: "14px", fontWeight: 600,
|
||||||
|
fontFamily: "inherit", transition: "all 0.2s ease", marginTop: "4px"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{wapAction === "delete" ? (
|
||||||
|
<>
|
||||||
|
<div style={{
|
||||||
|
width: 18, height: 18, border: "2px solid rgba(255,255,255,0.2)",
|
||||||
|
borderTopColor: "var(--wine-red)", borderRadius: "50%",
|
||||||
|
animation: "spin 1s linear infinite"
|
||||||
|
}} />
|
||||||
|
Eliminando bot...
|
||||||
|
</>
|
||||||
|
) : "Eliminar bot"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</QuestionCard>
|
||||||
|
|
||||||
|
{/* Scan QR */}
|
||||||
|
<QuestionCard
|
||||||
|
isActive={wapPhase === "scan-qr"}
|
||||||
|
title="Escanear código QR"
|
||||||
|
description="Tu bot está funcionando. Ahora necesitás vincular tu número de WhatsApp escaneando este código QR con tu teléfono."
|
||||||
|
onNext={handleWapVerifyQr}
|
||||||
|
disableNext={wapAction !== null}
|
||||||
|
nextLabel={wapAction === "verify" ? "Verificando..." : "Ya escaneé el código"}
|
||||||
|
topAccessory={
|
||||||
|
<div style={{ fontSize: 48 }}>📱</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: "16px", width: "100%" }}>
|
||||||
|
{wapQr ? (
|
||||||
|
<div style={{
|
||||||
|
padding: "16px",
|
||||||
|
borderRadius: "12px",
|
||||||
|
background: "white",
|
||||||
|
boxShadow: "0 4px 20px rgba(0,0,0,0.3)"
|
||||||
|
}}>
|
||||||
|
<img
|
||||||
|
src={getImageFromBase64(wapQr)}
|
||||||
|
width={250}
|
||||||
|
height={250}
|
||||||
|
alt="Escaneá el código QR"
|
||||||
|
style={{ display: "block" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div style={{
|
||||||
|
width: 250, height: 250, borderRadius: "12px",
|
||||||
|
background: "rgba(255,255,255,0.05)",
|
||||||
|
border: "2px dashed rgba(255,255,255,0.2)",
|
||||||
|
display: "flex", alignItems: "center", justifyContent: "center",
|
||||||
|
color: "rgba(255,255,255,0.5)", fontSize: "14px"
|
||||||
|
}}>
|
||||||
|
Cargando QR...
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div style={{
|
||||||
|
padding: "12px 16px",
|
||||||
|
borderRadius: "10px",
|
||||||
|
background: "rgba(255,255,255,0.05)",
|
||||||
|
border: "1px solid rgba(255,255,255,0.1)",
|
||||||
|
color: "rgba(255,255,255,0.7)",
|
||||||
|
fontSize: "13px",
|
||||||
|
lineHeight: 1.5,
|
||||||
|
textAlign: "center",
|
||||||
|
maxWidth: "350px"
|
||||||
|
}}>
|
||||||
|
Abrí WhatsApp en tu teléfono → <strong style={{ color: "white" }}>Configuración</strong> → <strong style={{ color: "white" }}>Dispositivos vinculados</strong> → <strong style={{ color: "white" }}>Vincular dispositivo</strong>
|
||||||
|
</div>
|
||||||
|
{wapError && (
|
||||||
|
<div style={{
|
||||||
|
padding: "12px 16px",
|
||||||
|
borderRadius: "10px",
|
||||||
|
background: "rgba(255,60,60,0.1)",
|
||||||
|
border: "1px solid rgba(255,60,60,0.3)",
|
||||||
|
color: "#ff9999",
|
||||||
|
fontSize: "13px",
|
||||||
|
textAlign: "center",
|
||||||
|
maxWidth: "350px"
|
||||||
|
}}>
|
||||||
|
{wapError}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleWapStopBot}
|
||||||
|
disabled={wapAction !== null}
|
||||||
|
style={{
|
||||||
|
display: "flex", alignItems: "center", justifyContent: "center", gap: "8px",
|
||||||
|
padding: "12px 20px", borderRadius: "10px", width: "100%",
|
||||||
|
background: "rgba(255,255,255,0.05)", border: "1px solid rgba(255,255,255,0.15)",
|
||||||
|
color: "rgba(255,255,255,0.7)", cursor: "pointer", fontSize: "14px", fontWeight: 600,
|
||||||
|
fontFamily: "inherit", transition: "all 0.2s ease", marginTop: "4px"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{wapAction === "stop" ? (
|
||||||
|
<>
|
||||||
|
<div style={{
|
||||||
|
width: 18, height: 18, border: "2px solid rgba(255,255,255,0.2)",
|
||||||
|
borderTopColor: "var(--wine-red)", borderRadius: "50%",
|
||||||
|
animation: "spin 1s linear infinite"
|
||||||
|
}} />
|
||||||
|
Deteniendo bot...
|
||||||
|
</>
|
||||||
|
) : "Detener bot"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</QuestionCard>
|
||||||
|
|
||||||
|
{/* Bot ready */}
|
||||||
|
<QuestionCard
|
||||||
|
isActive={wapPhase === "bot-ready"}
|
||||||
|
title="¡Bot funcionando!"
|
||||||
|
description="Tu bot de WhatsApp está conectado y funcionando correctamente. Ya puede enviar notificaciones a tus clientes."
|
||||||
|
onNext={() => setIsSuccess(true)}
|
||||||
|
nextLabel="Finalizar"
|
||||||
|
topAccessory={
|
||||||
|
<div style={{ fontSize: 48 }}>✅</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: "12px", width: "100%" }}>
|
||||||
|
<div style={{
|
||||||
|
padding: "14px", borderRadius: "12px",
|
||||||
|
background: "rgba(74,222,128,0.08)", border: "1px solid rgba(74,222,128,0.2)",
|
||||||
|
color: "#4ade80", fontSize: "13px", textAlign: "center", fontWeight: 600
|
||||||
|
}}>
|
||||||
|
Sesión de WhatsApp activa — Bot operativo
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleWapStopBot}
|
||||||
|
disabled={wapAction !== null}
|
||||||
|
style={{
|
||||||
|
display: "flex", alignItems: "center", justifyContent: "center", gap: "8px",
|
||||||
|
padding: "12px 20px", borderRadius: "10px", width: "100%",
|
||||||
|
background: "rgba(255,255,255,0.05)", border: "1px solid rgba(255,255,255,0.15)",
|
||||||
|
color: "rgba(255,255,255,0.7)", cursor: "pointer", fontSize: "14px", fontWeight: 600,
|
||||||
|
fontFamily: "inherit", transition: "all 0.2s ease", marginTop: "4px"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{wapAction === "stop" ? (
|
||||||
|
<>
|
||||||
|
<div style={{
|
||||||
|
width: 18, height: 18, border: "2px solid rgba(255,255,255,0.2)",
|
||||||
|
borderTopColor: "var(--wine-red)", borderRadius: "50%",
|
||||||
|
animation: "spin 1s linear infinite"
|
||||||
|
}} />
|
||||||
|
Deteniendo bot...
|
||||||
|
</>
|
||||||
|
) : "Detener bot"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</QuestionCard>
|
||||||
|
|
||||||
|
{/* Error */}
|
||||||
|
<QuestionCard
|
||||||
|
isActive={wapPhase === "error"}
|
||||||
|
title="Error"
|
||||||
|
description={wapError || "Ocurrió un error inesperado. Por favor, intentá nuevamente."}
|
||||||
|
onNext={() => {
|
||||||
|
setWapPhase("intro");
|
||||||
|
setWapError(null);
|
||||||
|
}}
|
||||||
|
nextLabel="Reintentar"
|
||||||
|
topAccessory={
|
||||||
|
<div style={{ fontSize: 48 }}>⚠️</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div style={{
|
||||||
|
padding: "14px",
|
||||||
|
borderRadius: "12px",
|
||||||
|
background: "rgba(255,60,60,0.1)",
|
||||||
|
border: "1px solid rgba(255,60,60,0.3)",
|
||||||
|
color: "#ff9999",
|
||||||
|
fontSize: "13px",
|
||||||
|
lineHeight: 1.5
|
||||||
|
}}>
|
||||||
|
Si el problema persiste, contactá al soporte de TurnosXpress.
|
||||||
|
</div>
|
||||||
|
</QuestionCard>
|
||||||
|
|
||||||
|
<style>{`
|
||||||
|
@keyframes spin {
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const renderOnboardingOrgFlow = () => {
|
const renderOnboardingOrgFlow = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -3945,6 +4495,7 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm
|
|||||||
if (flowId === "new-collaborator") return renderNewCollaboratorFlow();
|
if (flowId === "new-collaborator") return renderNewCollaboratorFlow();
|
||||||
if (flowId === "public-visibility") return renderPublicVisibilityFlow();
|
if (flowId === "public-visibility") return renderPublicVisibilityFlow();
|
||||||
if (flowId === "notifications") return renderNotificationsFlow();
|
if (flowId === "notifications") return renderNotificationsFlow();
|
||||||
|
if (flowId === "whatsapp-bot") return renderWhatsAppBotFlow();
|
||||||
if (flowId === "disable-schedule") return renderDisableScheduleFlow();
|
if (flowId === "disable-schedule") return renderDisableScheduleFlow();
|
||||||
if (flowId === "override-schedule") return renderOverrideScheduleFlow();
|
if (flowId === "override-schedule") return renderOverrideScheduleFlow();
|
||||||
if (flowId === "update-schedule") return renderUpdateScheduleFlow();
|
if (flowId === "update-schedule") return renderUpdateScheduleFlow();
|
||||||
@@ -3964,6 +4515,7 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm
|
|||||||
if (flowId === "new-collaborator") return "El colaborador fue invitado exitosamente.";
|
if (flowId === "new-collaborator") return "El colaborador fue invitado exitosamente.";
|
||||||
if (flowId === "setup-collaborator") return "El colaborador ha sido configurado exitosamente y ya puede comenzar a recibir reservas.";
|
if (flowId === "setup-collaborator") return "El colaborador ha sido configurado exitosamente y ya puede comenzar a recibir reservas.";
|
||||||
if (flowId === "notifications") return "La configuración de notificaciones se ha guardado exitosamente.";
|
if (flowId === "notifications") return "La configuración de notificaciones se ha guardado exitosamente.";
|
||||||
|
if (flowId === "whatsapp-bot") return "Tu bot de WhatsApp está configurado y funcionando. Tus clientes recibirán notificaciones automáticas por WhatsApp.";
|
||||||
if (action) return "Tus cambios se han guardado exitosamente.";
|
if (action) return "Tus cambios se han guardado exitosamente.";
|
||||||
return "Has configurado exitosamente tu negocio. Todo está listo para que empieces a recibir reservas y gestionar tu agenda como un profesional.";
|
return "Has configurado exitosamente tu negocio. Todo está listo para que empieces a recibir reservas y gestionar tu agenda como un profesional.";
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user