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();
|
const headers = serviceHeaders();
|
||||||
if (!headers) {
|
if (!headers) {
|
||||||
return NextResponse.json({ error: 'no service role' }, { status: 500 });
|
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"
|
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" />
|
<Upload className="w-4 h-4 mr-2" />
|
||||||
{busy ? 'Publicando…' : 'Publicar mes en portal'}
|
{busy ? 'Publicando…' : 'Dejar octubre visible para el cliente'}
|
||||||
</button>
|
</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}
|
{msg ? <p className="text-[11px] text-gray-400">{msg}</p> : null}
|
||||||
</div>
|
</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 }> = [];
|
let calendarDocs: Array<{ title?: string; content?: unknown }> = [];
|
||||||
if (agency && ctx.viaView && ctx.tenantId) {
|
if (agency && ctx.viaView && ctx.tenantId) {
|
||||||
const tid = encodeURIComponent(ctx.tenantId);
|
const tid = encodeURIComponent(ctx.tenantId);
|
||||||
@@ -68,7 +77,6 @@ export default async function CalendarioPage({
|
|||||||
calendarDocs = data || [];
|
calendarDocs = data || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prefer structured month plans; fallback to bundled Octubre Miia seed
|
|
||||||
const plans: RrssMonthPlan[] = [];
|
const plans: RrssMonthPlan[] = [];
|
||||||
for (const doc of calendarDocs) {
|
for (const doc of calendarDocs) {
|
||||||
const raw =
|
const raw =
|
||||||
@@ -82,12 +90,18 @@ export default async function CalendarioPage({
|
|||||||
})()
|
})()
|
||||||
: doc.content;
|
: doc.content;
|
||||||
const plan = asPlan(raw);
|
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);
|
plans.push(octPlan as unknown as RrssMonthPlan);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const canPublishMiia = agency && isAguaMiia;
|
||||||
|
const publishBtn = canPublishMiia ? <PublishRrssButton /> : null;
|
||||||
|
|
||||||
const monthParam = sp.month || plans[0]?.month;
|
const monthParam = sp.month || plans[0]?.month;
|
||||||
const active = plans.find((p) => p.month === monthParam) || plans[0];
|
const active = plans.find((p) => p.month === monthParam) || plans[0];
|
||||||
// Portada = archivo anual estilo /rrss/ig-fb/
|
// Portada = archivo anual estilo /rrss/ig-fb/
|
||||||
@@ -111,8 +125,8 @@ export default async function CalendarioPage({
|
|||||||
{ctx.label}
|
{ctx.label}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-sm text-gray-400 mt-2 max-w-xl">
|
<p className="text-sm text-gray-400 mt-2 max-w-xl">
|
||||||
Calendario Instagram/Facebook y estrategia LinkedIn. Sin publicación
|
Calendario editorial de {ctx.label}. No se publica en redes: solo el
|
||||||
pública: solo portal + doble check agencia/cliente.
|
cliente (y la agencia en su contexto) lo ve aquí.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap gap-3">
|
<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
|
<Linkedin className="w-4 h-4 mr-2" /> LinkedIn
|
||||||
</Link>
|
</Link>
|
||||||
{agency ? <PublishRrssButton /> : null}
|
{publishBtn}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -142,7 +156,7 @@ export default async function CalendarioPage({
|
|||||||
year={year}
|
year={year}
|
||||||
plans={plans}
|
plans={plans}
|
||||||
tenantLabel={ctx.label}
|
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(
|
const legacy = asLegacyPosts(
|
||||||
typeof calendarDocs[0]?.content === 'string'
|
typeof calendarDocs[0]?.content === 'string'
|
||||||
? JSON.parse(calendarDocs[0].content as string)
|
? JSON.parse(calendarDocs[0].content as string)
|
||||||
: calendarDocs[0]?.content
|
: 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 (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<PageHeader
|
<PageHeader
|
||||||
|
|||||||
Reference in New Issue
Block a user