Files
microdao-daarion/apps/web/src/app/city/[slug]/page.tsx
Apple a6e531a098 fix: NODE1_REPAIR - healthchecks, dependencies, SSR env, telegram gateway
TASK_PHASE_NODE1_REPAIR:
- Fix daarion-web SSR: use CITY_API_BASE_URL instead of 127.0.0.1
- Fix auth API routes: use AUTH_API_URL env var
- Add wget to Dockerfiles for healthchecks (stt, ocr, web-search, swapper, vector-db, rag)
- Update healthchecks to use wget instead of curl
- Fix vector-db-service: update torch==2.4.0, sentence-transformers==2.6.1
- Fix rag-service: correct haystack imports for v2.x
- Fix telegram-gateway: remove msg.ack() for non-JetStream NATS
- Add /health endpoint to nginx mvp-routes.conf
- Add room_role, is_public, sort_order columns to city_rooms migration
- Add TASK_PHASE_NODE1_REPAIR.md and DEPLOY_NODE1_REPAIR.md docs

Previous tasks included:
- TASK 039-044: Orchestrator rooms, Matrix chat cleanup, CrewAI integration
2025-11-29 05:17:08 -08:00

181 lines
6.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Link from 'next/link'
import { ArrowLeft, Users, FileText, Clock, MessageCircle } from 'lucide-react'
import { api, CityRoom } from '@/lib/api'
import { formatDate } from '@/lib/utils'
import { notFound } from 'next/navigation'
import { CityChatWidget } from '@/components/city/CityChatWidget'
// Force dynamic rendering - don't prerender at build time
export const dynamic = 'force-dynamic'
interface PageProps {
params: Promise<{ slug: string }>
}
async function getRoom(slug: string): Promise<CityRoom | null> {
try {
return await api.getCityRoom(slug)
} catch (error) {
console.error('Failed to fetch room:', error)
return null
}
}
export default async function RoomPage({ params }: PageProps) {
const { slug } = await params
const room = await getRoom(slug)
if (!room) {
notFound()
}
return (
<div className="min-h-screen">
{/* Header */}
<div className="px-4 py-6 border-b border-white/5">
<div className="max-w-6xl mx-auto">
<Link
href="/city"
className="inline-flex items-center gap-2 text-slate-400 hover:text-white transition-colors mb-4"
>
<ArrowLeft className="w-4 h-4" />
Назад до кімнат
</Link>
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div>
<h1 className="text-2xl sm:text-3xl font-bold text-white">
{room.name}
</h1>
<p className="text-slate-400 mt-1">
{room.description || 'Без опису'}
</p>
</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-2 text-sm">
<span className="w-2 h-2 rounded-full bg-emerald-400 animate-pulse" />
<span className="text-emerald-400">{room.members_online} онлайн</span>
</div>
{room.is_default && (
<span className="px-3 py-1 text-xs font-medium bg-cyan-500/20 text-cyan-400 rounded-full">
Default Room
</span>
)}
</div>
</div>
</div>
</div>
{/* Main Content */}
<div className="max-w-6xl mx-auto px-4 py-6">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Chat Area */}
<div className="lg:col-span-2">
<CityChatWidget
roomSlug={room.slug}
mode="embedded"
className="h-[600px] min-h-[500px]"
/>
{/* Matrix Room Info */}
{room.matrix_room_id && (
<div className="mt-4 glass-panel p-4">
<div className="flex items-center gap-2 text-sm">
<MessageCircle className="w-4 h-4 text-cyan-400" />
<span className="text-slate-400">Matrix Room:</span>
<code className="text-xs font-mono text-cyan-400 bg-slate-800/50 px-2 py-0.5 rounded">
{room.matrix_room_alias || room.matrix_room_id}
</code>
</div>
</div>
)}
</div>
{/* Sidebar */}
<div className="space-y-6">
{/* Room Info */}
<div className="glass-panel p-6">
<h3 className="text-lg font-semibold text-white mb-4 flex items-center gap-2">
<FileText className="w-5 h-5 text-cyan-400" />
Інформація
</h3>
<div className="space-y-4">
<InfoRow label="ID" value={room.id} />
<InfoRow label="Slug" value={room.slug} />
<InfoRow
label="Створено"
value={formatDate(room.created_at)}
/>
<InfoRow
label="Автор"
value={room.created_by || 'Система'}
/>
</div>
</div>
{/* Online Users */}
<div className="glass-panel p-6">
<h3 className="text-lg font-semibold text-white mb-4 flex items-center gap-2">
<Users className="w-5 h-5 text-cyan-400" />
Учасники онлайн
</h3>
{room.members_online > 0 ? (
<div className="flex flex-wrap gap-2">
{Array.from({ length: Math.min(room.members_online, 8) }).map((_, i) => (
<div
key={i}
className="w-10 h-10 rounded-full bg-gradient-to-br from-cyan-500/30 to-blue-600/30 flex items-center justify-center text-sm font-medium text-cyan-400"
>
U{i + 1}
</div>
))}
{room.members_online > 8 && (
<div className="w-10 h-10 rounded-full bg-slate-700/50 flex items-center justify-center text-sm text-slate-400">
+{room.members_online - 8}
</div>
)}
</div>
) : (
<p className="text-sm text-slate-400">
Наразі ніхто не онлайн
</p>
)}
</div>
{/* Co-Memory Placeholder */}
<div className="glass-panel p-6">
<h3 className="text-lg font-semibold text-white mb-4 flex items-center gap-2">
<Clock className="w-5 h-5 text-cyan-400" />
Co-Memory / Files
</h3>
<div className="text-center py-6">
<div className="w-12 h-12 mx-auto mb-3 rounded-full bg-slate-800/50 flex items-center justify-center">
<FileText className="w-6 h-6 text-slate-600" />
</div>
<p className="text-sm text-slate-400">
Спільна памʼять та файли будуть доступні скоро
</p>
</div>
</div>
</div>
</div>
</div>
</div>
)
}
function InfoRow({ label, value }: { label: string; value: string }) {
return (
<div className="flex justify-between items-center text-sm">
<span className="text-slate-400">{label}</span>
<span className="text-white font-mono text-xs bg-slate-800/50 px-2 py-1 rounded">
{value}
</span>
</div>
)
}