diff --git a/README.md b/README.md
index 3f61dec..9aae9d1 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,15 @@ A modern, high-performance webmail application built with the latest React ecosy
> 💬 **Join our community on Discord:** [discord.gg/29zTxvque](https://discord.gg/29zTxvque) — ask questions, report bugs, share ideas, and get involved.
+## 🔗 Related Projects
+
+| Project | Description |
+| --- | --- |
+| [Docs](https://webmail.yukthi.com/) | Documentation site |
+| [WebMail-API](https://github.com/Yukthi-Systems/WebMail-API) | Backend API |
+| [WebMail-BIMI-API](https://github.com/Yukthi-Systems/WebMail-BIMI-API) | BIMI API |
+| [WebMail-RMQ-Worker](https://github.com/Yukthi-Systems/WebMail-RMQ-Worker) | RabbitMQ worker |
+
## 📸 Screenshots
diff --git a/lint.md b/lint.md
new file mode 100644
index 0000000..dd2d888
--- /dev/null
+++ b/lint.md
@@ -0,0 +1,81 @@
+\E:\Apps\yukthi\Webmail-ui-OSS\src\components\common\MinimizedModalContext.tsx
+ 82:14 warning Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components react-refresh/only-export-components
+
+E:\Apps\yukthi\Webmail-ui-OSS\src\components\common\TourTips.tsx
+ 28:14 warning Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components react-refresh/only-export-components
+
+E:\Apps\yukthi\Webmail-ui-OSS\src\components\common\header\index.tsx
+ 209:6 warning React Hook useEffect has a missing dependency: 'userSettings.ui'. Either include it or remove the dependency array react-hooks/exhaustive-deps
+
+E:\Apps\yukthi\Webmail-ui-OSS\src\components\common\header\search\index.tsx
+ 242:6 warning React Hook useEffect has missing dependencies: 'PER_PAGE', 'filters', 'query', and 'setSearchState'. Either include them or remove the dependency array react-hooks/exhaustive-deps
+ 251:6 warning React Hook useEffect has a missing dependency: 'initialFilters'. Either include it or remove the dependency array react-hooks/exhaustive-deps
+ 343:6 warning React Hook useEffect has a missing dependency: 'initialFilters'. Either include it or remove the dependency array react-hooks/exhaustive-deps
+ 348:6 warning React Hook useEffect has a missing dependency: 'handleClearAll'. Either include it or remove the dependency array react-hooks/exhaustive-deps
+
+E:\Apps\yukthi\Webmail-ui-OSS\src\components\composer\AttachmentUploader.tsx
+ 48:14 warning Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components react-refresh/only-export-components
+ 64:14 warning Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components react-refresh/only-export-components
+
+E:\Apps\yukthi\Webmail-ui-OSS\src\components\composer\FolderSelectField.tsx
+ 121:6 warning React Hook useMemo has a missing dependency: 'renderDropdownItems'. Either include it or remove the dependency array react-hooks/exhaustive-deps
+
+E:\Apps\yukthi\Webmail-ui-OSS\src\components\composer\index.tsx
+ 56:14 warning Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components react-refresh/only-export-components
+ 200:6 warning React Hook useEffect has a missing dependency: 'messageId'. Either include it or remove the dependency array react-hooks/exhaustive-deps
+ 282:6 warning React Hook useEffect has missing dependencies: 'setComposerData' and 'setDraftMessageId'. Either include them or remove the dependency array react-hooks/exhaustive-deps
+ 313:5 warning React Hook useCallback has a missing dependency: 'setDraftMessageId'. Either include it or remove the dependency array react-hooks/exhaustive-deps
+ 462:6 warning React Hook useCallback has missing dependencies: 'saveDraft' and 'setDraftMessageId'. Either include them or remove the dependency array react-hooks/exhaustive-deps
+ 674:5 warning React Hook useCallback has missing dependencies: 'saveDraft', 'undoTime', and 'userSettings?.general?.reply_to'. Either include them or remove the dependency array react-hooks/exhaustive-deps
+ 759:6 warning React Hook useCallback has a missing dependency: 'toast'. Either include it or remove the dependency array react-hooks/exhaustive-deps
+ 819:6 warning React Hook useCallback has missing dependencies: 'closeComposer' and 'saveDraft'. Either include them or remove the dependency array react-hooks/exhaustive-deps
+ 845:6 warning React Hook useCallback has a missing dependency: 'closeComposer'. Either include it or remove the dependency array react-hooks/exhaustive-deps
+ 955:6 warning React Hook useEffect has a missing dependency: 'setComposerData'. Either include it or remove the dependency array react-hooks/exhaustive-deps
+
+E:\Apps\yukthi\Webmail-ui-OSS\src\components\mailbox\ComposeButton.tsx
+ 23:14 warning Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components react-refresh/only-export-components
+
+E:\Apps\yukthi\Webmail-ui-OSS\src\components\mailbox\CustomFolder.tsx
+ 49:14 warning Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components react-refresh/only-export-components
+
+E:\Apps\yukthi\Webmail-ui-OSS\src\components\mailbox\EmailAttachments.tsx
+ 584:6 warning React Hook useEffect has a missing dependency: 'blobUrls'. Either include it or remove the dependency array react-hooks/exhaustive-deps
+
+E:\Apps\yukthi\Webmail-ui-OSS\src\components\mailbox\EmailComposer.tsx
+ 544:6 warning React Hook useEffect has missing dependencies: 'email', 'isInitialized', 'resetComposerData', and 'setComposerData'. Either include them or remove the dependency array react-hooks/exhaustive-deps
+
+E:\Apps\yukthi\Webmail-ui-OSS\src\components\mailbox\EmailList.tsx
+ 358:6 warning React Hook useEffect has missing dependencies: 'currentFolderDetail', 'folder', 'folderDetails', 'queryClient', and 'setFolderDetails'. Either include them or remove the dependency array
+ react-hooks/exhaustive-deps
+ 514:6 warning React Hook useEffect has a missing dependency: 'isMobile'. Either include it or remove the dependency array
+ react-hooks/exhaustive-deps
+ 604:6 warning React Hook useMemo has missing dependencies: 'folder', 'isFolderThread', 'searchState.isActive', 'simpleEmailArray', and 'threadedView'. Either include them or remove the dependency array
+ react-hooks/exhaustive-deps
+ 611:6 warning React Hook useEffect has a missing dependency: 'setViewingEmailFlag'. Either include it or remove the dependency array
+ react-hooks/exhaustive-deps
+ 776:6 warning React Hook useEffect has a missing dependency: 'toast'. Either include it or remove the dependency array
+ react-hooks/exhaustive-deps
+ 782:6 warning React Hook useEffect has a missing dependency: 'handleBackToList'. Either include it or remove the dependency array
+ react-hooks/exhaustive-deps
+ 782:48 warning React Hook useEffect has a complex expression in the dependency array. Extract it to a separate variable so it can be statically checked
+ react-hooks/exhaustive-deps
+ 795:6 warning React Hook useEffect has a missing dependency: 'isMobile'. Either include it or remove the dependency array
+ react-hooks/exhaustive-deps
+ 815:6 warning React Hook useEffect has missing dependencies: 'folder', 'isMobile', and 'layout'. Either include them or remove the dependency array
+ react-hooks/exhaustive-deps
+ 1532:6 warning React Hook useEffect has a missing dependency: 'handleBackToList'. Either include it or remove the dependency array
+ react-hooks/exhaustive-deps
+ 1743:5 warning React Hook useCallback has missing dependencies: 'handleBackToList', 'handleEditAsNew', 'handleForward', 'handleForwardAsAttachment', 'handleReply', 'handleReplyAll', 'handleSaveAsContactAction', 'handleSendDraft', 'handleSingleEmailDelete', 'handleSingleEmailMarkAsFlagged', 'handleSingleEmailMarkAsRead', and 'layout'. Either include them or remove the dependency array react-hooks/exhaustive-deps
+ 1781:6 warning React Hook useEffect has a missing dependency: 'handleRefresh'. Either include it or remove the dependency array
+ react-hooks/exhaustive-deps
+
+E:\Apps\yukthi\Webmail-ui-OSS\src\components\mailbox\MoveEmail.tsx
+ 87:6 warning React Hook React.useMemo has a missing dependency: 'transformFolderNode'. Either include it or remove the dependency array react-hooks/exhaustive-deps
+
+E:\Apps\yukthi\Webmail-ui-OSS\src\components\ui\ToastComponent.tsx
+ 62:14 warning Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components react-refresh/only-export-components
+
+E:\Apps\yukthi\Webmail-ui-OSS\src\routes\$slug.tsx
+ 75:6 warning React Hook useEffect has missing dependencies: 'navigate' and 'slug'. Either include them or remove the dependency array react-hooks/exhaustive-deps
+
+✖ 39 problems (0 errors, 39 warnings)
\ No newline at end of file
diff --git a/src/api/admin-domain.ts b/src/api/admin-domain.ts
index 49f68c1..b92fcb0 100644
--- a/src/api/admin-domain.ts
+++ b/src/api/admin-domain.ts
@@ -16,7 +16,7 @@
*/
import { webmailStore } from '../store.ts';
-import { apiKeyAtom, csrfTokenAtom } from '../state/auth';
+import { apiKeyAtom } from '../state/auth';
import { API_URL } from './config.ts';
export type Domain = {
@@ -57,7 +57,21 @@ const getHeaders = () => {
};
};
-export const getDomainList = async (page = 1, size = 20, query?: string): Promise => {
+export interface DomainListResponse {
+ data: Domain[];
+ total_count: number;
+ current_page?: number;
+ total_pages?: number;
+ has_next?: boolean;
+ has_previous?: boolean;
+ page_size?: number;
+}
+
+export const getDomainList = async (
+ page = 1,
+ size = 20,
+ query?: string
+): Promise => {
const url = new URL(`${API_URL}/user/admin/domains`);
url.searchParams.set('page', page.toString());
url.searchParams.set('size', size.toString());
diff --git a/src/api/auth.ts b/src/api/auth.ts
index fd8c760..3952957 100644
--- a/src/api/auth.ts
+++ b/src/api/auth.ts
@@ -29,6 +29,10 @@ export type LoginResponse = {
isVersionTwoUser: boolean;
};
+export class LoginError extends Error {
+ status?: number;
+}
+
export const login = async (credentials: {
email: string;
domain: string;
@@ -45,7 +49,7 @@ export const login = async (credentials: {
if (!res.ok) {
const error = await res.json();
- const err = new Error(error.message || 'Login failed') as any;
+ const err = new LoginError(error.message || 'Login failed');
err.status = res.status;
throw err;
}
diff --git a/src/api/config.ts b/src/api/config.ts
index b067f8d..bd4ba73 100644
--- a/src/api/config.ts
+++ b/src/api/config.ts
@@ -18,7 +18,7 @@
// TODO: Update the api url from the environment variable or configuration file
import { API_CONFIG } from '../constants/config';
-import { resetLayoutCache } from '../routes/_baselayout';
+import { resetLayoutCache } from '../utils/resetLayoutCache';
import { getCompanySlugFromPath } from '../utils/routeUtils';
export const API_URL = API_CONFIG.baseURL;
diff --git a/src/api/contacts.ts b/src/api/contacts.ts
index 2779e7a..86a3ad4 100644
--- a/src/api/contacts.ts
+++ b/src/api/contacts.ts
@@ -119,6 +119,12 @@ export const deleteContact = async (contact_id: string | number) => {
return await res.json();
};
+export class BulkContactError extends Error {
+ status?: number;
+ data?: unknown;
+ details?: unknown;
+}
+
export const createBulkContact = async (contactData: CreateContactData[]) => {
const csrfToken = webmailStore.get(csrfTokenAtom);
@@ -137,10 +143,10 @@ export const createBulkContact = async (contactData: CreateContactData[]) => {
if (!res.ok) {
// Changed from res.status !== 201
const errorMessage = data.message || data.error || 'Unable to create contacts';
- const error = new Error(errorMessage);
- (error as any).status = res.status;
- (error as any).data = data;
- (error as any).details = data.details;
+ const error = new BulkContactError(errorMessage);
+ error.status = res.status;
+ error.data = data;
+ error.details = data.details;
console.error('Throwing error:', error);
throw error;
diff --git a/src/api/fetchWrapper.ts b/src/api/fetchWrapper.ts
index c054313..82b5794 100644
--- a/src/api/fetchWrapper.ts
+++ b/src/api/fetchWrapper.ts
@@ -47,7 +47,12 @@ export const fetchWithAuth = async (url: string, options: RequestInit = {}) => {
return res;
};
-export const fetchListWithAuth = async (url: string, options: any) => {
+export const fetchListWithAuth = async (
+ url: string,
+ // `timeout` isn't a real fetch() option — browsers silently ignore it — but a
+ // caller passes it, so it's kept in the type to avoid changing behavior here.
+ options: RequestInit & { timeout?: number } = {}
+) => {
const res = await fetch(url, {
credentials: 'include',
...options,
diff --git a/src/api/mailbox.ts b/src/api/mailbox.ts
index 7ba5324..2881cf3 100644
--- a/src/api/mailbox.ts
+++ b/src/api/mailbox.ts
@@ -19,6 +19,8 @@ import { csrfTokenAtom } from '../state/auth';
import { webmailStore } from '../store.ts';
import { API_URL } from './config.ts';
import { fetchListWithAuth, fetchWithAuth } from './fetchWrapper.ts';
+import type { FolderQuota } from '../state/folders.ts';
+import type { EmailLike } from '../utils/emailThreading.ts';
export type CustomFolders = string[];
@@ -176,7 +178,7 @@ export const foldersFullPath = async (): Promise => {
return body;
};
-export const foldersQuota = async (path: string): Promise => {
+export const foldersQuota = async (): Promise<{ quota: FolderQuota }> => {
const csrfToken = webmailStore.get(csrfTokenAtom);
const res = await fetchWithAuth(`${API_URL}/folder/quota?folder_path=INBOX`, {
method: 'GET',
@@ -202,7 +204,7 @@ export const emails = async (
full_headers: boolean = true
): Promise => {
const csrfToken = webmailStore.get(csrfTokenAtom);
- let sanitizedFolder = sanitizeFolderPath(folder);
+ const sanitizedFolder = sanitizeFolderPath(folder);
const params = new URLSearchParams();
params.append('folder_path', sanitizedFolder);
params.append('full_headers', full_headers.toString());
@@ -229,7 +231,7 @@ export const emailRaw = async (
): Promise => {
const csrfToken = webmailStore.get(csrfTokenAtom);
const params = new URLSearchParams();
- let sanitizedFolder = sanitizeFolderPath(folderPath);
+ const sanitizedFolder = sanitizeFolderPath(folderPath);
params.append('folder_path', sanitizedFolder);
params.append('mark_as_read', String(markAsRead));
@@ -257,7 +259,7 @@ export const emailFetchByIds = async ({
}: {
folderPath: string;
messageIds: string[];
-}): Promise => {
+}): Promise<{ emails: EmailLike[] }> => {
if (!folderPath || messageIds.length === 0) {
throw new Error('folderPath and messageIds are required');
}
@@ -295,8 +297,8 @@ export const moveEmail = async (
body: number[]
): Promise => {
const csrfToken = webmailStore.get(csrfTokenAtom);
- let sanitizedSource = sanitizeFolderPath(sourceFolder);
- let sanitizedDest = sanitizeFolderPath(destFolder);
+ const sanitizedSource = sanitizeFolderPath(sourceFolder);
+ const sanitizedDest = sanitizeFolderPath(destFolder);
const res = await fetchWithAuth(
`${API_URL}/email/move?folder_path=${path}&source_folder=${sanitizedSource}&dest_folder=${sanitizedDest}`,
{
@@ -368,7 +370,7 @@ export const deleteEmail = async (path: string, body: number[]): Promise => {
const csrfToken = webmailStore.get(csrfTokenAtom);
- let sanitizedFolder = sanitizeFolderPath(path);
+ const sanitizedFolder = sanitizeFolderPath(path);
const res = await fetchWithAuth(`${API_URL}/email/mark/read?folder_path=${sanitizedFolder}`, {
method: 'PUT',
credentials: 'include',
@@ -389,7 +391,7 @@ export const markReadEmail = async (path: string, body: number[]): Promise => {
const csrfToken = webmailStore.get(csrfTokenAtom);
- let sanitizedFolder = sanitizeFolderPath(path);
+ const sanitizedFolder = sanitizeFolderPath(path);
const res = await fetchWithAuth(`${API_URL}/email/mark/unseen?folder_path=${sanitizedFolder}`, {
method: 'PATCH',
credentials: 'include',
@@ -410,7 +412,7 @@ export const unmarkReadEmail = async (path: string, body: number[]): Promise => {
const csrfToken = webmailStore.get(csrfTokenAtom);
- let sanitizedFolder = sanitizeFolderPath(path);
+ const sanitizedFolder = sanitizeFolderPath(path);
const res = await fetchWithAuth(`${API_URL}/email/mark/flagged?folder_path=${sanitizedFolder}`, {
method: 'PUT',
credentials: 'include',
@@ -431,7 +433,7 @@ export const markFlaggedEmail = async (path: string, body: number[]): Promise => {
const csrfToken = webmailStore.get(csrfTokenAtom);
- let sanitizedFolder = sanitizeFolderPath(path);
+ const sanitizedFolder = sanitizeFolderPath(path);
const res = await fetchWithAuth(
`${API_URL}/email/mark/unflagged?folder_path=${sanitizedFolder}`,
{
@@ -477,7 +479,7 @@ export const createEmailFolder = async (path: string): Promise =>
export const deleteEmailFolder = async (path: string): Promise => {
const csrfToken = webmailStore.get(csrfTokenAtom);
- let sanitizedFolder = sanitizeFolderPath(path);
+ const sanitizedFolder = sanitizeFolderPath(path);
const res = await fetchWithAuth(`${API_URL}/folder/path?folder_path=${sanitizedFolder}`, {
method: 'DELETE',
credentials: 'include',
@@ -497,8 +499,8 @@ export const deleteEmailFolder = async (path: string): Promise =>
export const editEmailFolder = async (oldpath: string, newpath: string): Promise => {
const csrfToken = webmailStore.get(csrfTokenAtom);
- let sanitizedOldFolder = sanitizeFolderPath(oldpath);
- let sanitizedNewFolder = sanitizeFolderPath(newpath);
+ const sanitizedOldFolder = sanitizeFolderPath(oldpath);
+ const sanitizedNewFolder = sanitizeFolderPath(newpath);
const res = await fetchWithAuth(
`${API_URL}/folder/path?old_folder_path=${sanitizedOldFolder}&new_folder_path=${sanitizedNewFolder}`,
{
@@ -527,7 +529,6 @@ export const folderUidValidity = async (
message: string;
}> => {
const csrfToken = webmailStore.get(csrfTokenAtom);
- let sanitizedFolder = sanitizeFolderPath(folderPath);
const res = await fetchWithAuth(`${API_URL}/folder/uid-validity?folder_path=${folderPath}`, {
method: 'GET',
credentials: 'include',
diff --git a/src/api/search.ts b/src/api/search.ts
index d973b3f..2d13bd4 100644
--- a/src/api/search.ts
+++ b/src/api/search.ts
@@ -21,6 +21,7 @@ import { webmailStore } from '../store';
import { API_URL, authCheck } from './config';
import { fetchWithAuth } from './fetchWrapper';
import { sanitizeFolderPath } from './mailbox';
+import type { SimplifiedEmail } from '../utils/email';
// export interface SearchRequest {
// folder?: string;
@@ -75,7 +76,17 @@ export interface SearchResponse {
has_more: boolean;
}
-export const searchEmails = async (searchData: SearchRequest): Promise => {
+// The real response shape (confirmed by every current consumer's `.data.data` /
+// `.data.total_count` access) — SearchResponse above doesn't match what the
+// endpoint actually returns and appears to be stale/aspirational.
+export interface SearchApiResponse {
+ data: {
+ data: SimplifiedEmail[];
+ total_count: number;
+ };
+}
+
+export const searchEmails = async (searchData: SearchRequest): Promise => {
const csrfToken = webmailStore.get(csrfTokenAtom);
const res = await fetchWithAuth(`${API_URL}/email/search/all`, {
diff --git a/src/api/sieve.ts b/src/api/sieve.ts
index e939e3a..0316b15 100644
--- a/src/api/sieve.ts
+++ b/src/api/sieve.ts
@@ -30,9 +30,11 @@ export interface ListScriptsApiResponse {
scripts: SieveScriptList;
}
-// API uses tuple arrays, not objects
-export type SieveFilterCondition = any[]; // e.g., ["Subject", ":matches", "Junk*"]
-export type SieveFilterAction = any[]; // e.g., ["fileinto", "Junks"]
+// API uses tuple arrays, not objects. Element types vary per condition/action
+// kind (strings for field/operator/value, but e.g. size comparisons can carry
+// numbers), so these stay loosely typed rather than guessing a fixed tuple shape.
+export type SieveFilterCondition = unknown[]; // e.g., ["Subject", ":matches", "Junk*"]
+export type SieveFilterAction = unknown[]; // e.g., ["fileinto", "Junks"]
export interface SieveFilter {
name: string;
@@ -90,7 +92,7 @@ export interface UpdateFilterRequest {
export interface SieveApiResponse {
success: boolean;
message?: string;
- data?: any;
+ data?: unknown;
}
// List all scripts
diff --git a/src/components/admin/domain/BrandingManager.tsx b/src/components/admin/domain/BrandingManager.tsx
index 69710b9..846ed4b 100644
--- a/src/components/admin/domain/BrandingManager.tsx
+++ b/src/components/admin/domain/BrandingManager.tsx
@@ -16,10 +16,8 @@
*/
import { useState, useRef } from 'react';
-import { useAtomValue } from 'jotai';
import { FiUpload, FiX, FiSearch, FiSave, FiEye } from 'react-icons/fi';
-import { apiKeyAtom } from '../../../state/auth';
-import { useToast } from '../../ui/ToastComponent';
+import { useToast } from '../../../hooks/useToast';
import { API_CONFIG } from '../../../constants/config';
import { BgImageService } from '../../../utils/bimiService';
@@ -109,7 +107,6 @@ function ImageUpload({
}
export default function BrandingManager() {
- const apiKey = useAtomValue(apiKeyAtom);
const toast = useToast();
const [slug, setSlug] = useState('');
diff --git a/src/components/admin/domain/BulkEdit.tsx b/src/components/admin/domain/BulkEdit.tsx
index d80efb1..700f242 100644
--- a/src/components/admin/domain/BulkEdit.tsx
+++ b/src/components/admin/domain/BulkEdit.tsx
@@ -25,7 +25,6 @@ import {
FiAlertCircle,
FiCheckCircle,
FiDownload,
- FiLoader,
FiInfo,
FiChevronDown,
FiChevronUp,
@@ -166,8 +165,9 @@ function parseFile(file: File): Promise<{ rows: ParsedRow[]; missingHeaders: str
const rows = normalised.map((row, i) => validateRow(row, i + 2));
resolve({ rows, missingHeaders: [] });
- } catch (err: any) {
- reject(new Error(`Failed to parse file: ${err.message}`));
+ } catch (err) {
+ const message = err instanceof Error ? err.message : String(err);
+ reject(new Error(`Failed to parse file: ${message}`));
}
};
@@ -280,8 +280,8 @@ export default function DomainBulkEditModal({ isOpen, onClose }: Props) {
}
setRows(parsed);
setPhase('parsed');
- } catch (err: any) {
- setParseError(err.message);
+ } catch (err) {
+ setParseError(err instanceof Error ? err.message : String(err));
}
}, []);
@@ -333,7 +333,7 @@ export default function DomainBulkEditModal({ isOpen, onClose }: Props) {
successes++;
resolve();
},
- onError: (err: any) => {
+ onError: (err) => {
errors.push({
domain: row.domain,
rowIndex: row._rowIndex,
diff --git a/src/components/admin/domain/BulkImport.tsx b/src/components/admin/domain/BulkImport.tsx
index 4962dda..cb19a55 100644
--- a/src/components/admin/domain/BulkImport.tsx
+++ b/src/components/admin/domain/BulkImport.tsx
@@ -25,7 +25,6 @@ import {
FiAlertCircle,
FiCheckCircle,
FiDownload,
- FiLoader,
FiInfo,
FiChevronDown,
FiChevronUp,
@@ -166,8 +165,9 @@ function parseFile(file: File): Promise<{ rows: ParsedRow[]; missingHeaders: str
const rows = normalised.map((row, i) => validateRow(row, i + 2)); // +2 for header + 1-based
resolve({ rows, missingHeaders: [] });
- } catch (err: any) {
- reject(new Error(`Failed to parse file: ${err.message}`));
+ } catch (err) {
+ const message = err instanceof Error ? err.message : String(err);
+ reject(new Error(`Failed to parse file: ${message}`));
}
};
@@ -227,23 +227,6 @@ function ProgressRing({ pct }: { pct: number }) {
);
}
-function ValidationBadge({ count, type }: { count: number; type: 'error' | 'ok' }) {
- if (type === 'error') {
- return (
-
-
- {count} invalid
-
- );
- }
- return (
-
-
- {count} valid
-
- );
-}
-
// ─── Main Modal ───────────────────────────────────────────────────────────────
interface Props {
@@ -294,8 +277,8 @@ export default function DomainBulkImportModal({ isOpen, onClose }: Props) {
}
setRows(parsed);
setPhase('parsed');
- } catch (err: any) {
- setParseError(err.message);
+ } catch (err) {
+ setParseError(err instanceof Error ? err.message : String(err));
}
}, []);
@@ -327,7 +310,7 @@ export default function DomainBulkImportModal({ isOpen, onClose }: Props) {
setCurrentDomain(row.domain);
try {
- await new Promise((resolve, reject) => {
+ await new Promise((resolve) => {
createMutation.mutate(
{
domain: row.domain,
@@ -339,13 +322,13 @@ export default function DomainBulkImportModal({ isOpen, onClose }: Props) {
sieve_port: row.sieve_port,
is_active: row.is_active,
is_v2_user: row.is_v2_user,
- } as any,
+ },
{
onSuccess: () => {
successes++;
resolve();
},
- onError: (err: any) => {
+ onError: (err) => {
errors.push({
domain: row.domain,
rowIndex: row._rowIndex,
diff --git a/src/components/admin/domain/DomainDetailsModal.tsx b/src/components/admin/domain/DomainDetailsModal.tsx
index f08ba75..3653ece 100644
--- a/src/components/admin/domain/DomainDetailsModal.tsx
+++ b/src/components/admin/domain/DomainDetailsModal.tsx
@@ -22,7 +22,7 @@ import type { Domain } from '../../../api/admin-domain';
interface DomainDetailsModalProps {
isOpen: boolean;
onClose: () => void;
- data: any;
+ data: Domain | null;
}
const DomainDetailsModal = ({ isOpen, onClose, data }: DomainDetailsModalProps) => {
diff --git a/src/components/admin/domain/DomainForm.tsx b/src/components/admin/domain/DomainForm.tsx
index 8686427..cf81922 100644
--- a/src/components/admin/domain/DomainForm.tsx
+++ b/src/components/admin/domain/DomainForm.tsx
@@ -24,9 +24,9 @@ import { FiArrowLeft } from 'react-icons/fi';
import { Flex, Switch, Text } from '@radix-ui/themes';
import { Controller } from 'react-hook-form';
import { useCreateDomain, useUpdateDomain, useGetDomain } from '../../../hooks/useAdminDomain';
-import { useToast } from '../../ui/ToastComponent';
import { apiKeyAtom } from '../../../state/auth';
import { useAtomValue } from 'jotai';
+import type { CreateDomainData } from '../../../api/admin-domain';
const schema = yup.object().shape({
domain: yup.string().required('Domain name is required'),
@@ -66,7 +66,6 @@ function DomainForm() {
const { data: domainData, isLoading } = useGetDomain(params.domain);
const createMutation = useCreateDomain();
const updateMutation = useUpdateDomain();
- const toast = useToast();
const {
register,
@@ -116,7 +115,7 @@ function DomainForm() {
}
);
} else {
- createMutation.mutate(data as any, {
+ createMutation.mutate(data as unknown as CreateDomainData, {
onSuccess: () => {
navigate({ to: '/1219/admin/domain' });
},
@@ -382,7 +381,15 @@ function DomainForm() {
);
}
-const FormField = ({ label, error, children }: any) => (
+const FormField = ({
+ label,
+ error,
+ children,
+}: {
+ label: string;
+ error?: string;
+ children: React.ReactNode;
+}) => (