Skip to content

Commit a419df9

Browse files
grv-saini-20sosweetham
authored andcommitted
feat: new group functionality and textarea fixed
1 parent a0ff772 commit a419df9

File tree

2 files changed

+157
-13
lines changed

2 files changed

+157
-13
lines changed

platforms/pictique/src/lib/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,9 @@ export type Image = {
4747
url: string;
4848
alt: string;
4949
};
50+
51+
export type GroupInfo = {
52+
id: string;
53+
name: string;
54+
avatar: string;
55+
};
Lines changed: 151 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,35 @@
11
<script lang="ts">
22
import { goto } from '$app/navigation';
3-
import { Message } from '$lib/fragments';
43
import { onMount } from 'svelte';
5-
import { apiClient } from '$lib/utils/axios';
6-
import { heading } from '../../store';
4+
import { Message } from '$lib/fragments';
75
import Group from '$lib/fragments/Group/Group.svelte';
6+
import { Button, Avatar, Input } from '$lib/ui';
7+
import { clickOutside } from '$lib/utils';
8+
import { heading } from '../../store';
9+
import { apiClient } from '$lib/utils/axios';
10+
11+
import {
12+
searchUsers,
13+
searchResults,
14+
isSearching,
15+
searchError
16+
} from '$lib/stores/users';
17+
import type { GroupInfo } from '$lib/types';
818
919
let messages = $state([]);
20+
let groups: GroupInfo[] = $state([]);
21+
let allMembers = $state([]);
22+
let selectedMembers = $state<string[]>([]);
23+
let currentUserId = '';
24+
let openNewChatModal = $state(false);
25+
let searchValue = $state('');
26+
let debounceTimer: NodeJS.Timeout;
1027
11-
onMount(async () => {
28+
async function loadMessages() {
1229
const { data } = await apiClient.get('/api/chats');
1330
const { data: userData } = await apiClient.get('/api/users');
31+
currentUserId = userData.id;
32+
1433
messages = data.chats.map((c) => {
1534
const members = c.participants.filter((u) => u.id !== userData.id);
1635
const memberNames = members.map((m) => m.name ?? m.handle ?? m.ename);
@@ -23,14 +42,80 @@
2342
text: c.latestMessage?.text ?? 'No message yet'
2443
};
2544
});
45+
}
46+
47+
onMount(async () => {
48+
await loadMessages();
49+
50+
const memberRes = await apiClient.get('/api/members');
51+
allMembers = memberRes.data;
2652
});
53+
54+
function toggleMemberSelection(id: string) {
55+
if (selectedMembers.includes(id)) {
56+
selectedMembers = selectedMembers.filter((m) => m !== id);
57+
} else {
58+
selectedMembers = [...selectedMembers, id];
59+
}
60+
}
61+
62+
function handleSearch(value: string) {
63+
searchValue = value;
64+
clearTimeout(debounceTimer);
65+
debounceTimer = setTimeout(() => {
66+
searchUsers(value);
67+
}, 300);
68+
}
69+
70+
async function createChat() {
71+
if (selectedMembers.length === 0) return;
72+
73+
try {
74+
if (selectedMembers.length === 1) {
75+
await apiClient.post(`/api/chats/`, {
76+
name:
77+
allMembers.find((m) => m.id === selectedMembers[0])?.name ??
78+
'New Chat',
79+
participantIds: [selectedMembers[0]]
80+
});
81+
await loadMessages(); // 🛠️ Refresh to include the new direct message
82+
} else {
83+
const groupMembers = allMembers.filter((m) =>
84+
selectedMembers.includes(m.id)
85+
);
86+
const groupName = groupMembers.map((m) => m.name ?? m.handle ?? m.ename).join(', ');
87+
groups = [
88+
...groups,
89+
{
90+
id: Math.random().toString(36).slice(2),
91+
name: groupName,
92+
avatar: '/images/group.png'
93+
}
94+
];
95+
}
96+
} catch (err) {
97+
console.error('Failed to create chat:', err);
98+
} finally {
99+
openNewChatModal = false;
100+
selectedMembers = [];
101+
searchValue = '';
102+
}
103+
}
27104
</script>
28105

29-
<section>
30-
{#if messages && messages.length > 0}
106+
107+
<section class="px-4 py-4">
108+
<div class="flex justify-end mb-4">
109+
<Button variant="secondary" size="sm" callback={() => {(openNewChatModal = true)}}>
110+
New Chat
111+
</Button>
112+
</div>
113+
114+
{#if messages.length > 0}
115+
<h3 class="text-md font-semibold text-gray-700 mb-2">Messages</h3>
31116
{#each messages as message}
32117
<Message
33-
class="mb-6"
118+
class="mb-2"
34119
avatar={message.avatar}
35120
username={message.username}
36121
text={message.text}
@@ -41,12 +126,65 @@
41126
}}
42127
/>
43128
{/each}
44-
{:else}
45-
<div class="w-full px-5 py-5 text-center">
46-
You don't have any messages yet, please start a Direct Message with Someone by searching
47-
their name
129+
{/if}
130+
131+
{#if groups.length > 0}
132+
<h3 class="text-md font-semibold text-gray-700 mb-2 mt-6">Groups</h3>
133+
{#each groups as group}
134+
<Group
135+
name={group.name || "New Group"}
136+
avatar={group.avatar}
137+
unread={true}
138+
callback={() => goto(`/group/${group.id}`)}
139+
/>
140+
{/each}
141+
{:else if messages.length === 0}
142+
<div class="w-full px-5 py-5 text-center text-sm text-gray-500">
143+
You don't have any messages yet. Start a Direct Message by searching a name.
48144
</div>
49-
<!-- group id needs to be added -->
50-
<Group name="Developers" avatar="https://picsum.photos/200/300" unread={true} callback={() => goto("/group/123")}/>
51145
{/if}
146+
147+
<dialog
148+
open={openNewChatModal}
149+
use:clickOutside={() => (openNewChatModal = false)}
150+
onclose={() => (openNewChatModal = false)}
151+
class="w-[90vw] md:max-w-[40vw] z-50 absolute start-[50%] top-[50%] translate-x-[-50%] translate-y-[-50%] p-4 border border-gray-400 rounded-3xl bg-white shadow-xl"
152+
>
153+
<div class="bg-white rounded-xl w-full p-6 space-y-6 relative">
154+
<h2 class="text-xl font-semibold">Start a New Chat</h2>
155+
156+
<Input
157+
type="text"
158+
bind:value={searchValue}
159+
placeholder="Search users..."
160+
oninput={(e: Event) => handleSearch((e.target as HTMLInputElement).value)}
161+
/>
162+
163+
{#if $isSearching}
164+
<div class="text-gray-500 mt-2">Searching...</div>
165+
{:else if $searchError}
166+
<div class="text-red-500 mt-2">{$searchError}</div>
167+
{/if}
168+
169+
<div class="max-h-[250px] overflow-y-auto space-y-3">
170+
{#each $searchResults.filter(m => m.id !== currentUserId) as member}
171+
<label class="flex items-center space-x-3 cursor-pointer">
172+
<input
173+
type="checkbox"
174+
checked={selectedMembers.includes(member.id)}
175+
onchange={() => toggleMemberSelection(member.id)}
176+
class="accent-brand focus:ring"
177+
/>
178+
<Avatar src={member.avatarUrl} size="sm" />
179+
<span class="text-sm">{member.name ?? member.handle ?? member.ename}</span>
180+
</label>
181+
{/each}
182+
</div>
183+
184+
<div class="flex justify-end gap-2 pt-4">
185+
<Button size="sm" variant="secondary" callback={() => {(openNewChatModal = false)}}>Cancel</Button>
186+
<Button size="sm" variant="primary" callback={createChat}>Start Chat</Button>
187+
</div>
188+
</div>
189+
</dialog>
52190
</section>

0 commit comments

Comments
 (0)