Rediseña panel y auth con la identidad de la landing B2C
- 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>
This commit is contained in:
56
mvp/b2c/src/app/panel/galeria/actions.ts
Normal file
56
mvp/b2c/src/app/panel/galeria/actions.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
'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');
|
||||
}
|
||||
Reference in New Issue
Block a user