Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"@internxt/css-config": "^1.1.0",
"@internxt/lib": "^1.4.1",
"@internxt/sdk": "^1.15.7",
"@internxt/ui": "^0.1.11",
"@internxt/ui": "^0.1.12",
"@phosphor-icons/react": "^2.1.10",
"@reduxjs/toolkit": "^2.11.2",
"@tailwindcss/vite": "^4.2.1",
Expand Down
6 changes: 3 additions & 3 deletions src/features/mail/MailView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const MailView = ({ folder }: MailViewProps) => {
const { translate } = useTranslationContext();
const [activeMailId, setActiveMailId] = useState<string | undefined>(undefined);

const { isLoadingListFolder, listFolder, hasMore: hasMoreItems, onLoadMore } = useListFolderPaginated(folder);
const { isLoadingListFolder, listFolderEmails, hasMoreEmails, onLoadMore } = useListFolderPaginated(folder);
const { data: activeMail } = useGetMailMessageQuery({ emailId: activeMailId! }, { skip: !activeMailId });
const [markAsRead] = useMarkAsReadMutation();

Expand Down Expand Up @@ -49,12 +49,12 @@ const MailView = ({ folder }: MailViewProps) => {
{/* Tray */}
<TrayList
folderName={folderName}
listFolder={listFolder?.emails}
listFolder={listFolderEmails}
isLoadingListFolder={isLoadingListFolder}
activeMailId={activeMailId}
onMailSelected={onSelectEmail}
loadMore={onLoadMore}
hasMoreItems={hasMoreItems}
hasMoreItems={hasMoreEmails}
/>
{/* Mail Preview */}
<div className="flex flex-col w-full">
Expand Down
18 changes: 9 additions & 9 deletions src/hooks/mail/useListFolderPaginated.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ describe('List Folder Paginated - custom hook', () => {
waitFor(() => {
expect(result.current.isLoadingListFolder).toBe(true);

expect(result.current.listFolder?.emails).toHaveLength(DEFAULT_FOLDER_LIMIT);
expect(result.current.hasMore).toBeTruthy();
expect(result.current.listFolderEmails).toHaveLength(DEFAULT_FOLDER_LIMIT);
expect(result.current.hasMoreEmails).toBeTruthy();
});
});

Expand All @@ -46,7 +46,7 @@ describe('List Folder Paginated - custom hook', () => {
wrapper: createWrapper(store),
});

expect(result.current.hasMore).toBeFalsy();
expect(result.current.hasMoreEmails).toBeFalsy();
});

test('When the user scrolls to the end of the list, then the next batch of emails is loaded and appended', async () => {
Expand All @@ -67,9 +67,9 @@ describe('List Folder Paginated - custom hook', () => {
});

waitFor(() => {
expect(result.current.listFolder?.emails).toHaveLength(DEFAULT_FOLDER_LIMIT * 2);
expect(result.current.listFolder?.emails).toStrictEqual([...page1.emails, ...page2.emails]);
expect(result.current.hasMore).toBeFalsy();
expect(result.current.listFolderEmails).toHaveLength(DEFAULT_FOLDER_LIMIT * 2);
expect(result.current.listFolderEmails).toStrictEqual([...page1.emails, ...page2.emails]);
expect(result.current.hasMoreEmails).toBeFalsy();
});
});

Expand Down Expand Up @@ -105,14 +105,14 @@ describe('List Folder Paginated - custom hook', () => {
});

waitFor(() => {
expect(result.current.listFolder?.emails).toStrictEqual(inboxEmails.emails);
expect(result.current.listFolderEmails).toStrictEqual(inboxEmails.emails);
});

rerender({ mailbox: 'sent' });

waitFor(() => {
expect(result.current.listFolder?.emails).toStrictEqual(sentEmails.emails);
expect(result.current.listFolder?.emails).toHaveLength(DEFAULT_FOLDER_LIMIT);
expect(result.current.listFolderEmails).toStrictEqual(sentEmails.emails);
expect(result.current.listFolderEmails).toHaveLength(DEFAULT_FOLDER_LIMIT);
});
});
});
22 changes: 8 additions & 14 deletions src/hooks/mail/useListFolderPaginated.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
/* eslint-disable react-hooks/set-state-in-effect */
import { DEFAULT_FOLDER_LIMIT } from '@/constants';
import { useGetListFolderQuery } from '@/store/api/mail';
import type { FolderType } from '@/types/mail';
import { useEffect, useState } from 'react';
import { useState } from 'react';

const useListFolderPaginated = (mailbox: FolderType) => {
const [position, setPosition] = useState(0);

useEffect(() => {
setPosition(0);
}, [mailbox]);
const [anchorId, setAnchorId] = useState<string | undefined>(undefined);

const {
data: listFolder,
Expand All @@ -19,26 +14,25 @@
{
mailbox,
limit: DEFAULT_FOLDER_LIMIT,
position,
anchorId,

Check failure on line 17 in src/hooks/mail/useListFolderPaginated.ts

View workflow job for this annotation

GitHub Actions / ci (24.x)

Object literal may only specify known properties, and 'anchorId' does not exist in type '{ mailbox?: "inbox" | "sent" | "drafts" | "spam" | "trash" | "archive" | undefined; limit?: number | undefined; position?: number | undefined; }'.
},
{
pollingInterval: 30000,
skip: !mailbox,
},
);

const hasMore = (listFolder?.emails.length ?? 0) < (listFolder?.total ?? 0);

const onLoadMore = () => {
if (isFetching || !hasMore) return;
setPosition((prev) => prev + DEFAULT_FOLDER_LIMIT);
if (isFetching || !listFolder?.hasMoreMails) return;

Check failure on line 26 in src/hooks/mail/useListFolderPaginated.ts

View workflow job for this annotation

GitHub Actions / ci (24.x)

Property 'hasMoreMails' does not exist on type '{ emails: { id: string; threadId: string; from: { name?: string | undefined; email: string; }[]; to: { name?: string | undefined; email: string; }[]; subject: string; receivedAt: string; preview: string; isRead: boolean; isFlagged: boolean; hasAttachment: boolean; size: number; }[]; total: number; }'.

setAnchorId(listFolder?.nextAnchor);

Check failure on line 28 in src/hooks/mail/useListFolderPaginated.ts

View workflow job for this annotation

GitHub Actions / ci (24.x)

Property 'nextAnchor' does not exist on type '{ emails: { id: string; threadId: string; from: { name?: string | undefined; email: string; }[]; to: { name?: string | undefined; email: string; }[]; subject: string; receivedAt: string; preview: string; isRead: boolean; isFlagged: boolean; hasAttachment: boolean; size: number; }[]; total: number; }'.
};

return {
listFolder,
listFolderEmails: listFolder?.emails,
isLoadingListFolder,
onLoadMore,
hasMore,
hasMoreEmails: listFolder?.hasMoreMails,

Check failure on line 35 in src/hooks/mail/useListFolderPaginated.ts

View workflow job for this annotation

GitHub Actions / ci (24.x)

Property 'hasMoreMails' does not exist on type '{ emails: { id: string; threadId: string; from: { name?: string | undefined; email: string; }[]; to: { name?: string | undefined; email: string; }[]; subject: string; receivedAt: string; preview: string; isRead: boolean; isFlagged: boolean; hasAttachment: boolean; size: number; }[]; total: number; }'.
};
};

Expand Down
15 changes: 4 additions & 11 deletions src/services/sdk/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ import type { ApiSecurity, AppDetails } from '@internxt/sdk/dist/shared';
import packageJson from '../../../package.json';
import { ConfigService } from '../config';
import { LocalStorageService } from '../local-storage';
import { AuthService } from './auth';
import { NavigationService } from '../navigation';
import { AppView } from '@/routes/paths';
import { store } from '@/store';
import { logoutThunk } from '@/store/slices/user/thunks';

export type SdkManagerApiSecurity = ApiSecurity & { newToken: string };

Expand Down Expand Up @@ -33,14 +32,8 @@ export class SdkManager {
private getNewTokenApiSecurity(): ApiSecurity {
return {
token: LocalStorageService.instance?.getToken() ?? '',
unauthorizedCallback: () => {
if (LocalStorageService.instance) {
LocalStorageService.instance.clearCredentials();
AuthService.instance.logOut().catch((error) => {
console.error(error);
});
NavigationService.instance.navigate({ id: AppView.Welcome });
}
unauthorizedCallback: async () => {
await store.dispatch(logoutThunk());
},
};
}
Expand Down
16 changes: 11 additions & 5 deletions src/services/sdk/sdk.service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,14 @@ import { beforeEach, describe, expect, test, vi, afterEach } from 'vitest';
import { SdkManager } from '.';
import { ConfigService } from '../config';
import { LocalStorageService } from '../local-storage';
import { AuthService } from './auth';
import { NavigationService } from '../navigation';
import { store } from '@/store';

vi.mock('@/store', () => ({
store: {
dispatch: vi.fn(),
},
}));

vi.mock('./auth', () => ({
AuthService: {
Expand Down Expand Up @@ -202,7 +208,7 @@ describe('SDK Manager', () => {
const securityArg = (Drive.Users.client as any).mock.calls[0][2];
securityArg.unauthorizedCallback();

expect(AuthService.instance.logOut).toHaveBeenCalled();
expect(store.dispatch).toHaveBeenCalled();
});
});

Expand Down Expand Up @@ -232,7 +238,7 @@ describe('SDK Manager', () => {
const securityArg = (Drive.Storage.client as any).mock.calls[0][2];
securityArg.unauthorizedCallback();

expect(AuthService.instance.logOut).toHaveBeenCalled();
expect(store.dispatch).toHaveBeenCalled();
});
});

Expand Down Expand Up @@ -262,7 +268,7 @@ describe('SDK Manager', () => {
const securityArg = (Drive.Payments.client as any).mock.calls[0][2];
securityArg.unauthorizedCallback();

expect(AuthService.instance.logOut).toHaveBeenCalled();
expect(store.dispatch).toHaveBeenCalled();
});
});

Expand Down Expand Up @@ -292,7 +298,7 @@ describe('SDK Manager', () => {
const securityArg = (Mail.client as any).mock.calls[0][2];
securityArg.unauthorizedCallback();

expect(AuthService.instance.logOut).toHaveBeenCalled();
expect(store.dispatch).toHaveBeenCalled();
});
});
});
15 changes: 7 additions & 8 deletions src/store/api/mail/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,17 @@
getListFolder: builder.query<EmailListResponse, ListEmailsQuery>({
serializeQueryArgs: ({ queryArgs }) => ({ mailbox: queryArgs?.mailbox }),
merge: (currentCache, newItems, { arg }) => {
const currentPosition = arg?.position ?? 0;

// This prevents the concatenation of the existent cached emails with the new ones (repeated emails)
if (currentPosition === 0) {
currentCache.emails = newItems.emails;
} else {
// No anchorId means first page — replace instead of accumulate
if (arg?.anchorId) {

Check failure on line 26 in src/store/api/mail/index.ts

View workflow job for this annotation

GitHub Actions / ci (24.x)

Property 'anchorId' does not exist on type '{ mailbox?: "inbox" | "sent" | "drafts" | "spam" | "trash" | "archive" | undefined; limit?: number | undefined; position?: number | undefined; }'.
currentCache.emails.push(...newItems.emails);
} else {
currentCache.emails = newItems.emails;
}
currentCache.total = newItems.total;
currentCache.hasMoreMails = newItems.hasMoreMails;

Check failure on line 31 in src/store/api/mail/index.ts

View workflow job for this annotation

GitHub Actions / ci (24.x)

Property 'hasMoreMails' does not exist on type '{ emails: { id: string; threadId: string; from: { name?: string | undefined; email: string; }[]; to: { name?: string | undefined; email: string; }[]; subject: string; receivedAt: string; preview: string; isRead: boolean; isFlagged: boolean; hasAttachment: boolean; size: number; }[]; total: number; }'.

Check failure on line 31 in src/store/api/mail/index.ts

View workflow job for this annotation

GitHub Actions / ci (24.x)

Property 'hasMoreMails' does not exist on type '{ emails: { id: string; threadId: string; from: { name?: string | undefined; email: string; }[]; to: { name?: string | undefined; email: string; }[]; subject: string; receivedAt: string; preview: string; isRead: boolean; isFlagged: boolean; hasAttachment: boolean; size: number; }[]; total: number; }'.
currentCache.nextAnchor = newItems.nextAnchor;

Check failure on line 32 in src/store/api/mail/index.ts

View workflow job for this annotation

GitHub Actions / ci (24.x)

Property 'nextAnchor' does not exist on type '{ emails: { id: string; threadId: string; from: { name?: string | undefined; email: string; }[]; to: { name?: string | undefined; email: string; }[]; subject: string; receivedAt: string; preview: string; isRead: boolean; isFlagged: boolean; hasAttachment: boolean; size: number; }[]; total: number; }'.

Check failure on line 32 in src/store/api/mail/index.ts

View workflow job for this annotation

GitHub Actions / ci (24.x)

Property 'nextAnchor' does not exist on type '{ emails: { id: string; threadId: string; from: { name?: string | undefined; email: string; }[]; to: { name?: string | undefined; email: string; }[]; subject: string; receivedAt: string; preview: string; isRead: boolean; isFlagged: boolean; hasAttachment: boolean; size: number; }[]; total: number; }'.
},
forceRefetch: ({ currentArg, previousArg }) =>
currentArg?.mailbox !== previousArg?.mailbox || currentArg?.position !== previousArg?.position,
currentArg?.mailbox !== previousArg?.mailbox || currentArg?.anchorId !== previousArg?.anchorId,

Check failure on line 35 in src/store/api/mail/index.ts

View workflow job for this annotation

GitHub Actions / ci (24.x)

Property 'anchorId' does not exist on type '{ mailbox?: "inbox" | "sent" | "drafts" | "spam" | "trash" | "archive" | undefined; limit?: number | undefined; position?: number | undefined; }'.
async queryFn(query) {
try {
const mailList = await MailService.instance.listFolder(query);
Expand Down
6 changes: 3 additions & 3 deletions src/store/api/mail/mail.api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ describe('Mail Query', () => {
const store = createTestStore();
await store.dispatch(mailApi.endpoints.getListFolder.initiate(query as ListEmailsQuery));
await store.dispatch(
mailApi.endpoints.getListFolder.initiate({ ...query, position: DEFAULT_FOLDER_LIMIT } as ListEmailsQuery),
mailApi.endpoints.getListFolder.initiate({ ...query, anchorId: 'anchor-page-2' } as ListEmailsQuery),
);

const state = store.getState() as unknown as RootState;
Expand All @@ -111,7 +111,7 @@ describe('Mail Query', () => {
const cache = mailApi.endpoints.getListFolder.select(query as ListEmailsQuery)(state);

expect(cache.data?.emails).toHaveLength(DEFAULT_FOLDER_LIMIT);
expect(cache.data?.emails).toEqual(reload.emails);
expect(cache.data?.emails).toStrictEqual(reload.emails);
});
});

Expand Down Expand Up @@ -141,7 +141,7 @@ describe('Mail Query', () => {
});

describe('Mark Mail As Read', () => {
const mailboxQuery = { mailbox: 'inbox', limit: DEFAULT_FOLDER_LIMIT, position: 0 } as ListEmailsQuery;
const mailboxQuery = { mailbox: 'inbox', limit: DEFAULT_FOLDER_LIMIT } as ListEmailsQuery;

const setupOptimisticStore = async () => {
const mockedMails = getMockedMails();
Expand Down
1 change: 1 addition & 0 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export default defineConfig({
alias: {
'@': path.resolve(__dirname, './src'),
},
preserveSymlinks: true,
},
optimizeDeps: {
include: ['@internxt/sdk'],
Expand Down
Loading