- Vista de leads en tarjetas + tabla con toggle (tarjetas por defecto, preferencia persistida) - Galería de trabajos: gestión en /panel/galeria y bloque público en el funnel - Selector de tema por reformista (presets + color de marca opcional) aplicado a la landing - Login y registro rediseñados a pantalla partida 50/50 con foto de reforma - Enlace "Entrar" funcional en la cabecera del funnel; elimina Navbar muerto - Unifica tipografía y botones del panel con los tokens de la landing Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
57 lines
1.9 KiB
TypeScript
57 lines
1.9 KiB
TypeScript
'use server';
|
|
|
|
import { and, eq } from 'drizzle-orm';
|
|
import { revalidatePath } from 'next/cache';
|
|
import { db } from '@/db';
|
|
import { galeriaFotos } from '@/db/schema';
|
|
import { getCurrentTenantId as getTenantId } from '@/lib/auth/current-user';
|
|
import { GALERIA_MAX_FOTOS } from '@/lib/galeria';
|
|
|
|
const GALERIA_MAX_BYTES = 2_000_000;
|
|
const GALERIA_TIPOS = ['image/png', 'image/jpeg', 'image/webp'];
|
|
|
|
export type GaleriaResult = { ok: boolean; error?: string };
|
|
|
|
export async function subirFotoGaleria(
|
|
_prev: GaleriaResult | null,
|
|
formData: FormData
|
|
): Promise<GaleriaResult> {
|
|
const tenantId = await getTenantId();
|
|
const file = formData.get('foto');
|
|
if (!(file instanceof File) || file.size === 0) {
|
|
return { ok: false, error: 'Selecciona una imagen.' };
|
|
}
|
|
if (!GALERIA_TIPOS.includes(file.type)) {
|
|
return { ok: false, error: 'Formato no válido. Usa PNG, JPG o WEBP.' };
|
|
}
|
|
if (file.size > GALERIA_MAX_BYTES) {
|
|
return { ok: false, error: 'La imagen no puede superar los 2 MB.' };
|
|
}
|
|
|
|
const existentes = await db
|
|
.select({ id: galeriaFotos.id })
|
|
.from(galeriaFotos)
|
|
.where(eq(galeriaFotos.tenantId, tenantId));
|
|
if (existentes.length >= GALERIA_MAX_FOTOS) {
|
|
return { ok: false, error: `Has alcanzado el máximo de ${GALERIA_MAX_FOTOS} fotos.` };
|
|
}
|
|
|
|
const titulo = String(formData.get('titulo') ?? '').trim() || null;
|
|
const base64 = Buffer.from(await file.arrayBuffer()).toString('base64');
|
|
const dataUri = `data:${file.type};base64,${base64}`;
|
|
|
|
await db
|
|
.insert(galeriaFotos)
|
|
.values({ tenantId, url: dataUri, titulo, orden: existentes.length });
|
|
revalidatePath('/panel/galeria');
|
|
return { ok: true };
|
|
}
|
|
|
|
export async function eliminarFotoGaleria(id: string) {
|
|
const tenantId = await getTenantId();
|
|
await db
|
|
.delete(galeriaFotos)
|
|
.where(and(eq(galeriaFotos.id, id), eq(galeriaFotos.tenantId, tenantId)));
|
|
revalidatePath('/panel/galeria');
|
|
}
|