+
diff --git a/platforms/blabsy/src/lib/context/chat-context.tsx b/platforms/blabsy/src/lib/context/chat-context.tsx
index 4d6801db..e2fae190 100644
--- a/platforms/blabsy/src/lib/context/chat-context.tsx
+++ b/platforms/blabsy/src/lib/context/chat-context.tsx
@@ -94,14 +94,29 @@ export function ChatContextProvider({
const chatsQuery = query(
chatsCollection,
- where('participants', 'array-contains', user.id)
+ where('participants', 'array-contains', user.id),
+ orderBy('updatedAt', 'desc')
);
const unsubscribe = onSnapshot(
chatsQuery,
(snapshot) => {
const chatsData = snapshot.docs.map((doc) => doc.data());
- setChats(chatsData);
+
+ // Sort chats by last message timestamp (most recent first)
+ const sortedChats = chatsData.sort((a, b) => {
+ // If both have lastMessage, sort by timestamp
+ if (a.lastMessage?.timestamp && b.lastMessage?.timestamp) {
+ return b.lastMessage.timestamp.toMillis() - a.lastMessage.timestamp.toMillis();
+ }
+ // If only one has lastMessage, prioritize it
+ if (a.lastMessage?.timestamp && !b.lastMessage?.timestamp) return -1;
+ if (!a.lastMessage?.timestamp && b.lastMessage?.timestamp) return 1;
+ // If neither has lastMessage, sort by updatedAt
+ return b.updatedAt.toMillis() - a.updatedAt.toMillis();
+ });
+
+ setChats(sortedChats);
setLoading(false);
},
(error) => {
diff --git a/platforms/pictique-api/src/services/ChatService.ts b/platforms/pictique-api/src/services/ChatService.ts
index 2111ba8c..e3172bdd 100644
--- a/platforms/pictique-api/src/services/ChatService.ts
+++ b/platforms/pictique-api/src/services/ChatService.ts
@@ -162,6 +162,11 @@ export class ChatService {
});
const savedMessage = await this.messageRepository.save(message);
+
+ // Update the chat's updatedAt timestamp to reflect the latest message
+ chat.updatedAt = new Date();
+ await this.chatRepository.save(chat);
+
console.log("Sent event", `chat:${chatId}`);
this.eventEmitter.emit(`chat:${chatId}`, [savedMessage]);
@@ -280,20 +285,32 @@ export class ChatService {
page: number;
totalPages: number;
}> {
- // First, get the chat IDs that the user is part of
- const [chatIds, total] = await this.chatRepository
+ // Get chats ordered by the most recent message timestamp
+ const queryBuilder = this.chatRepository
.createQueryBuilder("chat")
- .select(["chat.id", "chat.updatedAt"])
+ .leftJoin("chat.messages", "message")
.innerJoin("chat.participants", "participants")
.where("participants.id = :userId", { userId })
- .orderBy("chat.updatedAt", "DESC")
+ .groupBy("chat.id")
+ .addGroupBy("chat.name")
+ .addGroupBy("chat.ename")
+ .addGroupBy("chat.createdAt")
+ .addGroupBy("chat.updatedAt")
+ .orderBy("MAX(message.createdAt)", "DESC")
+ .addOrderBy("chat.createdAt", "DESC"); // Fallback for chats without messages
+
+ // Get total count for pagination
+ const total = await queryBuilder.getCount();
+
+ // Apply pagination
+ const chats = await queryBuilder
.skip((page - 1) * limit)
.take(limit)
- .getManyAndCount();
+ .getMany();
- // Then, load the full chat data with all relations
- const chats = await this.chatRepository.find({
- where: { id: In(chatIds.map((chat) => chat.id)) },
+ // Load full chat data with all relations for the paginated results
+ const chatsWithRelations = await this.chatRepository.find({
+ where: { id: In(chats.map((chat) => chat.id)) },
relations: [
"participants",
"messages",
@@ -301,12 +318,25 @@ export class ChatService {
"messages.readStatuses",
"messages.readStatuses.user",
],
- order: { updatedAt: "DESC" },
+ });
+
+ // Sort the chats by latest message timestamp (since we loaded relations, we need to sort again)
+ const sortedChats = chatsWithRelations.sort((a, b) => {
+ const aLatestMessage = a.messages[a.messages.length - 1];
+ const bLatestMessage = b.messages[b.messages.length - 1];
+
+ if (!aLatestMessage && !bLatestMessage) {
+ return b.createdAt.getTime() - a.createdAt.getTime();
+ }
+ if (!aLatestMessage) return 1;
+ if (!bLatestMessage) return -1;
+
+ return bLatestMessage.createdAt.getTime() - aLatestMessage.createdAt.getTime();
});
// For each chat, get the latest message and its read status
const chatsWithLatestMessage = await Promise.all(
- chats.map(async (chat) => {
+ sortedChats.map(async (chat) => {
const latestMessage = chat.messages[chat.messages.length - 1];
if (!latestMessage) {
return { ...chat, latestMessage: undefined };
diff --git a/platforms/pictique-api/src/services/MessageService.ts b/platforms/pictique-api/src/services/MessageService.ts
index a2732876..7e024673 100644
--- a/platforms/pictique-api/src/services/MessageService.ts
+++ b/platforms/pictique-api/src/services/MessageService.ts
@@ -1,8 +1,10 @@
import { AppDataSource } from "../database/data-source";
import { Message } from "../database/entities/Message";
+import { Chat } from "../database/entities/Chat";
export class MessageService {
public messageRepository = AppDataSource.getRepository(Message);
+ private chatRepository = AppDataSource.getRepository(Chat);
async findById(id: string): Promise {
return await this.messageRepository.findOneBy({ id });
@@ -17,7 +19,16 @@ export class MessageService {
isArchived: false
});
- return await this.messageRepository.save(message);
+ const savedMessage = await this.messageRepository.save(message);
+
+ // Update the chat's updatedAt timestamp to reflect the latest message
+ const chat = await this.chatRepository.findOneBy({ id: chatId });
+ if (chat) {
+ chat.updatedAt = new Date();
+ await this.chatRepository.save(chat);
+ }
+
+ return savedMessage;
}
async createSystemMessage(chatId: string, text: string): Promise {
diff --git a/platforms/pictique/src/routes/(protected)/messages/+page.svelte b/platforms/pictique/src/routes/(protected)/messages/+page.svelte
index 8526809e..c0697196 100644
--- a/platforms/pictique/src/routes/(protected)/messages/+page.svelte
+++ b/platforms/pictique/src/routes/(protected)/messages/+page.svelte
@@ -21,16 +21,36 @@
let groupName = $state('');
let debounceTimer: NodeJS.Timeout;
- async function loadMessages() {
+ // Pagination and loading state
+ let isLoading = $state(true);
+ let currentPage = $state(1);
+ let totalPages = $state(1);
+ let totalChats = $state(0);
+ let hasMorePages = $state(false);
+
+ async function loadMessages(page = 1, append = false) {
try {
- const { data } = await apiClient.get<{ chats: Chat[] }>('/api/chats');
+ isLoading = true;
+ const { data } = await apiClient.get<{
+ chats: Chat[];
+ total: number;
+ page: number;
+ totalPages: number;
+ }>(`/api/chats?page=${page}&limit=10`);
+
const { data: userData } = await apiClient.get('/api/users');
currentUserId = userData.id;
console.log('Raw chat data from API:', data.chats);
- // Show all chats (direct messages and groups) in one unified list
- messages = data.chats.map((c) => {
+ // Update pagination info
+ currentPage = data.page;
+ totalPages = data.totalPages;
+ totalChats = data.total;
+ hasMorePages = data.page < data.totalPages;
+
+ // Transform chats to messages
+ const newMessages = data.chats.map((c) => {
const members = c.participants.filter((u) => u.id !== userData.id);
const memberNames = members.map((m) => m.name ?? m.handle ?? m.ename);
const isGroup = members.length > 1;
@@ -57,8 +77,35 @@
name: displayName
};
});
+
+ // Append or replace messages based on pagination
+ if (append) {
+ messages = [...messages, ...newMessages];
+ } else {
+ messages = newMessages;
+ }
} catch (error) {
console.error('Failed to load messages:', error);
+ } finally {
+ isLoading = false;
+ }
+ }
+
+ async function loadNextPage() {
+ if (hasMorePages && !isLoading) {
+ await loadMessages(currentPage + 1, true);
+ }
+ }
+
+ async function loadPreviousPage() {
+ if (currentPage > 1 && !isLoading) {
+ await loadMessages(currentPage - 1, false);
+ }
+ }
+
+ async function goToPage(page: number) {
+ if (page >= 1 && page <= totalPages && !isLoading) {
+ await loadMessages(page, false);
}
}
@@ -197,7 +244,7 @@
// Navigate to messages and refresh the feed
goto('/messages');
// Refresh the messages to show the newly created group
- await loadMessages();
+ await loadMessages(1, false);
} catch (err) {
console.error('Failed to create group:', err);
alert('Failed to create group. Please try again.');
@@ -218,7 +265,14 @@
- {#if messages.length > 0}
+ {#if isLoading && messages.length === 0}
+
+ {:else if messages.length > 0}
{#each messages as message}
{/each}
- {/if}
- {#if messages.length === 0}
+
+ {#if totalPages > 1}
+
+
+
+
+ {#if totalPages <= 7}
+ {#each Array.from({ length: totalPages }, (_, i) => i + 1) as page}
+
+ {/each}
+ {:else}
+
+
+
+ {#if currentPage > 3}
+ ...
+ {/if}
+
+
+ {#each Array.from({ length: Math.min(3, totalPages - 2) }, (_, i) => {
+ const start = Math.max(2, currentPage - 1);
+ return Math.min(start + i, totalPages - 1);
+ }).filter((page, index, arr) => arr.indexOf(page) === index) as page}
+
+ {/each}
+
+ {#if currentPage < totalPages - 2}
+ ...
+ {/if}
+
+
+ {#if totalPages > 1}
+
+ {/if}
+ {/if}
+
+
+
+
+
+
+ Page {currentPage} of {totalPages} • {totalChats} total chats
+
+ {/if}
+ {:else if !isLoading}
You don't have any messages yet. Start a Direct Message by searching a name.
{/if}
{#if openNewChatModal}
-
+
@@ -250,6 +393,7 @@
@@ -348,7 +494,7 @@
{#if openNewGroupModal}
-