'use client'; /** * @file PublicationLayout.tsx * @description 12-column grid layout for the Post creation/edit form. * Organizes fields into logical sections: Datos Básicos, Contenido, * Multimedia, SEO, Configuración, and Comercial. * * Integrates: * - `FormSection` for visual grouping * - `RelationSelectField` for relation fields with quick-create * - `RichTextEditor` for contenido field * - `usePublicationStore` for optimistic UI and state * - Zod validation (client-side, before submission) * - `PermissionGuard` to ensure only editor/admin can submit */ import React, { useState, useTransition, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import { FileText, Image as ImageIcon, Search, Settings, Briefcase, Save, ArrowLeft, AlertCircle, CheckCircle, Loader2, Eye } from 'lucide-react'; import Link from 'next/link'; import { PublicationLayoutProps } from '@/types/publications'; import { PostSchema, generateSlug } from '@/lib/validators/publications'; import { usePublicationStore } from '@/store/usePublicationStore'; import FormSection from './FormSection'; import RelationSelectField from './RelationSelectField'; import PermissionGuard from './PermissionGuard'; // ───────────────────────────────────────────── // FIELD COMPONENTS // ───────────────────────────────────────────── function FieldWrapper({ label, required, error, children, hint, colSpan = 1, }: { label: string; required?: boolean; error?: string; children: React.ReactNode; hint?: string; colSpan?: number; }) { const spanClass = colSpan === 2 ? 'md:col-span-2' : ''; return (
{children} {hint && !error &&

{hint}

} {error && (

{error}

)}
); } const inputClass = 'w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-2.5 text-white text-sm ' + 'focus:border-cyan-500 focus:outline-none focus:ring-1 focus:ring-cyan-500/30 ' + 'hover:border-slate-700 transition-all placeholder:text-slate-700'; const textareaClass = inputClass + ' min-h-[100px] resize-y'; // ───────────────────────────────────────────── // MAIN COMPONENT // ───────────────────────────────────────────── export const PublicationLayout: React.FC = ({ initialData, relationData, submitAction, user, appwriteConfig, }) => { const router = useRouter(); const [isPending, startTransition] = useTransition(); const { setOptimisticState, optimisticState, errorMessage, reset, setRelationCache } = usePublicationStore(); // Form state const [titulo, setTitulo] = useState(initialData?.titulo || ''); const [slug, setSlug] = useState(initialData?.slug || ''); const [isAutoSlug, setIsAutoSlug] = useState(!initialData?.slug); const [validationErrors, setValidationErrors] = useState>({}); // Populate relation cache in the store on mount useEffect(() => { Object.entries(relationData).forEach(([collection, data]) => { setRelationCache(collection, data); }); return () => reset(); }, []); // eslint-disable-line react-hooks/exhaustive-deps // Auto-generate slug from titulo useEffect(() => { if (isAutoSlug && titulo) { setSlug(generateSlug(titulo)); } }, [titulo, isAutoSlug]); const userRole = (user?.prefs?.role as any) || 'viewer'; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); const formData = new FormData(e.currentTarget); // Client-side Zod validation const parseResult = PostSchema.safeParse(Object.fromEntries(formData.entries())); if (!parseResult.success) { const errors: Record = {}; parseResult.error.errors.forEach((err) => { const field = err.path[0] as string; errors[field] = err.message; }); setValidationErrors(errors); return; } setValidationErrors({}); setOptimisticState('saving'); startTransition(async () => { try { const result = await submitAction(formData); if (result?.success) { setOptimisticState('saved'); setTimeout(() => router.push('/dashboard/posts'), 800); } else { setOptimisticState('error', result?.error || 'Error al guardar.'); } } catch (err: unknown) { setOptimisticState('error', err instanceof Error ? err.message : 'Error desconocido.'); } }); }; const isLoading = isPending || optimisticState === 'saving'; const isSaved = optimisticState === 'saved'; return (
{/* Page Header */}

{initialData?.$id ? 'Editar Publicación' : 'Nueva Publicación'}

{initialData?.$id ? `ID: ${initialData.$id}` : 'Completa todos los campos requeridos.'}

{/* Status indicator */}
{isSaved && ( Guardado )} {optimisticState === 'error' && ( {errorMessage} )}
{/* 12-column grid layout: left=8cols content, right=4cols sidebar */}
{/* ── LEFT COLUMN (8/12) ─────────────────────── */}
{/* SECCIÓN: Datos Básicos */}
} columns={1}> {/* Título */} setTitulo(e.target.value)} placeholder="Escribe un título llamativo..." required className={inputClass} /> {/* Slug */}
{ setSlug(e.target.value); setIsAutoSlug(false); }} placeholder="url-de-la-publicacion" required className={`${inputClass} font-mono`} />
{/* Resumen */}