Private
Public Access
Rebuild frontend from the Claude Design handoff, DarkSingularity brand
Replaces the Phase 1 placeholder UI with a single persistent app shell (top bar + sidebar + chat pane, 860px responsive breakpoint) matching the "PWA chat system UI" design handoff: message bubbles with consecutive-run avatar/name grouping, auto-growing composer, room search, and the real DarkSingularity logo (also used to regenerate the PWA icons). The handoff didn't cover Phase 2 (private rooms, roles, invites) or browsing/joining open rooms, so those are added using the same visual language: a room info panel with role badges, invite-by-username with a pending-invites list, member management (remove/promote/demote/transfer ownership), room settings (rename/describe/delete), and separate browse-rooms/invites-inbox modals. Unread badges, last-message preview, and the typing indicator are deliberately deferred -- both need new backend features (read-tracking, a WS typing event) that weren't in scope this pass. Two small backend additions round out data the new UI needs but the API didn't expose: MessageRead.username (historic messages had no sender name) and InviteRead.target_username / MyInviteRead.room_name+invited_by_username (a recipient's invite list can't otherwise resolve a room they're not in). Both are additive; 35 backend tests still pass. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
import { apiFetch } from './client'
|
||||
import type { Invite, MyInvite, RoomMember } from '../types'
|
||||
|
||||
export function createInvite(roomId: string, targetUsername: string): Promise<Invite> {
|
||||
return apiFetch<Invite>(`/api/rooms/${roomId}/invites`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ target_username: targetUsername }),
|
||||
})
|
||||
}
|
||||
|
||||
export function listRoomInvites(roomId: string): Promise<Invite[]> {
|
||||
return apiFetch<Invite[]>(`/api/rooms/${roomId}/invites`)
|
||||
}
|
||||
|
||||
export function revokeInvite(roomId: string, inviteId: string): Promise<void> {
|
||||
return apiFetch<void>(`/api/rooms/${roomId}/invites/${inviteId}`, { method: 'DELETE' })
|
||||
}
|
||||
|
||||
export function listMyInvites(): Promise<MyInvite[]> {
|
||||
return apiFetch<MyInvite[]>('/api/invites/mine')
|
||||
}
|
||||
|
||||
export function acceptInvite(inviteId: string): Promise<RoomMember> {
|
||||
return apiFetch<RoomMember>(`/api/invites/${inviteId}/accept`, { method: 'POST' })
|
||||
}
|
||||
|
||||
export function declineInvite(inviteId: string): Promise<Invite> {
|
||||
return apiFetch<Invite>(`/api/invites/${inviteId}/decline`, { method: 'POST' })
|
||||
}
|
||||
@@ -1,21 +1,73 @@
|
||||
import { apiFetch } from './client'
|
||||
import type { Message, Room, RoomListItem } from '../types'
|
||||
import type { Message, MyRoomItem, Room, RoomListItem, RoomMember, RoomRole } from '../types'
|
||||
|
||||
export function listRooms(): Promise<RoomListItem[]> {
|
||||
return apiFetch<RoomListItem[]>('/api/rooms')
|
||||
}
|
||||
|
||||
export function createRoom(name: string, description?: string): Promise<Room> {
|
||||
export function listMyRooms(): Promise<MyRoomItem[]> {
|
||||
return apiFetch<MyRoomItem[]>('/api/rooms/mine')
|
||||
}
|
||||
|
||||
export function createRoom(
|
||||
name: string,
|
||||
description?: string,
|
||||
isPrivate = false,
|
||||
): Promise<Room> {
|
||||
return apiFetch<Room>('/api/rooms', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ name, description: description || null }),
|
||||
body: JSON.stringify({ name, description: description || null, is_private: isPrivate }),
|
||||
})
|
||||
}
|
||||
|
||||
export function updateRoom(
|
||||
roomId: string,
|
||||
data: { name?: string; description?: string },
|
||||
): Promise<Room> {
|
||||
return apiFetch<Room>(`/api/rooms/${roomId}`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteRoom(roomId: string): Promise<void> {
|
||||
return apiFetch<void>(`/api/rooms/${roomId}`, { method: 'DELETE' })
|
||||
}
|
||||
|
||||
export function joinRoom(roomId: string): Promise<Room> {
|
||||
return apiFetch<Room>(`/api/rooms/${roomId}/join`, { method: 'POST' })
|
||||
}
|
||||
|
||||
export function leaveRoom(roomId: string): Promise<void> {
|
||||
return apiFetch<void>(`/api/rooms/${roomId}/leave`, { method: 'POST' })
|
||||
}
|
||||
|
||||
export function listRoomMembers(roomId: string): Promise<RoomMember[]> {
|
||||
return apiFetch<RoomMember[]>(`/api/rooms/${roomId}/members`)
|
||||
}
|
||||
|
||||
export function removeMember(roomId: string, userId: string): Promise<void> {
|
||||
return apiFetch<void>(`/api/rooms/${roomId}/members/${userId}`, { method: 'DELETE' })
|
||||
}
|
||||
|
||||
export function changeMemberRole(
|
||||
roomId: string,
|
||||
userId: string,
|
||||
role: RoomRole,
|
||||
): Promise<RoomMember> {
|
||||
return apiFetch<RoomMember>(`/api/rooms/${roomId}/members/${userId}`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ role }),
|
||||
})
|
||||
}
|
||||
|
||||
export function transferOwnership(roomId: string, newOwnerUserId: string): Promise<Room> {
|
||||
return apiFetch<Room>(`/api/rooms/${roomId}/transfer-ownership`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ new_owner_user_id: newOwnerUserId }),
|
||||
})
|
||||
}
|
||||
|
||||
export function getRoomMessages(roomId: string): Promise<Message[]> {
|
||||
return apiFetch<Message[]>(`/api/rooms/${roomId}/messages`)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user