diff --git a/package-lock.json b/package-lock.json
index 3849289..8fed374 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,8 +11,8 @@
"dependencies": {
"@internxt/css-config": "^1.1.0",
"@internxt/lib": "^1.4.1",
- "@internxt/sdk": "^1.17.10",
- "@internxt/ui": "^0.1.16",
+ "@internxt/sdk": "^1.17.11",
+ "@internxt/ui": "^0.1.25",
"@phosphor-icons/react": "^2.1.10",
"@reduxjs/toolkit": "^2.11.2",
"@tailwindcss/vite": "^4.2.1",
@@ -1455,18 +1455,18 @@
"license": "MIT"
},
"node_modules/@internxt/sdk": {
- "version": "1.17.10",
- "resolved": "https://registry.npmjs.org/@internxt/sdk/-/sdk-1.17.10.tgz",
- "integrity": "sha512-gxpXc70c9pYVcQepTDwyhkB6zJ8i7Ea87FJNArMYwIliRNJllsNjdwQ8DV/MqH6iVY5EqbOfNHP3ZfurCdCq/w==",
+ "version": "1.17.11",
+ "resolved": "https://registry.npmjs.org/@internxt/sdk/-/sdk-1.17.11.tgz",
+ "integrity": "sha512-t9rTaNNli2GdnFTEJPgcI2MAX2LouKzVbFIBj6QUVlCk7Ivcj+hbkkZqVlHDNTvsw3UObq2ehSNYWmzkMgcbfQ==",
"license": "MIT",
"dependencies": {
"axios": "^1.16.0"
}
},
"node_modules/@internxt/ui": {
- "version": "0.1.16",
- "resolved": "https://registry.npmjs.org/@internxt/ui/-/ui-0.1.16.tgz",
- "integrity": "sha512-V9VQrUVZehvx5L3FChkCfwmOq/wnQsFSSjcfGHMb70SVKZQAfCSv3ZyjcnL5iIaLInWGwtwspmtMfl3qh0zVWQ==",
+ "version": "0.1.25",
+ "resolved": "https://registry.npmjs.org/@internxt/ui/-/ui-0.1.25.tgz",
+ "integrity": "sha512-xtDHnzLA3ml4FBLuNY15wm8vPQ59J4qSzim7sb/bXKZpJqlpt+kHbDtpLzzB6P+/l1QthXVO4VGDH0m44rlpug==",
"license": "MIT",
"dependencies": {
"@internxt/css-config": "1.1.0",
diff --git a/package.json b/package.json
index 79411df..54c7d41 100644
--- a/package.json
+++ b/package.json
@@ -24,8 +24,8 @@
"dependencies": {
"@internxt/css-config": "^1.1.0",
"@internxt/lib": "^1.4.1",
- "@internxt/sdk": "^1.17.10",
- "@internxt/ui": "^0.1.16",
+ "@internxt/sdk": "^1.17.11",
+ "@internxt/ui": "^0.1.25",
"@phosphor-icons/react": "^2.1.10",
"@reduxjs/toolkit": "^2.11.2",
"@tailwindcss/vite": "^4.2.1",
diff --git a/src/features/mail/MailView.tsx b/src/features/mail/MailView.tsx
index 225d078..379300c 100644
--- a/src/features/mail/MailView.tsx
+++ b/src/features/mail/MailView.tsx
@@ -51,7 +51,7 @@ const MailView = ({ folder }: MailViewProps) => {
toggleUnreadFilter,
} = useListFolderPaginated(folder);
- const { selectedEmails, selectAll, selectNone, selectRead, selectUnread, toggleSelectAll } =
+ const { selectedEmails, selectAll, selectEmail, selectNone, selectRead, selectUnread, toggleSelectAll } =
useMailSelection(listFolderEmails);
const { listActionContext, bulkActionContext } = useListActionContext(folder, selectedEmails, {
selectAll,
@@ -134,6 +134,7 @@ const MailView = ({ folder }: MailViewProps) => {
onLoadMore={onLoadMore}
emptyState={}
onMailSelected={onSelectEmail}
+ onMailChecked={selectEmail}
/>
diff --git a/src/features/mail/components/tray/search/components/list/index.tsx b/src/features/mail/components/tray/search/components/list/index.tsx
index 06bda4e..96f5ab5 100644
--- a/src/features/mail/components/tray/search/components/list/index.tsx
+++ b/src/features/mail/components/tray/search/components/list/index.tsx
@@ -42,7 +42,13 @@ const SearchEmailList = ({ mails, hasMoreItems, loading, onLoadMore, onMailSelec
>
{formattedMails.map((email) => (
-
+
))}
diff --git a/src/hooks/mail/useMailSelection.ts b/src/hooks/mail/useMailSelection.ts
index 3891fe0..1d55f73 100644
--- a/src/hooks/mail/useMailSelection.ts
+++ b/src/hooks/mail/useMailSelection.ts
@@ -10,6 +10,14 @@ export const useMailSelection = (emails: EmailListResponse['emails'] | undefined
const selectNone = () => setSelectedEmails([]);
+ const selectEmail = (emailId: string) => {
+ setSelectedEmails((prev) => {
+ if (prev.includes(emailId)) return prev.filter((id) => id !== emailId);
+ if (!emails?.some((email) => email.id === emailId)) return prev;
+ return [...prev, emailId];
+ });
+ };
+
const selectRead = () => {
setSelectedEmails(emails?.filter((email) => email.isRead).map((email) => email.id) ?? []);
};
@@ -31,6 +39,7 @@ export const useMailSelection = (emails: EmailListResponse['emails'] | undefined
return {
selectedEmails,
selectAll,
+ selectEmail,
selectNone,
selectRead,
selectUnread,
diff --git a/src/test-utils/fixtures.ts b/src/test-utils/fixtures.ts
index 7425cb9..f2f59bf 100644
--- a/src/test-utils/fixtures.ts
+++ b/src/test-utils/fixtures.ts
@@ -169,6 +169,7 @@ export const getMockedMail = (overrides?: Partial): EmailResponse
hasAttachment: faker.datatype.boolean(),
size: faker.number.int({ min: 1024, max: 16384 }),
attachments: faker.datatype.boolean() ? [getMockedAttachment()] : [],
+ isDraft: false,
...overrides,
});
@@ -176,6 +177,7 @@ export const getMockedMails = (count = 3): EmailListResponse => ({
emails: Array.from({ length: count }, () => {
const mail = getMockedMail();
return {
+ isDraft: false,
id: mail.id,
mailboxIds: mail.mailboxIds,
threadId: mail.threadId,
diff --git a/src/utils/format-emails/index.test.ts b/src/utils/format-emails/index.test.ts
index 0a5df9c..567cb6b 100644
--- a/src/utils/format-emails/index.test.ts
+++ b/src/utils/format-emails/index.test.ts
@@ -28,11 +28,34 @@ describe('Formatting emails to list format', () => {
expect(item.subject).toBe(emails[i].subject);
expect(item.body).toBe(emails[i].preview);
expect(item.read).toBe(emails[i].isRead);
- expect(item.from.avatar).toBe('');
+ expect(item.from).toEqual(expect.arrayContaining([expect.objectContaining({ avatar: '' })]));
expect(DateService.formatMailTimestamp).toHaveBeenCalledWith(emails[0].receivedAt);
});
});
+ test('When the row carries thread participants, then those are used instead of the message sender', () => {
+ const { emails } = getMockedMails(1);
+ emails[0].from = [{ email: 'alice@inxt.me', name: 'Alice' }];
+ (emails[0] as { participants?: { email: string; name?: string }[] }).participants = [
+ { email: 'alice@inxt.me', name: 'Alice' },
+ { email: 'bob@inxt.me', name: 'Bob' },
+ ];
+
+ const result = formatEmailsToList(emails);
+
+ expect(result?.[0].from.map((p) => p.name)).toEqual(['Alice', 'Bob']);
+ });
+
+ test('When the row has no participants, then the message sender is used as fallback', () => {
+ const { emails } = getMockedMails(1);
+ emails[0].from = [{ email: 'alice@inxt.me', name: 'Alice' }];
+ delete (emails[0] as { participants?: unknown }).participants;
+
+ const result = formatEmailsToList(emails);
+
+ expect(result?.[0].from.map((p) => p.name)).toEqual(['Alice']);
+ });
+
test('When a row is encrypted, then it uses the decrypted preview when available and never the ciphertext', () => {
const { emails } = getMockedMails(2);
emails.forEach((e) => {
diff --git a/src/utils/format-emails/index.ts b/src/utils/format-emails/index.ts
index f39c5a2..a6a8053 100644
--- a/src/utils/format-emails/index.ts
+++ b/src/utils/format-emails/index.ts
@@ -1,18 +1,22 @@
import { DateService } from '@/services/date';
import type { EmailListResponse } from '@internxt/sdk/dist/mail/types';
+type EmailSummary = EmailListResponse['emails'][number];
+
+const toParticipant = (sender: EmailSummary['from'][number]) => ({
+ name: sender.name ?? sender.email.split('@')[0],
+ avatar: '',
+});
+
export const formatEmailsToList = (
listFolderEmails?: EmailListResponse['emails'],
decryptedPreviews?: Record,
) => {
return listFolderEmails?.map((mail) => ({
id: mail.id,
- from: {
- name: mail.from[0]?.name ?? mail.from[0]?.email ?? '',
- avatar: '',
- },
+ from: (mail.participants?.length ? mail.participants : mail.from).map(toParticipant),
subject: mail.subject,
- createdAt: DateService.formatMailTimestamp(mail.receivedAt),
+ createdAt: DateService.formatMailTimestamp(mail.lastReceivedAt ?? mail.receivedAt),
body: decryptedPreviews?.[mail.id] ?? (mail.encryption ? '' : mail.preview),
read: mail.isRead,
}));