'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}
)}
);
};
export default PublicationLayout;