Files
Inboxintel/frontend/src/api/client.js
T
cesnimda 2f173dc3e5
CI / backend (push) Successful in 1m33s
CI / frontend (push) Successful in 33s
CI / format (push) Successful in 2m2s
CI / db-tests (push) Successful in 1m45s
Deploy Staging / deploy (push) Successful in 38s
Security / secrets (push) Successful in 6s
Security / dependencies (push) Successful in 1m17s
Security / sast (push) Successful in 56s
CI / backend (pull_request) Successful in 1m9s
CI / frontend (pull_request) Successful in 21s
CI / format (pull_request) Successful in 1m4s
CI / db-tests (pull_request) Successful in 1m27s
Security / secrets (pull_request) Successful in 6s
Security / dependencies (pull_request) Successful in 1m21s
Security / sast (pull_request) Successful in 1m8s
feat(ui): email dashboard UX refactor (PHASES 1/3/4) (#44)
2026-07-04 21:16:21 +02:00

167 lines
7.9 KiB
JavaScript

import axios from 'axios';
// Cookie-based auth (Google OAuth2 session), so send credentials with each call.
const api = axios.create({ baseURL: '/api/v1', withCredentials: true });
api.interceptors.response.use(
(r) => r,
(err) => {
if (err.response?.status === 401) {
// Not signed in. Send the user to the public landing page (unless already
// there) so they can read the features and choose to log in.
if (window.location.pathname !== '/') window.location.href = '/';
}
return Promise.reject(err);
}
);
// Top-level navigation that starts the Google flow and returns to the app.
export const LOGIN_URL = '/api/v1/auth/login?returnUrl=/app';
export const AppApi = {
info: () => api.get('/app/info').then((r) => r.data)
};
export const AuthApi = {
me: () => api.get('/auth/me').then((r) => r.data),
logout: () => api.post('/auth/logout')
};
export const SyncApi = {
status: () => api.get('/sync/status').then((r) => r.data),
full: () => api.post('/sync/full'),
incremental: () => api.post('/sync/incremental')
};
export const AnalyticsApi = {
dashboard: () => api.get('/analytics/dashboard').then((r) => r.data),
health: () => api.get('/analytics/health').then((r) => r.data),
topSenders: (take = 20) => api.get(`/analytics/top-senders?take=${take}`).then((r) => r.data),
volume: (days = 90) => api.get(`/analytics/volume?days=${days}`).then((r) => r.data),
categoryHeatmap: () => api.get('/analytics/category-heatmap').then((r) => r.data),
attachments: () => api.get('/analytics/attachments').then((r) => r.data),
sidebarCounts: () => api.get('/analytics/sidebar-counts').then((r) => r.data),
// Large take to retrieve all senders for the volume-tiers page.
allSenders: () => api.get('/analytics/top-senders?take=5000').then((r) => r.data)
};
export const CleanupApi = {
preview: (req) => api.post('/cleanup/preview', req).then((r) => r.data),
execute: (req) => api.post('/cleanup/execute', req).then((r) => r.data)
};
// Bulk actions over an explicit set of email IDs (selection-driven, always confirmed —
// the user already opted in by selecting rows and clicking the action).
// Numeric values must match CleanupActionType in Domain/Enums/Enums.cs (no string
// enum converter is configured on the API, so plain numbers are required here).
const CLEANUP_ACTION = { Archive: 0, Trash: 1, MarkRead: 5, MarkUnread: 6, Star: 7, Unstar: 8 };
export const BulkApi = {
markRead: (ids) => CleanupApi.execute({ action: CLEANUP_ACTION.MarkRead, emailIds: ids, confirmed: true }),
markUnread: (ids) => CleanupApi.execute({ action: CLEANUP_ACTION.MarkUnread, emailIds: ids, confirmed: true }),
star: (ids) => CleanupApi.execute({ action: CLEANUP_ACTION.Star, emailIds: ids, confirmed: true }),
unstar: (ids) => CleanupApi.execute({ action: CLEANUP_ACTION.Unstar, emailIds: ids, confirmed: true }),
archive: (ids) => CleanupApi.execute({ action: CLEANUP_ACTION.Archive, emailIds: ids, confirmed: true }),
trash: (ids) => CleanupApi.execute({ action: CLEANUP_ACTION.Trash, emailIds: ids, confirmed: true }),
};
export const UnsubscribeApi = {
detect: () => api.post('/unsubscribe/detect'),
safeList: () => api.get('/unsubscribe/safe-list').then((r) => r.data),
process: (req) => api.post('/unsubscribe/process', req).then((r) => r.data)
};
export const LayoutApi = {
get: () => api.get('/widgetlayout').then((r) => r.data),
save: (layout) => api.put('/widgetlayout', layout)
};
export const SearchApi = {
folder: (slug, page = 1, pageSize = 50) =>
api.post('/search', folderToRequest(slug, page, pageSize)).then((r) => r.data),
query: (q, page = 1, pageSize = 50) =>
api.get('/search', { params: { q, page, pageSize } }).then((r) => r.data),
};
function folderToRequest(slug, page, pageSize) {
const base = { page, pageSize };
switch (slug) {
// ── Mailbox ──
case 'inbox': return { ...base, isInInbox: true, isTrashed: false };
case 'allmail': return { ...base };
case 'unread': return { ...base, isUnread: true };
case 'starred': return { ...base, isStarred: true };
case 'sent': return { ...base, gmailLabel: 'SENT' };
case 'drafts': return { ...base, gmailLabel: 'DRAFT' };
// Archive = filed away: not in inbox, not trashed, and NOT Sent/Spam/Draft/Chat.
// Excluding those labels stops sent mail leaking into Archive.
case 'archive': return { ...base, isInInbox: false, isTrashed: false, excludeGmailLabels: ['SENT', 'DRAFT', 'SPAM', 'TRASH', 'CHAT'] };
case 'spam': return { ...base, gmailLabel: 'SPAM' };
case 'trash': return { ...base, isTrashed: true };
// Pinned = Gmail's "Important" marker (a real per-message flag), not "everything".
case 'pinned': return { ...base, isImportant: true };
// Read Later = only emails the user explicitly flagged (local marker).
case 'readlater': return { ...base, isReadLater: true };
// Unlabelled = emails not filed under any user-created label.
case 'unlabelled': return { ...base, hasUserLabels: false };
// ── Special filters ──
case 'large': return { ...base, minSizeBytes: 5_000_000 };
// Old Mail = strictly older than 6 months.
case 'old': {
const d = new Date(); d.setMonth(d.getMonth() - 6);
return { ...base, to: d.toISOString().slice(0, 10) };
}
// ── Smart folders ──
case 'automated': return { ...base, category: 'Notification' };
case 'finance': return { ...base, category: 'Finance' };
case 'social': return { ...base, category: 'Social' };
case 'shopping': return { ...base, category: 'Shopping' };
case 'noreply': return { ...base, query: 'from:noreply' };
case 'gaming': return { ...base, category: 'Gaming' };
case 'sales': return { ...base, category: 'SeasonalSales' };
case 'ridesharing': return { ...base, category: 'RideSharing' };
case 'food': return { ...base, category: 'FoodDelivery' };
case 'wellness': return { ...base, category: 'Wellness' };
// New categories
case 'travel': return { ...base, category: 'Travel' };
case 'subscriptions': return { ...base, category: 'Subscriptions' };
case 'parcels': return { ...base, category: 'Parcels' };
case 'recruitment': return { ...base, category: 'Recruitment' };
case 'events': return { ...base, category: 'Events' };
case 'security': return { ...base, category: 'SecurityAlerts' };
case 'healthcare': return { ...base, category: 'Healthcare' };
case 'education': return { ...base, category: 'Education' };
case 'news': return { ...base, category: 'NewsMedia' };
case 'property': return { ...base, category: 'PropertyUtilities' };
case 'charity': return { ...base, category: 'Charity' };
case 'government': return { ...base, category: 'Government' };
case 'crypto': return { ...base, category: 'CryptoInvesting' };
case 'family': return { ...base, category: 'FamilySchool' };
default: return { ...base };
}
}
export const EmailApi = {
get: (id) => api.get(`/email/${id}`).then((r) => r.data),
summary: (id) => api.get(`/email/${id}/summary`).then((r) => r.data),
markRead: (id) => api.post(`/email/${id}/read`),
markUnread: (id) => api.post(`/email/${id}/unread`),
star: (id) => api.post(`/email/${id}/star`),
unstar: (id) => api.post(`/email/${id}/unstar`),
trash: (id) => api.post(`/email/${id}/trash`),
untrash: (id) => api.post(`/email/${id}/untrash`),
unsubscribe:(id) => api.post(`/email/${id}/unsubscribe`).then((r) => r.data),
};
export const ExportApi = {
reportUrl: (format) => `/api/v1/export/report?format=${format}`
};
export const SettingsApi = {
getDigest: () => api.get('/settings/digest').then((r) => r.data),
setDigest: (enabled) => api.put('/settings/digest', enabled).then((r) => r.data),
sendDigestNow: () => api.post('/settings/digest/send-now'),
};
export default api;