feat: TASK 034-036 - MicroDAO Multi-Room Support
TASK 034: MicroDAO Multi-Room Backend
- Added migration 031_microdao_multi_room.sql
- Extended city_rooms with microdao_id, room_role, is_public, sort_order
- Added CityRoomSummary, MicrodaoRoomsList, MicrodaoRoomUpdate models
- Added get_microdao_rooms, get_microdao_rooms_by_slug functions
- Added attach_room_to_microdao, update_microdao_room functions
- Added API endpoints: GET/POST/PATCH /city/microdao/{slug}/rooms
TASK 035: MicroDAO Multi-Room UI
- Added proxy routes for rooms API
- Extended CityRoomSummary type with multi-room fields
- Added useMicrodaoRooms hook
- Created MicrodaoRoomsSection component with role labels/icons
TASK 036: MicroDAO Room Orchestrator Panel
- Created MicrodaoRoomsAdminPanel component
- Role selector, visibility toggle, set primary button
- Attach existing room form
- Integrated into /microdao/[slug] page
This commit is contained in:
44
apps/web/src/app/api/microdao/[slug]/rooms/[roomId]/route.ts
Normal file
44
apps/web/src/app/api/microdao/[slug]/rooms/[roomId]/route.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
const CITY_API_URL = process.env.INTERNAL_API_URL || process.env.CITY_API_BASE_URL || "http://daarion-city-service:7001";
|
||||
|
||||
/**
|
||||
* PATCH /api/microdao/[slug]/rooms/[roomId]
|
||||
* Update a MicroDAO room settings
|
||||
*/
|
||||
export async function PATCH(
|
||||
request: NextRequest,
|
||||
context: { params: Promise<{ slug: string; roomId: string }> }
|
||||
) {
|
||||
try {
|
||||
const { slug, roomId } = await context.params;
|
||||
const body = await request.text();
|
||||
|
||||
const response = await fetch(`${CITY_API_URL}/city/microdao/${slug}/rooms/${roomId}`, {
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
console.error("Failed to update microdao room:", response.status, text);
|
||||
return NextResponse.json(
|
||||
{ error: "Failed to update room", detail: text },
|
||||
{ status: response.status }
|
||||
);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return NextResponse.json(data);
|
||||
} catch (error) {
|
||||
console.error("Error updating microdao room:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Internal server error" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
const CITY_API_URL = process.env.INTERNAL_API_URL || process.env.CITY_API_BASE_URL || "http://daarion-city-service:7001";
|
||||
|
||||
/**
|
||||
* POST /api/microdao/[slug]/rooms/attach-existing
|
||||
* Attach an existing city room to a MicroDAO
|
||||
*/
|
||||
export async function POST(
|
||||
request: NextRequest,
|
||||
context: { params: Promise<{ slug: string }> }
|
||||
) {
|
||||
try {
|
||||
const { slug } = await context.params;
|
||||
const body = await request.text();
|
||||
|
||||
const response = await fetch(`${CITY_API_URL}/city/microdao/${slug}/rooms/attach-existing`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
console.error("Failed to attach room to microdao:", response.status, text);
|
||||
return NextResponse.json(
|
||||
{ error: "Failed to attach room", detail: text },
|
||||
{ status: response.status }
|
||||
);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return NextResponse.json(data);
|
||||
} catch (error) {
|
||||
console.error("Error attaching room to microdao:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Internal server error" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
42
apps/web/src/app/api/microdao/[slug]/rooms/route.ts
Normal file
42
apps/web/src/app/api/microdao/[slug]/rooms/route.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
const CITY_API_URL = process.env.INTERNAL_API_URL || process.env.CITY_API_BASE_URL || "http://daarion-city-service:7001";
|
||||
|
||||
/**
|
||||
* GET /api/microdao/[slug]/rooms
|
||||
* Get all rooms for a MicroDAO
|
||||
*/
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
context: { params: Promise<{ slug: string }> }
|
||||
) {
|
||||
try {
|
||||
const { slug } = await context.params;
|
||||
|
||||
const response = await fetch(`${CITY_API_URL}/city/microdao/${slug}/rooms`, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
cache: "no-store",
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
console.error("Failed to get microdao rooms:", response.status, text);
|
||||
return NextResponse.json(
|
||||
{ error: "Failed to get MicroDAO rooms", detail: text },
|
||||
{ status: response.status }
|
||||
);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return NextResponse.json(data);
|
||||
} catch (error) {
|
||||
console.error("Error getting microdao rooms:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Internal server error" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,16 +2,24 @@
|
||||
|
||||
import { useParams } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import { useMicrodaoDetail } from "@/hooks/useMicrodao";
|
||||
import { useMicrodaoDetail, useMicrodaoRooms } from "@/hooks/useMicrodao";
|
||||
import { DISTRICT_COLORS } from "@/lib/microdao";
|
||||
import { MicrodaoVisibilityCard } from "@/components/microdao/MicrodaoVisibilityCard";
|
||||
import { MicrodaoRoomsSection } from "@/components/microdao/MicrodaoRoomsSection";
|
||||
import { MicrodaoRoomsAdminPanel } from "@/components/microdao/MicrodaoRoomsAdminPanel";
|
||||
import { ChevronLeft, Users, MessageSquare, Crown, Building2, Globe, Lock, Layers, BarChart3, Bot, MessageCircle } from "lucide-react";
|
||||
import { CityChatWidget } from "@/components/city/CityChatWidget";
|
||||
|
||||
export default function MicrodaoDetailPage() {
|
||||
const params = useParams();
|
||||
const slug = params?.slug as string;
|
||||
const { microdao, isLoading, error } = useMicrodaoDetail(slug);
|
||||
const { microdao, isLoading, error, mutate: refreshMicrodao } = useMicrodaoDetail(slug);
|
||||
const { rooms, mutate: refreshRooms } = useMicrodaoRooms(slug);
|
||||
|
||||
const handleRoomUpdated = () => {
|
||||
refreshRooms();
|
||||
refreshMicrodao();
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
@@ -373,35 +381,21 @@ export default function MicrodaoDetailPage() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Public Chat Room */}
|
||||
<section className="bg-slate-800/30 border border-slate-700/50 rounded-xl p-6 space-y-4">
|
||||
<h2 className="text-lg font-semibold text-slate-100 flex items-center gap-2">
|
||||
<MessageCircle className="w-5 h-5 text-purple-400" />
|
||||
Публічний чат MicroDAO
|
||||
</h2>
|
||||
|
||||
{microdao.primary_city_room ? (
|
||||
<div className="space-y-3">
|
||||
<p className="text-sm text-slate-400">
|
||||
Matrix-чат у кімнаті: <span className="text-purple-400">{microdao.primary_city_room.name}</span>
|
||||
</p>
|
||||
{orchestrator && (
|
||||
<p className="text-xs text-slate-500">
|
||||
Оркестратор: <Link href={`/agents/${orchestrator.agent_id}`} className="text-cyan-400 hover:underline">{orchestrator.display_name}</Link>
|
||||
</p>
|
||||
)}
|
||||
<CityChatWidget roomSlug={microdao.primary_city_room.slug} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-8 text-slate-500">
|
||||
<MessageCircle className="w-12 h-12 mx-auto mb-2 opacity-30" />
|
||||
<p>Для цього MicroDAO ще не налаштована публічна кімната.</p>
|
||||
<p className="text-sm mt-2 text-slate-600">
|
||||
Налаштуйте primary room у City Service, щоб увімкнути чат.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
{/* Orchestrator Room Management Panel */}
|
||||
{orchestrator && (
|
||||
<MicrodaoRoomsAdminPanel
|
||||
microdaoSlug={slug}
|
||||
rooms={rooms.length > 0 ? rooms : (microdao.rooms || [])}
|
||||
canManage={true} // TODO: check if current user is orchestrator
|
||||
onRoomUpdated={handleRoomUpdated}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Multi-Room Section with Chats */}
|
||||
<MicrodaoRoomsSection
|
||||
rooms={rooms.length > 0 ? rooms : (microdao.rooms || [])}
|
||||
primaryRoomSlug={microdao.primary_city_room?.slug}
|
||||
/>
|
||||
|
||||
{/* Visibility Settings (only for orchestrator) */}
|
||||
{orchestrator && (
|
||||
|
||||
Reference in New Issue
Block a user