feat: enhance room management and UI with join/leave functionality
- Added GraphQL mutations and subscriptions for joining and leaving rooms - Implemented room member tracking and display in room list - Added leave room confirmation and error handling in chat room - Updated room list and chat room components with new interaction features - Improved UI with member badges, join/leave buttons, and error messages - Enhanced room query to include member information
This commit is contained in:
parent
16731409df
commit
c737258aed
6 changed files with 339 additions and 49 deletions
|
@ -6,6 +6,7 @@ const prisma = new PrismaClient();
|
||||||
const pubsub = new PubSub();
|
const pubsub = new PubSub();
|
||||||
|
|
||||||
export const ROOM_ADDED = 'ROOM_ADDED';
|
export const ROOM_ADDED = 'ROOM_ADDED';
|
||||||
|
export const ROOM_UPDATED = 'ROOM_UPDATED';
|
||||||
|
|
||||||
export const roomResolvers = {
|
export const roomResolvers = {
|
||||||
Query: {
|
Query: {
|
||||||
|
@ -81,14 +82,20 @@ export const roomResolvers = {
|
||||||
return room;
|
return room;
|
||||||
}
|
}
|
||||||
|
|
||||||
return prisma.room.update({
|
const updatedRoom = await prisma.room.update({
|
||||||
where: { id: roomId },
|
where: { id: roomId },
|
||||||
data: {
|
data: {
|
||||||
members: {
|
members: {
|
||||||
connect: { id: context.userId },
|
connect: { id: context.userId },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
include: { members: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Publish room updated event
|
||||||
|
pubsub.publish(ROOM_UPDATED, { roomUpdated: updatedRoom });
|
||||||
|
|
||||||
|
return updatedRoom;
|
||||||
},
|
},
|
||||||
leaveRoom: async (_: any, { roomId }: { roomId: string }, context: any) => {
|
leaveRoom: async (_: any, { roomId }: { roomId: string }, context: any) => {
|
||||||
if (!context.userId) {
|
if (!context.userId) {
|
||||||
|
@ -117,15 +124,19 @@ export const roomResolvers = {
|
||||||
throw new ForbiddenError('You cannot leave a room you own');
|
throw new ForbiddenError('You cannot leave a room you own');
|
||||||
}
|
}
|
||||||
|
|
||||||
await prisma.room.update({
|
const updatedRoom = await prisma.room.update({
|
||||||
where: { id: roomId },
|
where: { id: roomId },
|
||||||
data: {
|
data: {
|
||||||
members: {
|
members: {
|
||||||
disconnect: { id: context.userId },
|
disconnect: { id: context.userId },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
include: { members: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Publish room updated event
|
||||||
|
pubsub.publish(ROOM_UPDATED, { roomUpdated: updatedRoom });
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -133,6 +144,9 @@ export const roomResolvers = {
|
||||||
roomAdded: {
|
roomAdded: {
|
||||||
subscribe: () => pubsub.asyncIterator([ROOM_ADDED]),
|
subscribe: () => pubsub.asyncIterator([ROOM_ADDED]),
|
||||||
},
|
},
|
||||||
|
roomUpdated: {
|
||||||
|
subscribe: () => pubsub.asyncIterator([ROOM_UPDATED]),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Room: {
|
Room: {
|
||||||
messages: async (parent: any) => {
|
messages: async (parent: any) => {
|
||||||
|
|
|
@ -59,5 +59,6 @@ export const typeDefs = gql`
|
||||||
type Subscription {
|
type Subscription {
|
||||||
messageAdded(roomId: ID!): Message!
|
messageAdded(roomId: ID!): Message!
|
||||||
roomAdded: Room!
|
roomAdded: Room!
|
||||||
|
roomUpdated: Room!
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -211,23 +211,49 @@ button:disabled {
|
||||||
}
|
}
|
||||||
|
|
||||||
.room-item {
|
.room-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
cursor: pointer;
|
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
transition: background-color 0.2s;
|
border: 1px solid var(--border-color);
|
||||||
|
transition: border-color 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.room-item:hover {
|
.room-info {
|
||||||
|
flex: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-info.selected {
|
||||||
|
background-color: rgba(79, 70, 229, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-info:hover {
|
||||||
background-color: rgba(79, 70, 229, 0.1);
|
background-color: rgba(79, 70, 229, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.room-item.selected {
|
.join-button {
|
||||||
background-color: rgba(79, 70, 229, 0.2);
|
padding: 0.4rem 0.8rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
margin-left: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.room-name {
|
.room-name {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-badge {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: white;
|
||||||
|
padding: 0.1rem 0.4rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.room-description {
|
.room-description {
|
||||||
|
@ -259,6 +285,60 @@ button:disabled {
|
||||||
.chat-header {
|
.chat-header {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
border-bottom: 1px solid var(--border-color);
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leave-button {
|
||||||
|
padding: 0.4rem 0.8rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
background-color: var(--error-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.leave-button:hover {
|
||||||
|
background-color: #dc2626;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-leave {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-leave span {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--error-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-leave button {
|
||||||
|
padding: 0.3rem 0.6rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-leave button:first-of-type {
|
||||||
|
background-color: var(--error-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-leave button:last-of-type {
|
||||||
|
background-color: var(--secondary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
color: var(--error-color);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-main {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-header h2 {
|
.chat-header h2 {
|
||||||
|
|
|
@ -45,6 +45,10 @@ function App() {
|
||||||
setSelectedRoomId(roomId);
|
setSelectedRoomId(roomId);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleLeaveRoom = () => {
|
||||||
|
setSelectedRoomId(null);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Provider value={client}>
|
<Provider value={client}>
|
||||||
<div class='app'>
|
<div class='app'>
|
||||||
|
@ -95,18 +99,20 @@ function App() {
|
||||||
/>
|
/>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<div class='chat-content'>
|
<main class='chat-main'>
|
||||||
<Show
|
<Show when={selectedRoomId()}>
|
||||||
when={selectedRoomId()}
|
<ChatRoom
|
||||||
fallback={
|
roomId={selectedRoomId() || ''}
|
||||||
<div class='select-room-message'>
|
userId={userId() || ''}
|
||||||
Select a room to start chatting
|
onLeaveRoom={handleLeaveRoom}
|
||||||
</div>
|
/>
|
||||||
}
|
|
||||||
>
|
|
||||||
<ChatRoom roomId={selectedRoomId()!} userId={userId()} />
|
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
<Show when={!selectedRoomId()}>
|
||||||
|
<div class='select-room-message'>
|
||||||
|
Select a room to start chatting or create a new one
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
</main>
|
</main>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { createSignal, createEffect, For, Show } from 'solid-js';
|
import { createSignal, createEffect, For, Show } from 'solid-js';
|
||||||
import { gql } from '@urql/core';
|
import { gql } from '@urql/core';
|
||||||
import { createQuery, createMutation, createSubscription } from '@urql/solid';
|
import { createQuery, createMutation, createSubscription } from '@urql/solid';
|
||||||
import { Message, Room } from '../types';
|
import { Message } from '../types';
|
||||||
|
|
||||||
const ROOM_QUERY = gql`
|
const ROOM_QUERY = gql`
|
||||||
query GetRoom($id: ID!) {
|
query GetRoom($id: ID!) {
|
||||||
|
@ -18,19 +18,14 @@ const ROOM_QUERY = gql`
|
||||||
id
|
id
|
||||||
username
|
username
|
||||||
}
|
}
|
||||||
}
|
messages {
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const MESSAGES_QUERY = gql`
|
|
||||||
query GetMessages($roomId: ID!) {
|
|
||||||
messages(roomId: $roomId) {
|
|
||||||
id
|
|
||||||
content
|
|
||||||
createdAt
|
|
||||||
user {
|
|
||||||
id
|
id
|
||||||
username
|
content
|
||||||
|
createdAt
|
||||||
|
user {
|
||||||
|
id
|
||||||
|
username
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,25 +59,35 @@ const MESSAGE_SUBSCRIPTION = gql`
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const LEAVE_ROOM_MUTATION = gql`
|
||||||
|
mutation LeaveRoom($roomId: ID!) {
|
||||||
|
leaveRoom(roomId: $roomId)
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
interface ChatRoomProps {
|
interface ChatRoomProps {
|
||||||
roomId: string;
|
roomId: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
|
onLeaveRoom?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ChatRoom(props: ChatRoomProps) {
|
export function ChatRoom(props: ChatRoomProps) {
|
||||||
|
const [variables, setVariables] = createSignal({
|
||||||
|
id: props.roomId,
|
||||||
|
roomId: props.roomId,
|
||||||
|
});
|
||||||
const [message, setMessage] = createSignal('');
|
const [message, setMessage] = createSignal('');
|
||||||
const [messages, setMessages] = createSignal<Message[]>([]);
|
const [messages, setMessages] = createSignal<Message[]>([]);
|
||||||
|
const [confirmLeave, setConfirmLeave] = createSignal(false);
|
||||||
|
const [leaveError, setLeaveError] = createSignal<string | null>(null);
|
||||||
|
|
||||||
// Query room details
|
// Query room details
|
||||||
const [roomQuery] = createQuery({
|
const [roomQuery] = createQuery({
|
||||||
query: ROOM_QUERY,
|
query: ROOM_QUERY,
|
||||||
variables: { id: props.roomId },
|
variables: variables,
|
||||||
});
|
context: {
|
||||||
|
requestPolicy: 'network-only', // Force refetch when variables change
|
||||||
// Query messages
|
},
|
||||||
const [messagesQuery] = createQuery({
|
|
||||||
query: MESSAGES_QUERY,
|
|
||||||
variables: { roomId: props.roomId },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Send message mutation
|
// Send message mutation
|
||||||
|
@ -91,14 +96,28 @@ export function ChatRoom(props: ChatRoomProps) {
|
||||||
// Subscribe to new messages
|
// Subscribe to new messages
|
||||||
const [messageSubscription] = createSubscription({
|
const [messageSubscription] = createSubscription({
|
||||||
query: MESSAGE_SUBSCRIPTION,
|
query: MESSAGE_SUBSCRIPTION,
|
||||||
variables: { roomId: props.roomId },
|
variables: variables,
|
||||||
|
pause: false, // Ensure subscription is active
|
||||||
|
});
|
||||||
|
|
||||||
|
// Leave room mutation
|
||||||
|
const [leaveRoomResult, leaveRoom] = createMutation(LEAVE_ROOM_MUTATION);
|
||||||
|
|
||||||
|
// Reset messages when room changes
|
||||||
|
createEffect(() => {
|
||||||
|
// Access props.roomId to create a dependency
|
||||||
|
setVariables({ id: props.roomId, roomId: props.roomId });
|
||||||
|
// Reset messages when room changes
|
||||||
|
// Clear any errors or confirmations
|
||||||
|
setLeaveError(null);
|
||||||
|
setConfirmLeave(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Load initial messages
|
// Load initial messages
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const result = messagesQuery;
|
const result = roomQuery;
|
||||||
if (result.data?.messages) {
|
if (result.data?.room) {
|
||||||
setMessages(result.data.messages);
|
setMessages(result.data.room.messages);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -111,6 +130,14 @@ export function ChatRoom(props: ChatRoomProps) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Handle leave room error
|
||||||
|
createEffect(() => {
|
||||||
|
if (leaveRoomResult.error) {
|
||||||
|
setLeaveError(leaveRoomResult.error.message);
|
||||||
|
setConfirmLeave(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const handleSendMessage = async (e: Event) => {
|
const handleSendMessage = async (e: Event) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
@ -127,6 +154,30 @@ export function ChatRoom(props: ChatRoomProps) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleLeaveRoom = async () => {
|
||||||
|
if (!confirmLeave()) {
|
||||||
|
setConfirmLeave(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLeaveError(null);
|
||||||
|
try {
|
||||||
|
const result = await leaveRoom({ roomId: props.roomId });
|
||||||
|
if (result.data?.leaveRoom) {
|
||||||
|
if (props.onLeaveRoom) {
|
||||||
|
props.onLeaveRoom();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to leave room:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancelLeave = () => {
|
||||||
|
setConfirmLeave(false);
|
||||||
|
setLeaveError(null);
|
||||||
|
};
|
||||||
|
|
||||||
const formatTime = (dateString: string) => {
|
const formatTime = (dateString: string) => {
|
||||||
const date = new Date(dateString);
|
const date = new Date(dateString);
|
||||||
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||||
|
@ -138,6 +189,23 @@ export function ChatRoom(props: ChatRoomProps) {
|
||||||
<div class='chat-header'>
|
<div class='chat-header'>
|
||||||
<h2>{roomQuery.data?.room.name}</h2>
|
<h2>{roomQuery.data?.room.name}</h2>
|
||||||
<p>{roomQuery.data?.room.description}</p>
|
<p>{roomQuery.data?.room.description}</p>
|
||||||
|
<div class='room-actions'>
|
||||||
|
<Show when={leaveError()}>
|
||||||
|
<div class='error-message'>{leaveError()}</div>
|
||||||
|
</Show>
|
||||||
|
<Show when={confirmLeave()}>
|
||||||
|
<div class='confirm-leave'>
|
||||||
|
<span>Are you sure you want to leave this room?</span>
|
||||||
|
<button onClick={handleLeaveRoom}>Yes</button>
|
||||||
|
<button onClick={cancelLeave}>No</button>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
<Show when={!confirmLeave()}>
|
||||||
|
<button class='leave-button' onClick={handleLeaveRoom}>
|
||||||
|
Leave Room
|
||||||
|
</button>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { createSignal, createEffect, For, Show } from 'solid-js';
|
import { createSignal, createEffect, For, Show } from 'solid-js';
|
||||||
import { gql } from '@urql/core';
|
import { gql } from '@urql/core';
|
||||||
import { createQuery, createSubscription } from '@urql/solid';
|
import { createQuery, createSubscription, createMutation } from '@urql/solid';
|
||||||
import { Room } from '../types';
|
import { Room } from '../types';
|
||||||
|
|
||||||
const ROOMS_QUERY = gql`
|
const ROOMS_QUERY = gql`
|
||||||
|
@ -11,6 +11,9 @@ const ROOMS_QUERY = gql`
|
||||||
description
|
description
|
||||||
isPrivate
|
isPrivate
|
||||||
createdAt
|
createdAt
|
||||||
|
members {
|
||||||
|
id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -23,6 +26,36 @@ const ROOM_ADDED_SUBSCRIPTION = gql`
|
||||||
description
|
description
|
||||||
isPrivate
|
isPrivate
|
||||||
createdAt
|
createdAt
|
||||||
|
members {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ROOM_UPDATED_SUBSCRIPTION = gql`
|
||||||
|
subscription OnRoomUpdated {
|
||||||
|
roomUpdated {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
description
|
||||||
|
isPrivate
|
||||||
|
createdAt
|
||||||
|
members {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const JOIN_ROOM_MUTATION = gql`
|
||||||
|
mutation JoinRoom($roomId: ID!) {
|
||||||
|
joinRoom(roomId: $roomId) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
members {
|
||||||
|
id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -34,10 +67,15 @@ interface RoomListProps {
|
||||||
|
|
||||||
export function RoomList(props: RoomListProps) {
|
export function RoomList(props: RoomListProps) {
|
||||||
const [rooms, setRooms] = createSignal<Room[]>([]);
|
const [rooms, setRooms] = createSignal<Room[]>([]);
|
||||||
|
const [error, setError] = createSignal<string | null>(null);
|
||||||
|
const [isJoining, setIsJoining] = createSignal(false);
|
||||||
|
|
||||||
// Query rooms
|
// Query rooms
|
||||||
const [roomsQuery] = createQuery({
|
const [roomsQuery] = createQuery({
|
||||||
query: ROOMS_QUERY,
|
query: ROOMS_QUERY,
|
||||||
|
context: {
|
||||||
|
requestPolicy: 'network-only',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Subscribe to new rooms
|
// Subscribe to new rooms
|
||||||
|
@ -45,6 +83,14 @@ export function RoomList(props: RoomListProps) {
|
||||||
query: ROOM_ADDED_SUBSCRIPTION,
|
query: ROOM_ADDED_SUBSCRIPTION,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Subscribe to room updates (when members change)
|
||||||
|
const [roomUpdatedSubscription] = createSubscription({
|
||||||
|
query: ROOM_UPDATED_SUBSCRIPTION,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Join room mutation
|
||||||
|
const [joinRoomResult, joinRoom] = createMutation(JOIN_ROOM_MUTATION);
|
||||||
|
|
||||||
// Load initial rooms
|
// Load initial rooms
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const result = roomsQuery;
|
const result = roomsQuery;
|
||||||
|
@ -68,18 +114,93 @@ export function RoomList(props: RoomListProps) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Handle room updates from subscription
|
||||||
|
createEffect(() => {
|
||||||
|
const result = roomUpdatedSubscription;
|
||||||
|
if (result.data?.roomUpdated) {
|
||||||
|
const updatedRoom = result.data.roomUpdated;
|
||||||
|
setRooms((prev) => {
|
||||||
|
return prev.map((room) => {
|
||||||
|
if (room.id === updatedRoom.id) {
|
||||||
|
return { ...room, ...updatedRoom };
|
||||||
|
}
|
||||||
|
return room;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle join room error
|
||||||
|
createEffect(() => {
|
||||||
|
if (joinRoomResult.error) {
|
||||||
|
setError(joinRoomResult.error.message);
|
||||||
|
setIsJoining(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleJoinRoom = async (roomId: string, event: MouseEvent) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
setError(null);
|
||||||
|
setIsJoining(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await joinRoom({ roomId });
|
||||||
|
if (result.data?.joinRoom) {
|
||||||
|
// Update the local room data with the joined room
|
||||||
|
setRooms((prev) =>
|
||||||
|
prev.map((room) =>
|
||||||
|
room.id === roomId
|
||||||
|
? { ...room, members: result.data.joinRoom.members }
|
||||||
|
: room
|
||||||
|
)
|
||||||
|
);
|
||||||
|
// Select the room after joining
|
||||||
|
props.onSelectRoom(roomId);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to join room:', err);
|
||||||
|
} finally {
|
||||||
|
setIsJoining(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if the current user is a member of a room
|
||||||
|
const isMember = (room: Room) => {
|
||||||
|
const userId = localStorage.getItem('userId');
|
||||||
|
return room.members?.some((member) => member.id === userId);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class='room-list'>
|
<div class='room-list'>
|
||||||
<h2>Chat Rooms</h2>
|
<h2>Chat Rooms</h2>
|
||||||
|
<Show when={error()}>
|
||||||
|
<div class='error-message'>{error()}</div>
|
||||||
|
</Show>
|
||||||
<Show when={!roomsQuery.fetching} fallback={<div>Loading rooms...</div>}>
|
<Show when={!roomsQuery.fetching} fallback={<div>Loading rooms...</div>}>
|
||||||
<For each={rooms()}>
|
<For each={rooms()}>
|
||||||
{(room) => (
|
{(room) => (
|
||||||
<div
|
<div class='room-item'>
|
||||||
class={`room-item ${props.selectedRoomId === room.id ? 'selected' : ''}`}
|
<div
|
||||||
onClick={() => props.onSelectRoom(room.id)}
|
class={`room-info ${props.selectedRoomId === room.id ? 'selected' : ''}`}
|
||||||
>
|
onClick={() =>
|
||||||
<div class='room-name'>{room.name}</div>
|
isMember(room) ? props.onSelectRoom(room.id) : null
|
||||||
<div class='room-description'>{room.description}</div>
|
}
|
||||||
|
>
|
||||||
|
<div class='room-name'>
|
||||||
|
{room.name}
|
||||||
|
{isMember(room) && <span class='member-badge'>Member</span>}
|
||||||
|
</div>
|
||||||
|
<div class='room-description'>{room.description}</div>
|
||||||
|
</div>
|
||||||
|
<Show when={!isMember(room)}>
|
||||||
|
<button
|
||||||
|
class='join-button'
|
||||||
|
onClick={(e) => handleJoinRoom(room.id, e)}
|
||||||
|
disabled={isJoining()}
|
||||||
|
>
|
||||||
|
{isJoining() ? 'Joining...' : 'Join'}
|
||||||
|
</button>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
|
|
Loading…
Add table
Reference in a new issue