|
18 | 18 | let openNewGroupModal = $state(false); |
19 | 19 | let searchValue = $state(''); |
20 | 20 | let groupName = $state(''); |
| 21 | + let isStartingChat = $state(false); |
21 | 22 | let debounceTimer: NodeJS.Timeout; |
22 | 23 |
|
23 | 24 | // Pagination and loading state |
|
189 | 190 | }, 300); |
190 | 191 | } |
191 | 192 |
|
192 | | - async function createChat() { |
193 | | - if (selectedMembers.length === 0) return; |
| 193 | + function openNewMessageModal() { |
| 194 | + searchValue = ''; |
| 195 | + searchResults.set([]); |
| 196 | + openNewChatModal = true; |
| 197 | + } |
| 198 | +
|
| 199 | + async function startDirectMessage(member: { id: string; name?: string; handle?: string }) { |
| 200 | + if (isStartingChat) return; |
194 | 201 |
|
| 202 | + isStartingChat = true; |
195 | 203 | try { |
196 | | - if (selectedMembers.length === 1) { |
197 | | - // Create direct message |
198 | | - await apiClient.post('/api/chats', { |
199 | | - name: allMembers.find((m) => m.id === selectedMembers[0])?.name ?? 'New Chat', |
200 | | - participantIds: [selectedMembers[0]] |
201 | | - }); |
202 | | - } else { |
203 | | - // Create group chat |
204 | | - const groupMembers = allMembers.filter((m) => m.id === selectedMembers[0]); |
205 | | - const groupName = groupMembers.map((m) => m.name ?? m.handle ?? m.ename).join(', '); |
206 | | -
|
207 | | - // Create group chat via API |
208 | | - await apiClient.post('/api/chats', { |
209 | | - name: groupName, |
210 | | - participantIds: selectedMembers, |
211 | | - isGroup: true |
212 | | - }); |
213 | | - } |
| 204 | + // Omit `name` so the API treats this as a 1-to-1 chat and returns the |
| 205 | + // existing conversation between these two users instead of duplicating it. |
| 206 | + const { data: chat } = await apiClient.post<Chat>('/api/chats', { |
| 207 | + participantIds: [member.id] |
| 208 | + }); |
214 | 209 |
|
215 | | - // Navigate to the new chat instead of hard refresh |
216 | | - if (selectedMembers.length === 1) { |
217 | | - // For direct messages, we need to find the chat ID |
218 | | - // For now, redirect to messages and let the user click on the new chat |
219 | | - goto('/messages'); |
220 | | - } else { |
221 | | - // For group chats, redirect to messages |
222 | | - goto('/messages'); |
223 | | - } |
224 | | - } catch (err) { |
225 | | - console.error('Failed to create chat:', err); |
226 | | - alert('Failed to create chat. Please try again.'); |
227 | | - } finally { |
| 210 | + heading.set(member.name ?? member.handle ?? 'Chat'); |
228 | 211 | openNewChatModal = false; |
229 | | - selectedMembers = []; |
230 | 212 | searchValue = ''; |
| 213 | + searchResults.set([]); |
| 214 | +
|
| 215 | + goto(`/messages/${chat.id}`); |
| 216 | + } catch (err) { |
| 217 | + console.error('Failed to start chat:', err); |
| 218 | + alert('Failed to start chat. Please try again.'); |
| 219 | + } finally { |
| 220 | + isStartingChat = false; |
231 | 221 | } |
232 | 222 | } |
233 | 223 |
|
|
261 | 251 |
|
262 | 252 | <div class="flex"> |
263 | 253 | <section class="flex-1 px-4 py-4"> |
264 | | - <Button |
265 | | - variant="secondary" |
266 | | - size="sm" |
267 | | - callback={() => { |
268 | | - openNewGroupModal = true; |
269 | | - }} |
270 | | - class="mb-4 ml-auto w-min text-nowrap" |
271 | | - > |
272 | | - New Group |
273 | | - </Button> |
| 254 | + <div class="mb-4 ml-auto flex w-min items-center gap-2"> |
| 255 | + <Button |
| 256 | + variant="secondary" |
| 257 | + size="sm" |
| 258 | + callback={openNewMessageModal} |
| 259 | + class="w-min text-nowrap" |
| 260 | + > |
| 261 | + New Message |
| 262 | + </Button> |
| 263 | + <Button |
| 264 | + variant="secondary" |
| 265 | + size="sm" |
| 266 | + callback={() => { |
| 267 | + openNewGroupModal = true; |
| 268 | + }} |
| 269 | + class="w-min text-nowrap" |
| 270 | + > |
| 271 | + New Group |
| 272 | + </Button> |
| 273 | + </div> |
274 | 274 |
|
275 | 275 | {#if isLoading && messages.length === 0} |
276 | 276 | <div class="flex items-center justify-center py-8"> |
|
328 | 328 | class="w-[90vw] max-w-md rounded-3xl border border-gray-200 bg-white p-6 shadow-xl" |
329 | 329 | > |
330 | 330 | <div class="mb-6 flex items-center justify-between"> |
331 | | - <h2 class="text-xl font-semibold text-gray-900">Start a New Chat</h2> |
| 331 | + <h2 class="text-xl font-semibold text-gray-900">New Message</h2> |
332 | 332 | <button |
333 | 333 | onclick={() => (openNewChatModal = false)} |
334 | 334 | class="rounded-full p-2 hover:bg-gray-100" |
|
365 | 365 | <div class="text-center text-red-500">{$searchError}</div> |
366 | 366 | {:else if $searchResults.length === 0 && searchValue.trim()} |
367 | 367 | <div class="text-center text-gray-500">No users found</div> |
| 368 | + {:else if !searchValue.trim()} |
| 369 | + <div class="py-2 text-center text-sm text-gray-500"> |
| 370 | + Search for someone to start a conversation. |
| 371 | + </div> |
368 | 372 | {/if} |
369 | 373 |
|
370 | 374 | {#if $searchResults.length > 0} |
371 | | - <div class="max-h-[250px] space-y-3 overflow-y-auto"> |
| 375 | + <div class="max-h-75 space-y-1 overflow-y-auto"> |
372 | 376 | {#each $searchResults.filter((m) => m.id !== currentUserId) as member} |
373 | | - <label |
374 | | - class="flex cursor-pointer items-center space-x-3 rounded-lg p-3 hover:bg-gray-50" |
| 377 | + <button |
| 378 | + type="button" |
| 379 | + onclick={() => startDirectMessage(member)} |
| 380 | + disabled={isStartingChat} |
| 381 | + class="flex w-full cursor-pointer items-center space-x-3 rounded-lg p-3 text-left hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-50" |
375 | 382 | > |
376 | | - <input |
377 | | - type="checkbox" |
378 | | - checked={selectedMembers.includes(member.id)} |
379 | | - onchange={(e: Event) => { |
380 | | - toggleMemberSelection(member.id); |
381 | | - return; |
382 | | - }} |
383 | | - class="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500" |
384 | | - /> |
385 | 383 | <Avatar src={member.avatarUrl} size="sm" /> |
386 | 384 | <div class="flex flex-col"> |
387 | 385 | <span class="text-sm font-medium text-gray-900" |
|
393 | 391 | > |
394 | 392 | {/if} |
395 | 393 | </div> |
396 | | - </label> |
| 394 | + </button> |
397 | 395 | {/each} |
398 | 396 | </div> |
399 | 397 | {/if} |
400 | | - |
401 | | - {#if selectedMembers.length > 0} |
402 | | - <div class="rounded-lg bg-blue-50 p-3"> |
403 | | - <p class="text-sm text-blue-800"> |
404 | | - {selectedMembers.length === 1 |
405 | | - ? 'Direct message will be created' |
406 | | - : `Group chat with ${selectedMembers.length} members will be created`} |
407 | | - </p> |
408 | | - </div> |
409 | | - {/if} |
410 | | - |
411 | | - <div class="flex justify-end gap-3 pt-4"> |
412 | | - <Button |
413 | | - size="sm" |
414 | | - variant="secondary" |
415 | | - callback={() => { |
416 | | - openNewChatModal = false; |
417 | | - }} |
418 | | - > |
419 | | - Cancel |
420 | | - </Button> |
421 | | - <Button |
422 | | - size="sm" |
423 | | - variant="primary" |
424 | | - callback={() => createChat()} |
425 | | - disabled={selectedMembers.length === 0} |
426 | | - > |
427 | | - {selectedMembers.length === 1 ? 'Start Chat' : 'Create Group'} |
428 | | - </Button> |
429 | | - </div> |
430 | 398 | </div> |
431 | 399 | </div> |
432 | 400 | </div> |
|
0 commit comments