Skip to content

Commit fa8d7e6

Browse files
committed
feat: chat pagination
1 parent fafe04b commit fa8d7e6

File tree

1 file changed

+167
-13
lines changed
  • platforms/pictique/src/routes/(protected)/messages

1 file changed

+167
-13
lines changed

platforms/pictique/src/routes/(protected)/messages/+page.svelte

Lines changed: 167 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,36 @@
2121
let groupName = $state('');
2222
let debounceTimer: NodeJS.Timeout;
2323
24-
async function loadMessages() {
24+
// Pagination and loading state
25+
let isLoading = $state(true);
26+
let currentPage = $state(1);
27+
let totalPages = $state(1);
28+
let totalChats = $state(0);
29+
let hasMorePages = $state(false);
30+
31+
async function loadMessages(page = 1, append = false) {
2532
try {
26-
const { data } = await apiClient.get<{ chats: Chat[] }>('/api/chats');
33+
isLoading = true;
34+
const { data } = await apiClient.get<{
35+
chats: Chat[];
36+
total: number;
37+
page: number;
38+
totalPages: number;
39+
}>(`/api/chats?page=${page}&limit=10`);
40+
2741
const { data: userData } = await apiClient.get('/api/users');
2842
currentUserId = userData.id;
2943
3044
console.log('Raw chat data from API:', data.chats);
3145
32-
// Show all chats (direct messages and groups) in one unified list
33-
messages = data.chats.map((c) => {
46+
// Update pagination info
47+
currentPage = data.page;
48+
totalPages = data.totalPages;
49+
totalChats = data.total;
50+
hasMorePages = data.page < data.totalPages;
51+
52+
// Transform chats to messages
53+
const newMessages = data.chats.map((c) => {
3454
const members = c.participants.filter((u) => u.id !== userData.id);
3555
const memberNames = members.map((m) => m.name ?? m.handle ?? m.ename);
3656
const isGroup = members.length > 1;
@@ -57,8 +77,35 @@
5777
name: displayName
5878
};
5979
});
80+
81+
// Append or replace messages based on pagination
82+
if (append) {
83+
messages = [...messages, ...newMessages];
84+
} else {
85+
messages = newMessages;
86+
}
6087
} catch (error) {
6188
console.error('Failed to load messages:', error);
89+
} finally {
90+
isLoading = false;
91+
}
92+
}
93+
94+
async function loadNextPage() {
95+
if (hasMorePages && !isLoading) {
96+
await loadMessages(currentPage + 1, true);
97+
}
98+
}
99+
100+
async function loadPreviousPage() {
101+
if (currentPage > 1 && !isLoading) {
102+
await loadMessages(currentPage - 1, false);
103+
}
104+
}
105+
106+
async function goToPage(page: number) {
107+
if (page >= 1 && page <= totalPages && !isLoading) {
108+
await loadMessages(page, false);
62109
}
63110
}
64111
@@ -197,7 +244,7 @@
197244
// Navigate to messages and refresh the feed
198245
goto('/messages');
199246
// Refresh the messages to show the newly created group
200-
await loadMessages();
247+
await loadMessages(1, false);
201248
} catch (err) {
202249
console.error('Failed to create group:', err);
203250
alert('Failed to create group. Please try again.');
@@ -218,7 +265,14 @@
218265
</Button>
219266
</div>
220267

221-
{#if messages.length > 0}
268+
{#if isLoading && messages.length === 0}
269+
<div class="flex items-center justify-center py-8">
270+
<div
271+
class="h-8 w-8 animate-spin rounded-full border-2 border-gray-300 border-t-blue-600"
272+
></div>
273+
<span class="ml-3 text-gray-500">Loading chats...</span>
274+
</div>
275+
{:else if messages.length > 0}
222276
{#each messages as message}
223277
<Message
224278
class="mb-2"
@@ -232,16 +286,105 @@
232286
}}
233287
/>
234288
{/each}
235-
{/if}
236289

237-
{#if messages.length === 0}
290+
<!-- Pagination Controls -->
291+
{#if totalPages > 1}
292+
<div class="mt-6 flex items-center justify-between">
293+
<Button
294+
variant="secondary"
295+
size="sm"
296+
callback={loadPreviousPage}
297+
disabled={currentPage <= 1 || isLoading}
298+
>
299+
Previous
300+
</Button>
301+
302+
<div class="flex items-center space-x-2">
303+
{#if totalPages <= 7}
304+
{#each Array.from({ length: totalPages }, (_, i) => i + 1) as page}
305+
<button
306+
class="rounded px-3 py-1 text-sm {page === currentPage
307+
? 'bg-blue-600 text-white'
308+
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'}"
309+
onclick={() => goToPage(page)}
310+
disabled={isLoading}
311+
>
312+
{page}
313+
</button>
314+
{/each}
315+
{:else}
316+
<!-- Show first page -->
317+
<button
318+
class="rounded px-3 py-1 text-sm {1 === currentPage
319+
? 'bg-blue-600 text-white'
320+
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'}"
321+
onclick={() => goToPage(1)}
322+
disabled={isLoading}
323+
>
324+
1
325+
</button>
326+
327+
{#if currentPage > 3}
328+
<span class="text-gray-500">...</span>
329+
{/if}
330+
331+
<!-- Show pages around current page -->
332+
{#each Array.from({ length: Math.min(3, totalPages - 2) }, (_, i) => {
333+
const start = Math.max(2, currentPage - 1);
334+
return Math.min(start + i, totalPages - 1);
335+
}).filter((page, index, arr) => arr.indexOf(page) === index) as page}
336+
<button
337+
class="rounded px-3 py-1 text-sm {page === currentPage
338+
? 'bg-blue-600 text-white'
339+
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'}"
340+
onclick={() => goToPage(page)}
341+
disabled={isLoading}
342+
>
343+
{page}
344+
</button>
345+
{/each}
346+
347+
{#if currentPage < totalPages - 2}
348+
<span class="text-gray-500">...</span>
349+
{/if}
350+
351+
<!-- Show last page -->
352+
{#if totalPages > 1}
353+
<button
354+
class="rounded px-3 py-1 text-sm {totalPages === currentPage
355+
? 'bg-blue-600 text-white'
356+
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'}"
357+
onclick={() => goToPage(totalPages)}
358+
disabled={isLoading}
359+
>
360+
{totalPages}
361+
</button>
362+
{/if}
363+
{/if}
364+
</div>
365+
366+
<Button
367+
variant="secondary"
368+
size="sm"
369+
callback={loadNextPage}
370+
disabled={!hasMorePages || isLoading}
371+
>
372+
Next
373+
</Button>
374+
</div>
375+
376+
<div class="mt-2 text-center text-sm text-gray-500">
377+
Page {currentPage} of {totalPages} • {totalChats} total chats
378+
</div>
379+
{/if}
380+
{:else if !isLoading}
238381
<div class="w-full px-5 py-5 text-center text-sm text-gray-500">
239382
You don't have any messages yet. Start a Direct Message by searching a name.
240383
</div>
241384
{/if}
242385

243386
{#if openNewChatModal}
244-
<div class="bg-opacity-50 fixed inset-0 z-50 flex items-center justify-center bg-black">
387+
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50">
245388
<div
246389
class="w-[90vw] max-w-md rounded-3xl border border-gray-200 bg-white p-6 shadow-xl"
247390
>
@@ -250,6 +393,7 @@
250393
<button
251394
onclick={() => (openNewChatModal = false)}
252395
class="rounded-full p-2 hover:bg-gray-100"
396+
aria-label="Close modal"
253397
>
254398
<svg
255399
class="h-5 w-5 text-gray-500"
@@ -328,7 +472,9 @@
328472
<Button
329473
size="sm"
330474
variant="secondary"
331-
callback={() => (openNewChatModal = false)}
475+
callback={() => {
476+
openNewChatModal = false;
477+
}}
332478
>
333479
Cancel
334480
</Button>
@@ -348,7 +494,7 @@
348494

349495
<!-- New Group Modal -->
350496
{#if openNewGroupModal}
351-
<div class="bg-opacity-50 fixed inset-0 z-50 flex items-center justify-center bg-black">
497+
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50">
352498
<div
353499
class="w-[90vw] max-w-md rounded-3xl border border-gray-200 bg-white p-6 shadow-xl"
354500
>
@@ -357,6 +503,7 @@
357503
<button
358504
onclick={() => (openNewGroupModal = false)}
359505
class="rounded-full p-2 hover:bg-gray-100"
506+
aria-label="Close modal"
360507
>
361508
<svg
362509
class="h-5 w-5 text-gray-500"
@@ -391,10 +538,14 @@
391538

392539
<!-- Member Search -->
393540
<div>
394-
<label class="mb-2 block text-sm font-medium text-gray-700">
541+
<label
542+
for="memberSearch"
543+
class="mb-2 block text-sm font-medium text-gray-700"
544+
>
395545
Search Users by Name
396546
</label>
397547
<Input
548+
id="memberSearch"
398549
type="text"
399550
bind:value={searchValue}
400551
placeholder="Type a name to search..."
@@ -466,6 +617,7 @@
466617
<button
467618
onclick={() => toggleMemberSelection(member.id)}
468619
class="text-blue-600 hover:text-blue-800"
620+
aria-label="Remove member"
469621
>
470622
<svg
471623
class="h-4 w-4"
@@ -492,7 +644,9 @@
492644
<Button
493645
size="sm"
494646
variant="secondary"
495-
callback={() => (openNewGroupModal = false)}
647+
callback={() => {
648+
openNewGroupModal = false;
649+
}}
496650
>
497651
Cancel
498652
</Button>

0 commit comments

Comments
 (0)