Keep editorial calendars private to each tenant.
Stop seeding Agua Miia’s October plan for every client and restrict the portal publish action to that tenant only. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -30,6 +30,20 @@ export async function POST() {
|
||||
);
|
||||
}
|
||||
|
||||
const t = await restGet<{ slug?: string; name?: string }[]>(
|
||||
`/rest/v1/tenants?id=eq.${encodeURIComponent(ctx.tenantId)}&select=slug,name`
|
||||
);
|
||||
const slug = t.data?.[0]?.slug || '';
|
||||
if (slug !== 'agua-miia') {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error:
|
||||
'Este botón solo carga el plan de octubre de Agua Miia en su propio portal. No publica en Instagram ni copia el calendario a otros clientes.',
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const headers = serviceHeaders();
|
||||
if (!headers) {
|
||||
return NextResponse.json({ error: 'no service role' }, { status: 500 });
|
||||
|
||||
@@ -34,8 +34,13 @@ export default function PublishRrssButton() {
|
||||
className="inline-flex items-center justify-center min-h-[52px] px-6 rounded-xl border border-emerald-500/40 bg-emerald-500/15 text-emerald-300 text-sm font-bold hover:bg-emerald-500/25 disabled:opacity-50"
|
||||
>
|
||||
<Upload className="w-4 h-4 mr-2" />
|
||||
{busy ? 'Publicando…' : 'Publicar mes en portal'}
|
||||
{busy ? 'Publicando…' : 'Dejar octubre visible para el cliente'}
|
||||
</button>
|
||||
<p className="text-[11px] text-gray-500 max-w-xs leading-relaxed">
|
||||
No publica en Instagram ni Facebook. Solo guarda el plan de este mes en
|
||||
el portal de <strong>Agua Miia</strong> para que el cliente lo vea en
|
||||
Calendario.
|
||||
</p>
|
||||
{msg ? <p className="text-[11px] text-gray-400">{msg}</p> : null}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -51,6 +51,15 @@ export default async function CalendarioPage({
|
||||
);
|
||||
}
|
||||
|
||||
let tenantSlug: string | null = null;
|
||||
if (ctx.tenantId) {
|
||||
const t = await restGet<{ slug?: string }[]>(
|
||||
`/rest/v1/tenants?id=eq.${encodeURIComponent(ctx.tenantId)}&select=slug`
|
||||
);
|
||||
tenantSlug = t.data?.[0]?.slug || null;
|
||||
}
|
||||
const isAguaMiia = tenantSlug === 'agua-miia';
|
||||
|
||||
let calendarDocs: Array<{ title?: string; content?: unknown }> = [];
|
||||
if (agency && ctx.viaView && ctx.tenantId) {
|
||||
const tid = encodeURIComponent(ctx.tenantId);
|
||||
@@ -68,7 +77,6 @@ export default async function CalendarioPage({
|
||||
calendarDocs = data || [];
|
||||
}
|
||||
|
||||
// Prefer structured month plans; fallback to bundled Octubre Miia seed
|
||||
const plans: RrssMonthPlan[] = [];
|
||||
for (const doc of calendarDocs) {
|
||||
const raw =
|
||||
@@ -82,12 +90,18 @@ export default async function CalendarioPage({
|
||||
})()
|
||||
: doc.content;
|
||||
const plan = asPlan(raw);
|
||||
if (plan) plans.push(plan);
|
||||
if (!plan) continue;
|
||||
const blob = JSON.stringify(plan).toLowerCase();
|
||||
if (!isAguaMiia && blob.includes('agua miia')) continue;
|
||||
plans.push(plan);
|
||||
}
|
||||
if (!plans.length) {
|
||||
if (!plans.length && isAguaMiia) {
|
||||
plans.push(octPlan as unknown as RrssMonthPlan);
|
||||
}
|
||||
|
||||
const canPublishMiia = agency && isAguaMiia;
|
||||
const publishBtn = canPublishMiia ? <PublishRrssButton /> : null;
|
||||
|
||||
const monthParam = sp.month || plans[0]?.month;
|
||||
const active = plans.find((p) => p.month === monthParam) || plans[0];
|
||||
// Portada = archivo anual estilo /rrss/ig-fb/
|
||||
@@ -111,8 +125,8 @@ export default async function CalendarioPage({
|
||||
{ctx.label}
|
||||
</h2>
|
||||
<p className="text-sm text-gray-400 mt-2 max-w-xl">
|
||||
Calendario Instagram/Facebook y estrategia LinkedIn. Sin publicación
|
||||
pública: solo portal + doble check agencia/cliente.
|
||||
Calendario editorial de {ctx.label}. No se publica en redes: solo el
|
||||
cliente (y la agencia en su contexto) lo ve aquí.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
@@ -128,7 +142,7 @@ export default async function CalendarioPage({
|
||||
>
|
||||
<Linkedin className="w-4 h-4 mr-2" /> LinkedIn
|
||||
</Link>
|
||||
{agency ? <PublishRrssButton /> : null}
|
||||
{publishBtn}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -142,7 +156,7 @@ export default async function CalendarioPage({
|
||||
year={year}
|
||||
plans={plans}
|
||||
tenantLabel={ctx.label}
|
||||
agencyPublish={agency ? <PublishRrssButton /> : null}
|
||||
agencyPublish={publishBtn}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -159,13 +173,26 @@ export default async function CalendarioPage({
|
||||
);
|
||||
}
|
||||
|
||||
// Legacy flat posts fallback
|
||||
const legacy = asLegacyPosts(
|
||||
typeof calendarDocs[0]?.content === 'string'
|
||||
? JSON.parse(calendarDocs[0].content as string)
|
||||
: calendarDocs[0]?.content
|
||||
);
|
||||
|
||||
if (!legacy.length) {
|
||||
return (
|
||||
<div className="rounded-xl border border-gray-200 bg-white p-6 space-y-2">
|
||||
<h2 className="text-lg font-semibold text-gray-900">
|
||||
Sin calendario editorial
|
||||
</h2>
|
||||
<p className="text-sm text-gray-600">
|
||||
{ctx.label} no tiene un plan RRSS en el portal. Cada cliente solo ve
|
||||
el suyo; no se muestra el de otros tenants.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<PageHeader
|
||||
|
||||
Reference in New Issue
Block a user