From dc020d94d21e1ce0edb02ae676db6249ed577fb3 Mon Sep 17 00:00:00 2001 From: Horacio Daniel Ros Date: Mon, 27 Jul 2026 21:50:59 -0300 Subject: [PATCH] feat: add public link and QR code generation flow to assistant --- .../AssistantHome/AssistantHome.tsx | 10 ++ .../components/StepEngine/StepEngine.tsx | 105 +++++++++++++++++- 2 files changed, 113 insertions(+), 2 deletions(-) diff --git a/txclient/src/app/admin/assistant/components/AssistantHome/AssistantHome.tsx b/txclient/src/app/admin/assistant/components/AssistantHome/AssistantHome.tsx index 6a46422..65c5051 100644 --- a/txclient/src/app/admin/assistant/components/AssistantHome/AssistantHome.tsx +++ b/txclient/src/app/admin/assistant/components/AssistantHome/AssistantHome.tsx @@ -18,6 +18,7 @@ import PinDropIcon from '@mui/icons-material/PinDrop'; import ReviewsIcon from '@mui/icons-material/Reviews'; import NotificationsIcon from '@mui/icons-material/Notifications'; import WhatsAppIcon from '@mui/icons-material/WhatsApp'; +import QrCode2Icon from '@mui/icons-material/QrCode2'; import API from "@services/Api.Service"; @@ -77,6 +78,15 @@ const AVAILABLE_FLOWS: FlowDefinition[] = [ keywords: ["opiniones", "puntuaciones", "calificaciones", "profesionales", "visibilidad", "reseñas", "publico"], icon: ReviewsIcon }, + { + id: "public-link", + flowId: "public-link", + title: "Link Público y QR", + description: "Obtén el enlace público de tu organización y un código QR para compartirlo.", + category: "Negocio", + keywords: ["link", "enlace", "url", "slug", "qr", "codigo qr", "publico", "reservas"], + icon: QrCode2Icon + }, { id: "notifications", flowId: "notifications", diff --git a/txclient/src/app/admin/assistant/components/StepEngine/StepEngine.tsx b/txclient/src/app/admin/assistant/components/StepEngine/StepEngine.tsx index e5f83c1..cef602b 100644 --- a/txclient/src/app/admin/assistant/components/StepEngine/StepEngine.tsx +++ b/txclient/src/app/admin/assistant/components/StepEngine/StepEngine.tsx @@ -27,6 +27,8 @@ import TextTime from "@components/TextTime/TextTime"; import { NotificationChannel, ReminderRule } from "@models/NotificationPreferences.model"; import { OrganizationClientView } from "@models/Clients.model"; import WhatsAppIcon from "@mui/icons-material/WhatsApp"; +import QrCode2Icon from "@mui/icons-material/QrCode2"; +import { getSlug } from "@core/app/helpers/Slug"; interface StepEngineProps { flowId: string; @@ -115,6 +117,7 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm const [orgShowPublicProfessionals, setOrgShowPublicProfessionals] = useState(true); const [servicesShowPublicScores, setServicesShowPublicScores] = useState(true); const [servicesShowPublicOpinions, setServicesShowPublicOpinions] = useState(true); + const [publicLinkCopied, setPublicLinkCopied] = useState(false); // State for Notifications Flow const [notificationsScope, setNotificationsScope] = useState<"org" | "client" | null>(null); @@ -285,6 +288,9 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm setServicesShowPublicScores(services.every(service => service.showPublicScores !== false)); setServicesShowPublicOpinions(services.every(service => service.showPublicOpinions !== false)); }).catch(console.error); + } else if (flowId === "public-link") { + setCurrentStepIndex(0); + setPublicLinkCopied(false); } else if (flowId === "setup-collaborator") { setCurrentStepIndex(0); if (initialEmployeeId) { @@ -336,7 +342,7 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm }; React.useEffect(() => { - 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") { + if (flowId === "new-collaborator" || flowId === "setup-collaborator" || flowId === "public-visibility" || flowId === "public-link" || flowId === "notifications" || flowId === "whatsapp-bot" || flowId.startsWith("onboarding") || flowId === "update-schedule" || flowId === "override-schedule" || flowId === "disable-schedule") { setIsResuming(true); API.post("companies/get-by-user", { sessionUser: SessionInfo.userId }) .then(response => { @@ -357,7 +363,7 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm .finally(() => setIsResuming(false)); } - if (flowId === "onboarding-service" || flowId === "onboarding-schedule" || flowId === "setup-collaborator" || flowId === "public-visibility" || flowId === "notifications" || flowId === "whatsapp-bot" || (flowId === "onboarding-org" && action === "new-org")) { + if (flowId === "onboarding-service" || flowId === "onboarding-schedule" || flowId === "setup-collaborator" || flowId === "public-visibility" || flowId === "public-link" || flowId === "notifications" || flowId === "whatsapp-bot" || (flowId === "onboarding-org" && action === "new-org")) { setCurrentStepIndex(0); } }, [flowId, SessionInfo.metrics.organizationsCount, SessionInfo.userId, router]); @@ -4021,6 +4027,100 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm ); }; + const getSelectedOrganizationSlug = () => { + if (!selectedOrganization) return ""; + return selectedOrganization.slug || (selectedOrganization.name ? getSlug(selectedOrganization.name) : ""); + }; + + const getPublicLink = () => { + const slug = getSelectedOrganizationSlug(); + const publicBaseUrl = process.env.NEXT_PUBLIC_BASE_URL || "https://turnosxpress.com.ar"; + return slug ? `${publicBaseUrl.replace(/\/$/, "")}/${slug}` : ""; + }; + + const getPublicLinkQrUrl = () => { + const publicLink = getPublicLink(); + return publicLink ? `https://api.qrserver.com/v1/create-qr-code/?size=260x260&format=png&data=${encodeURIComponent(publicLink)}` : ""; + }; + + const handleCopyPublicLink = async () => { + const publicLink = getPublicLink(); + if (!publicLink) return; + + try { + await navigator.clipboard.writeText(publicLink); + setPublicLinkCopied(true); + window.setTimeout(() => setPublicLinkCopied(false), 2000); + } catch (error) { + console.error("Error copiando el link público:", error); + alert("No se pudo copiar el link. Podés seleccionarlo y copiarlo manualmente."); + } + }; + + const renderPublicLinkFlow = () => { + const publicLink = getPublicLink(); + const publicLinkQrUrl = getPublicLinkQrUrl(); + const orgSlug = getSelectedOrganizationSlug(); + + return ( + router.push("/admin/assistant")} + nextLabel="Volver al asistente" + topAccessory={} + > +
+
+ Organización: {selectedOrganization?.name || "Sin organización seleccionada"} + {orgSlug && ( +
+ Slug público: {orgSlug} +
+ )} +
+ + {publicLink ? ( + <> +
+ Link público + + {publicLink} + +
+ +
+ + + + Descargar QR + +
+ +
+
+ {`Código +
+ + Este QR apunta al mismo link público. Si cambiás el nombre o slug de la organización, generá y compartí el QR actualizado. + +
+ + ) : ( +
+ No encontramos un slug para esta organización. Revisá el nombre de la organización en la configuración del perfil y volvé a intentar. +
+ )} +
+
+ ); + }; + const NOTIF_CHANNELS: { key: NotificationChannel; label: string; description: string }[] = [ { key: "whatsapp", label: "WhatsApp", description: "Mensajes automáticos por WhatsApp." }, { key: "email", label: "Email", description: "Correos electrónicos al cliente." }, @@ -4494,6 +4594,7 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm if (flowId === "setup-collaborator") return renderSetupCollaboratorFlow(); if (flowId === "new-collaborator") return renderNewCollaboratorFlow(); if (flowId === "public-visibility") return renderPublicVisibilityFlow(); + if (flowId === "public-link") return renderPublicLinkFlow(); if (flowId === "notifications") return renderNotificationsFlow(); if (flowId === "whatsapp-bot") return renderWhatsAppBotFlow(); if (flowId === "disable-schedule") return renderDisableScheduleFlow();