179 lines
9.3 KiB
TypeScript
179 lines
9.3 KiB
TypeScript
import { APPWRITE_ALMEIRA_CONFIG } from '@/lib/appwrite';
|
|
import { createAdminClient, getLoggedInUser } from '@/lib/appwrite.server';
|
|
import { Query } from 'node-appwrite';
|
|
import { NewsCard } from '@/components/NewsCard';
|
|
import { GlobalSidebar, hasSidebarContent } from '@/components/AdWrappers';
|
|
import { Sparkles } from 'lucide-react';
|
|
import { Settings } from '@/types/settings';
|
|
import { Post, MediaItem } from '@/types/publications';
|
|
|
|
async function fetchClasificadosData() {
|
|
try {
|
|
const client = await createAdminClient();
|
|
|
|
// 1. Settings
|
|
const settingsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'settings', []);
|
|
const settings = (settingsRes.documents.length > 0 ? settingsRes.documents[0] : {
|
|
ticker_count: 10,
|
|
rotation_seconds: 5,
|
|
feed_news_count: 12,
|
|
show_news_date: true
|
|
}) as Settings;
|
|
|
|
const postsCount = settings.feed_news_count || 12;
|
|
const cardSettings = {
|
|
showCategory: settings.news_card_show_category ?? false,
|
|
showFooterDate: settings.show_news_date !== false,
|
|
showFooterButton: settings.news_card_show_footer_button ?? true,
|
|
footerText: settings.news_card_footer_text || ""
|
|
};
|
|
|
|
// 2. Get CLASIFICADO type ID
|
|
const typeRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'publication_types', [
|
|
Query.equal('codigo', 'CLASIFICADO')
|
|
]);
|
|
|
|
let posts: Post[] = [];
|
|
if (typeRes.documents.length > 0) {
|
|
const firstType = typeRes.documents[0];
|
|
const typeId = firstType?.$id;
|
|
if (!typeId) return { posts: [], cardSettings };
|
|
const postsRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'posts', [
|
|
Query.equal('tipo_publicacion_id', typeId),
|
|
Query.equal('estado', ['Publicado', 'publicado']),
|
|
Query.orderDesc('fecha_publicacion'),
|
|
Query.limit(postsCount * 2)
|
|
]);
|
|
|
|
const now = new Date();
|
|
posts = postsRes.documents.filter((post: any) => {
|
|
if (!post.fecha_finalizacion) return true;
|
|
return new Date(post.fecha_finalizacion) >= now;
|
|
}).slice(0, postsCount) as unknown as Post[];
|
|
|
|
// 3. Resolve Media
|
|
const mediaIds = posts.map(p => p.imagen_destacada_id).filter(id => id && typeof id === 'string') as string[];
|
|
if (mediaIds.length > 0) {
|
|
const mediaRes = await client.databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, 'media', [
|
|
Query.equal('$id', mediaIds)
|
|
]);
|
|
const mediaMap = new Map<string, MediaItem>(mediaRes.documents.map(m => [m.$id, m as unknown as MediaItem]));
|
|
posts = posts.map(p => JSON.parse(JSON.stringify({
|
|
...p,
|
|
resolved_media: mediaMap.get(p.imagen_destacada_id as string)
|
|
})));
|
|
} else {
|
|
posts = JSON.parse(JSON.stringify(posts));
|
|
}
|
|
}
|
|
|
|
return { posts: JSON.parse(JSON.stringify(posts)) as Post[], cardSettings };
|
|
} catch (e) {
|
|
console.error("Clasificados fetch error", e);
|
|
return { posts: [], cardSettings: { showCategory: false, showFooterDate: true, showFooterButton: true, footerText: "" } };
|
|
}
|
|
}
|
|
|
|
export default async function ClasificadosPage() {
|
|
const { posts, cardSettings } = await fetchClasificadosData();
|
|
const hasSidebar = await hasSidebarContent();
|
|
|
|
const rawUser = await getLoggedInUser();
|
|
const user = rawUser ? JSON.parse(JSON.stringify(rawUser)) : null;
|
|
const isAdmin = (user as any)?.labels?.includes('admin') || (user as any)?.labels?.includes('editor') || user?.$id === 'admin-emergency';
|
|
|
|
return (
|
|
<main className="animate-fade-in relative min-h-screen">
|
|
{/* Sci-Fi Hero Section */}
|
|
<section className="relative flex flex-col items-center justify-center min-h-[60vh] text-center px-4 overflow-hidden mesh-bg">
|
|
<div className="absolute top-1/4 left-1/4 w-[500px] h-[500px] bg-purple-600/10 rounded-full blur-[120px] mix-blend-screen pointer-events-none"></div>
|
|
|
|
<div className="relative z-10 flex flex-col items-center max-w-4xl mx-auto">
|
|
<h1 className="text-5xl md:text-7xl font-outfit font-black tracking-tighter mb-6 leading-tight">
|
|
MERCADO DE<br/>
|
|
<span className="holographic-text block mt-2">CLASIFICADOS</span>
|
|
</h1>
|
|
|
|
<p className="text-lg text-slate-400 max-w-2xl mb-10 font-medium">
|
|
El primer portal transaccional de medios impulsado por inteligencia artificial.
|
|
Compra, venta y servicios en tu comunidad al instante con total seguridad.
|
|
</p>
|
|
|
|
<a
|
|
href="#noticias"
|
|
className="group relative inline-flex items-center justify-center gap-3 px-8 py-3 rounded-full font-bold text-white overflow-hidden transition-all hover:scale-105"
|
|
>
|
|
<div className="absolute inset-0 bg-gradient-to-r from-purple-600 to-blue-600 opacity-90 group-hover:opacity-100 transition-opacity"></div>
|
|
<span className="relative z-10 tracking-widest uppercase text-sm">Explorar Mercado</span>
|
|
</a>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="noticias" className="container mx-auto px-4 py-20 relative z-10 w-full">
|
|
<div className="flex flex-col lg:flex-row gap-8 w-full items-start">
|
|
<div className="flex-1 w-full relative">
|
|
<div className="flex flex-col md:flex-row justify-between items-end mb-12 gap-6">
|
|
<div>
|
|
<div className="flex items-center gap-3 mb-2">
|
|
<div className="w-12 h-1 bg-gradient-to-r from-purple-400 to-blue-600 rounded-full"></div>
|
|
<span className="text-purple-400 font-bold tracking-widest uppercase text-sm">Avisos</span>
|
|
</div>
|
|
<h2 className="text-4xl font-outfit font-black text-white">Anuncios <span className="text-transparent bg-clip-text bg-gradient-to-r from-white to-slate-500">Recientes</span></h2>
|
|
</div>
|
|
</div>
|
|
|
|
{posts.length === 0 ? (
|
|
<div className="w-full py-20 flex flex-col items-center justify-center border border-white/5 rounded-3xl glass-panel relative overflow-hidden">
|
|
<div className="p-6 rounded-full bg-slate-800/80 mb-6 border border-white/10 relative z-10">
|
|
<Sparkles size={48} className="text-purple-500" />
|
|
</div>
|
|
<h3 className="text-2xl font-black font-outfit text-white mb-2 relative z-10">Sin Anuncios Recientes</h3>
|
|
<p className="text-slate-400 relative z-10">El mercado se encuentra a la espera de nuevas ofertas globales.</p>
|
|
</div>
|
|
) : (
|
|
<div className={`grid grid-cols-1 md:grid-cols-2 ${hasSidebar ? 'lg:grid-cols-2 xl:grid-cols-3' : 'lg:grid-cols-3 xl:grid-cols-4'} gap-6`}>
|
|
{posts.map((post: Post) => {
|
|
const PROJECT_ID = process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID || '69b84df5003bd8957c5c';
|
|
const ENDPOINT = process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT || 'https://appwrite.almeira.com.co/v1';
|
|
let imagenUrl = post.og_image_url || null;
|
|
|
|
const mediaObj = post.resolved_media || (typeof post.imagen_destacada_id === 'object' ? post.imagen_destacada_id : null) as MediaItem | null;
|
|
|
|
if (mediaObj) {
|
|
imagenUrl = `${ENDPOINT}/storage/buckets/${mediaObj.bucket_id}/files/${mediaObj.file_id}/preview?project=${PROJECT_ID}&width=640&height=640&quality=80`;
|
|
}
|
|
|
|
return (
|
|
<NewsCard
|
|
key={post.$id}
|
|
id={post.$id}
|
|
slug={post.slug || post.$id}
|
|
titulo={post.titulo}
|
|
resumen={post.resumen_redes || post.contenido?.replace(/<[^>]*>?/gm, '').substring(0, 150) + '...'}
|
|
tipo="Aviso"
|
|
fecha={post.fecha_publicacion || post.$createdAt}
|
|
imagenUrl={imagenUrl}
|
|
showCategory={cardSettings.showCategory}
|
|
showFooterDate={cardSettings.showFooterDate}
|
|
showFooterButton={cardSettings.showFooterButton}
|
|
footerText={cardSettings.footerText}
|
|
imageAspect="aspect-square"
|
|
isAdmin={isAdmin}
|
|
/>
|
|
)
|
|
})}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{hasSidebar && (
|
|
<aside className="w-full lg:w-[320px] xl:w-[360px] shrink-0 mt-8 lg:mt-0 sticky top-24">
|
|
<GlobalSidebar />
|
|
</aside>
|
|
)}
|
|
</div>
|
|
</section>
|
|
</main>
|
|
);
|
|
}
|