feat: add public visibility configuration flow for organizations and services
This commit is contained in:
@@ -15,6 +15,7 @@ import EventRepeatIcon from "@mui/icons-material/EventRepeat";
|
|||||||
import CalendarMonthIcon from '@mui/icons-material/CalendarMonth';
|
import CalendarMonthIcon from '@mui/icons-material/CalendarMonth';
|
||||||
import ColorLensIcon from '@mui/icons-material/ColorLens';
|
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 API from "@services/Api.Service";
|
import API from "@services/Api.Service";
|
||||||
|
|
||||||
|
|
||||||
@@ -65,6 +66,15 @@ const AVAILABLE_FLOWS: FlowDefinition[] = [
|
|||||||
keywords: ["telefono", "contacto", "email", "whatsapp", "redes", "celular"],
|
keywords: ["telefono", "contacto", "email", "whatsapp", "redes", "celular"],
|
||||||
icon: AddCircleOutlineIcon
|
icon: AddCircleOutlineIcon
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "public-visibility",
|
||||||
|
flowId: "public-visibility",
|
||||||
|
title: "Visibilidad Pública",
|
||||||
|
description: "Configura opiniones, puntuaciones y profesionales visibles para tus clientes.",
|
||||||
|
category: "Negocio",
|
||||||
|
keywords: ["opiniones", "puntuaciones", "calificaciones", "profesionales", "visibilidad", "reseñas", "publico"],
|
||||||
|
icon: ReviewsIcon
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "new-service",
|
id: "new-service",
|
||||||
flowId: "onboarding-service&action=new-service",
|
flowId: "onboarding-service&action=new-service",
|
||||||
|
|||||||
@@ -100,6 +100,15 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm
|
|||||||
|
|
||||||
const [previewServices, setPreviewServices] = useState<any[]>([]);
|
const [previewServices, setPreviewServices] = useState<any[]>([]);
|
||||||
|
|
||||||
|
// State for Public Visibility Flow
|
||||||
|
const [selectedOrganization, setSelectedOrganization] = useState<any | null>(null);
|
||||||
|
const [publicVisibilityServices, setPublicVisibilityServices] = useState<any[]>([]);
|
||||||
|
const [orgShowPublicScores, setOrgShowPublicScores] = useState(true);
|
||||||
|
const [orgShowPublicOpinions, setOrgShowPublicOpinions] = useState(true);
|
||||||
|
const [orgShowPublicProfessionals, setOrgShowPublicProfessionals] = useState(true);
|
||||||
|
const [servicesShowPublicScores, setServicesShowPublicScores] = useState(true);
|
||||||
|
const [servicesShowPublicOpinions, setServicesShowPublicOpinions] = useState(true);
|
||||||
|
|
||||||
// State for standalone update schedule flow
|
// State for standalone update schedule flow
|
||||||
const [scheduleScope, setScheduleScope] = useState<"me" | "specific" | "all" | null>(null);
|
const [scheduleScope, setScheduleScope] = useState<"me" | "specific" | "all" | null>(null);
|
||||||
const [targetEmployeeId, setTargetEmployeeId] = useState<string | null>(null);
|
const [targetEmployeeId, setTargetEmployeeId] = useState<string | null>(null);
|
||||||
@@ -138,6 +147,7 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm
|
|||||||
|
|
||||||
const loadOrganizationContext = (org: any) => {
|
const loadOrganizationContext = (org: any) => {
|
||||||
setCreatedCompanyId(org.id || org._id);
|
setCreatedCompanyId(org.id || org._id);
|
||||||
|
setSelectedOrganization(org);
|
||||||
setOrgName(org.name || "");
|
setOrgName(org.name || "");
|
||||||
setCategoryId(org.categoryId || null);
|
setCategoryId(org.categoryId || null);
|
||||||
setOrgDescription(org.description || "");
|
setOrgDescription(org.description || "");
|
||||||
@@ -156,6 +166,9 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm
|
|||||||
setHeatMapFraction(org.heatMapFraction || 60);
|
setHeatMapFraction(org.heatMapFraction || 60);
|
||||||
setAppointmentTime(org.appointmentTime || 24);
|
setAppointmentTime(org.appointmentTime || 24);
|
||||||
setCancellationTime(org.cancellationTime || 12);
|
setCancellationTime(org.cancellationTime || 12);
|
||||||
|
setOrgShowPublicScores(org.showPublicScores !== undefined ? org.showPublicScores : true);
|
||||||
|
setOrgShowPublicOpinions(org.showPublicOpinions !== undefined ? org.showPublicOpinions : true);
|
||||||
|
setOrgShowPublicProfessionals(org.showPublicProfessionals !== undefined ? org.showPublicProfessionals : true);
|
||||||
if (org.headerColor) setHeaderColor(org.headerColor);
|
if (org.headerColor) setHeaderColor(org.headerColor);
|
||||||
if (org.headerFontColor) setHeaderFontColor(org.headerFontColor);
|
if (org.headerFontColor) setHeaderFontColor(org.headerFontColor);
|
||||||
|
|
||||||
@@ -237,6 +250,15 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm
|
|||||||
}).catch(console.error);
|
}).catch(console.error);
|
||||||
} else if (flowId === "new-collaborator") {
|
} else if (flowId === "new-collaborator") {
|
||||||
setCurrentStepIndex(1);
|
setCurrentStepIndex(1);
|
||||||
|
} else if (flowId === "public-visibility") {
|
||||||
|
setCurrentStepIndex(0);
|
||||||
|
API.post<any[]>("services/get-by-company", { companyId: org.id || org._id })
|
||||||
|
.then(res => {
|
||||||
|
const services = res || [];
|
||||||
|
setPublicVisibilityServices(services);
|
||||||
|
setServicesShowPublicScores(services.every(service => service.showPublicScores !== false));
|
||||||
|
setServicesShowPublicOpinions(services.every(service => service.showPublicOpinions !== false));
|
||||||
|
}).catch(console.error);
|
||||||
} else if (flowId === "setup-collaborator") {
|
} else if (flowId === "setup-collaborator") {
|
||||||
setCurrentStepIndex(0);
|
setCurrentStepIndex(0);
|
||||||
if (initialEmployeeId) {
|
if (initialEmployeeId) {
|
||||||
@@ -264,7 +286,7 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm
|
|||||||
};
|
};
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (flowId === "new-collaborator" || flowId === "setup-collaborator" || flowId.startsWith("onboarding") || flowId === "update-schedule" || flowId === "override-schedule" || flowId === "disable-schedule") {
|
if (flowId === "new-collaborator" || flowId === "setup-collaborator" || flowId === "public-visibility" || 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 => {
|
||||||
@@ -285,7 +307,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 === "onboarding-org" && action === "new-org")) {
|
if (flowId === "onboarding-service" || flowId === "onboarding-schedule" || flowId === "setup-collaborator" || flowId === "public-visibility" || (flowId === "onboarding-org" && action === "new-org")) {
|
||||||
setCurrentStepIndex(0);
|
setCurrentStepIndex(0);
|
||||||
}
|
}
|
||||||
}, [flowId, SessionInfo.metrics.organizationsCount, SessionInfo.userId, router]);
|
}, [flowId, SessionInfo.metrics.organizationsCount, SessionInfo.userId, router]);
|
||||||
@@ -3257,6 +3279,116 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
const isSelectedOrganizationPaidPlan = () => {
|
||||||
|
if (!createdCompanyId) return false;
|
||||||
|
return (SessionInfo.organizationSubscriptions?.[createdCompanyId]?.plan?.price || 0) > 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSavePublicVisibility = async () => {
|
||||||
|
if (!createdCompanyId || isCreating) return;
|
||||||
|
|
||||||
|
setIsCreating(true);
|
||||||
|
try {
|
||||||
|
await API.post<any>("companies/update", {
|
||||||
|
id: createdCompanyId,
|
||||||
|
showPublicScores: orgShowPublicScores,
|
||||||
|
showPublicOpinions: orgShowPublicOpinions,
|
||||||
|
showPublicProfessionals: orgShowPublicProfessionals,
|
||||||
|
sessionUser: SessionInfo.userId
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(publicVisibilityServices.map(service =>
|
||||||
|
API.post<any>("services/update", {
|
||||||
|
id: service.id || service._id,
|
||||||
|
companyId: createdCompanyId,
|
||||||
|
name: service.name,
|
||||||
|
description: service.description,
|
||||||
|
originalPrice: service.originalPrice,
|
||||||
|
limit: service.limit,
|
||||||
|
length: service.length,
|
||||||
|
color: service.color,
|
||||||
|
fontColor: service.fontColor,
|
||||||
|
fontShadowColor: service.fontShadowColor,
|
||||||
|
sessionUser: SessionInfo.userId,
|
||||||
|
showPublicScores: servicesShowPublicScores,
|
||||||
|
showPublicOpinions: servicesShowPublicOpinions
|
||||||
|
})
|
||||||
|
));
|
||||||
|
|
||||||
|
setIsSuccess(true);
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("Error actualizando visibilidad pública:", error);
|
||||||
|
alert(error?.message || "Hubo un error al guardar la visibilidad pública.");
|
||||||
|
} finally {
|
||||||
|
setIsCreating(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderVisibilityCheckbox = (label: string, description: string, checked: boolean, onChange: (checked: boolean) => void) => (
|
||||||
|
<label style={{
|
||||||
|
display: 'flex', gap: '12px', alignItems: 'flex-start', padding: '14px', borderRadius: '12px',
|
||||||
|
background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', cursor: 'pointer'
|
||||||
|
}}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={checked}
|
||||||
|
onChange={(e) => onChange(e.target.checked)}
|
||||||
|
style={{ width: '18px', height: '18px', marginTop: '2px', accentColor: 'var(--wine-red)', cursor: 'pointer' }}
|
||||||
|
/>
|
||||||
|
<span style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
|
||||||
|
<span style={{ color: 'white', fontWeight: 700 }}>{label}</span>
|
||||||
|
<span style={{ color: 'rgba(255,255,255,0.65)', fontSize: '13px', lineHeight: 1.4 }}>{description}</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderPublicVisibilityFlow = () => {
|
||||||
|
if (!isSelectedOrganizationPaidPlan()) {
|
||||||
|
return (
|
||||||
|
<QuestionCard
|
||||||
|
isActive={true}
|
||||||
|
title="Esta configuración requiere un plan pago"
|
||||||
|
description="La visibilidad pública de opiniones, puntuaciones y profesionales está disponible para organizaciones con plan pago activo. El flujo seguirá visible para que puedas volver cuando actualices tu plan."
|
||||||
|
onNext={() => router.push(createdCompanyId ? `/landing/upgrade-plan?org=${createdCompanyId}` : "/landing/pricing")}
|
||||||
|
nextLabel="Ver planes"
|
||||||
|
>
|
||||||
|
<div style={{
|
||||||
|
color: 'rgba(255,255,255,0.75)', textAlign: 'center', padding: '18px', borderRadius: '16px',
|
||||||
|
background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', lineHeight: 1.5
|
||||||
|
}}>
|
||||||
|
Estás configurando {selectedOrganization?.name ? <strong>{selectedOrganization.name}</strong> : "esta organización"}. Para cambiar qué opiniones, puntuaciones y profesionales se muestran públicamente, el dueño de esta organización necesita un plan pago.
|
||||||
|
</div>
|
||||||
|
</QuestionCard>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<QuestionCard
|
||||||
|
isActive={true}
|
||||||
|
title="Configura la visibilidad pública"
|
||||||
|
description="Elige qué información verán tus clientes en la página pública de la organización y sus servicios. Si un valor nunca fue configurado, se considera visible por defecto."
|
||||||
|
onNext={handleSavePublicVisibility}
|
||||||
|
disableNext={isCreating}
|
||||||
|
nextLabel={isCreating ? "Guardando..." : "Guardar configuración"}
|
||||||
|
>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '18px', width: '100%' }}>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
|
||||||
|
<h3 style={{ color: 'white', margin: 0, fontSize: '16px' }}>Organización</h3>
|
||||||
|
{renderVisibilityCheckbox("Mostrar puntuación pública", "Permite que los clientes vean la puntuación promedio de la organización.", orgShowPublicScores, setOrgShowPublicScores)}
|
||||||
|
{renderVisibilityCheckbox("Mostrar opiniones públicas", "Permite que los clientes vean las opiniones recibidas por la organización.", orgShowPublicOpinions, setOrgShowPublicOpinions)}
|
||||||
|
{renderVisibilityCheckbox("Mostrar profesionales públicos", "Permite que los clientes vean la lista de profesionales de la organización.", orgShowPublicProfessionals, setOrgShowPublicProfessionals)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
|
||||||
|
<h3 style={{ color: 'white', margin: 0, fontSize: '16px' }}>Servicios</h3>
|
||||||
|
{renderVisibilityCheckbox("Mostrar puntuación pública de servicios", "Aplica este valor a todos los servicios de la organización.", servicesShowPublicScores, setServicesShowPublicScores)}
|
||||||
|
{renderVisibilityCheckbox("Mostrar opiniones públicas de servicios", "Aplica este valor a todos los servicios de la organización.", servicesShowPublicOpinions, setServicesShowPublicOpinions)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</QuestionCard>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const renderFlow = () => {
|
const renderFlow = () => {
|
||||||
if (allOrganizations.length > 1 && !createdCompanyId && action !== "new-org") {
|
if (allOrganizations.length > 1 && !createdCompanyId && action !== "new-org") {
|
||||||
return (
|
return (
|
||||||
@@ -3294,6 +3426,7 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm
|
|||||||
|
|
||||||
if (flowId === "setup-collaborator") return renderSetupCollaboratorFlow();
|
if (flowId === "setup-collaborator") return renderSetupCollaboratorFlow();
|
||||||
if (flowId === "new-collaborator") return renderNewCollaboratorFlow();
|
if (flowId === "new-collaborator") return renderNewCollaboratorFlow();
|
||||||
|
if (flowId === "public-visibility") return renderPublicVisibilityFlow();
|
||||||
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();
|
||||||
|
|||||||
Reference in New Issue
Block a user