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 ColorLensIcon from '@mui/icons-material/ColorLens';
|
||||
import PinDropIcon from '@mui/icons-material/PinDrop';
|
||||
import ReviewsIcon from '@mui/icons-material/Reviews';
|
||||
import API from "@services/Api.Service";
|
||||
|
||||
|
||||
@@ -65,6 +66,15 @@ const AVAILABLE_FLOWS: FlowDefinition[] = [
|
||||
keywords: ["telefono", "contacto", "email", "whatsapp", "redes", "celular"],
|
||||
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",
|
||||
flowId: "onboarding-service&action=new-service",
|
||||
|
||||
@@ -100,6 +100,15 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm
|
||||
|
||||
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
|
||||
const [scheduleScope, setScheduleScope] = useState<"me" | "specific" | "all" | 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) => {
|
||||
setCreatedCompanyId(org.id || org._id);
|
||||
setSelectedOrganization(org);
|
||||
setOrgName(org.name || "");
|
||||
setCategoryId(org.categoryId || null);
|
||||
setOrgDescription(org.description || "");
|
||||
@@ -156,6 +166,9 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm
|
||||
setHeatMapFraction(org.heatMapFraction || 60);
|
||||
setAppointmentTime(org.appointmentTime || 24);
|
||||
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.headerFontColor) setHeaderFontColor(org.headerFontColor);
|
||||
|
||||
@@ -237,6 +250,15 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm
|
||||
}).catch(console.error);
|
||||
} else if (flowId === "new-collaborator") {
|
||||
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") {
|
||||
setCurrentStepIndex(0);
|
||||
if (initialEmployeeId) {
|
||||
@@ -264,7 +286,7 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm
|
||||
};
|
||||
|
||||
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);
|
||||
API.post<any[]>("companies/get-by-user", { sessionUser: SessionInfo.userId })
|
||||
.then(response => {
|
||||
@@ -285,7 +307,7 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm
|
||||
.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);
|
||||
}
|
||||
}, [flowId, SessionInfo.metrics.organizationsCount, SessionInfo.userId, router]);
|
||||
@@ -3257,6 +3279,116 @@ export default function StepEngine({ flowId, action, initialCompanyId, initialEm
|
||||
|
||||
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 = () => {
|
||||
if (allOrganizations.length > 1 && !createdCompanyId && action !== "new-org") {
|
||||
return (
|
||||
@@ -3294,6 +3426,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 === "disable-schedule") return renderDisableScheduleFlow();
|
||||
if (flowId === "override-schedule") return renderOverrideScheduleFlow();
|
||||
if (flowId === "update-schedule") return renderUpdateScheduleFlow();
|
||||
|
||||
Reference in New Issue
Block a user