diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b03568..876f17d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- **Pull Now**: Added a "Pull Now" button (⟳) to each mail account card on the Accounts page. Clicking it immediately queues a Celery `process_mail_account` task for that account via the new `POST /mail-accounts/{id}/pull-now` backend endpoint. The button shows a spinner while the request is in flight and is disabled for inactive accounts. +- **Provider logos**: Provider logos (Gmail, GMX, WEB.DE, Outlook, Yahoo, AOL, T-Online, IONOS, Freenet, Posteo, iCloud, Proton Mail) are displayed as a full-width banner at the top of each account card. Using `next/image` with `fill` + `object-contain` ensures every logo – from square icons to very wide wordmarks (up to 6:1 aspect ratio) – renders correctly without distortion. +- **Proton Mail**: Added Proton Mail as a provider preset (backend + ProviderWizard). Supports IMAP and POP3 via Proton Mail Bridge (default ports 127.0.0.1:1143 / 127.0.0.1:1144). Domains: `proton.me`, `protonmail.com`, `protonmail.ch`, `pm.me`. + ### Changed - **Mailbox Activity view**: The user-facing "Logs" page has been redesigned to a mailbox-centric layout (renamed "Mailbox Activity"). Each mail account is shown as a card with its last check diff --git a/backend/app/api/v1/endpoints/mail_accounts.py b/backend/app/api/v1/endpoints/mail_accounts.py index 7c9c9bf..4e73ba7 100644 --- a/backend/app/api/v1/endpoints/mail_accounts.py +++ b/backend/app/api/v1/endpoints/mail_accounts.py @@ -17,6 +17,7 @@ from app.models.database_models import ( AccountStatus, SubscriptionPlan, ) +from app.workers.tasks import process_mail_account as process_mail_account_task from app.models.schemas import ( MailAccountCreate, MailAccountResponse, @@ -241,6 +242,36 @@ async def toggle_mail_account( return account +@router.post("/{account_id}/pull-now", status_code=status.HTTP_202_ACCEPTED) +async def pull_now( + account_id: int, + current_user: User = Depends(get_current_active_user), + db: AsyncSession = Depends(get_db), +): + """Immediately queue a pull for the given mail account""" + result = await db.execute( + select(MailAccount).where( + MailAccount.id == account_id, MailAccount.user_id == current_user.id + ) + ) + account = result.scalar_one_or_none() + + if not account: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="Mail account not found" + ) + + if not account.is_enabled: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail="Account is disabled. Enable it before pulling.", + ) + + process_mail_account_task.delay(account_id) + + return {"message": "Pull queued successfully"} + + @router.post("/test", response_model=MailAccountTestResponse) async def test_mail_connection( test_request: MailAccountTestRequest, diff --git a/backend/app/api/v1/endpoints/providers.py b/backend/app/api/v1/endpoints/providers.py index 7a9a72a..1757b87 100644 --- a/backend/app/api/v1/endpoints/providers.py +++ b/backend/app/api/v1/endpoints/providers.py @@ -155,6 +155,15 @@ PROVIDER_PRESETS: List[ProviderPreset] = [ pop3_ssl={"host": "pop.mail.de", "port": 995}, notes="Use your mail.de email credentials.", ), + ProviderPreset( + id="protonmail", + name="Proton Mail", + icon="protonmail", + domains=["proton.me", "protonmail.com", "protonmail.ch", "pm.me"], + imap_ssl={"host": "127.0.0.1", "port": 1143}, + pop3_ssl={"host": "127.0.0.1", "port": 1144}, + notes="Requires Proton Mail Bridge running locally. Use your Bridge password (not your Proton account password). Default Bridge ports: IMAP 127.0.0.1:1143, POP3 127.0.0.1:1144.", + ), ProviderPreset( id="icloud", name="iCloud Mail", diff --git a/backend/app/services/mail_processor.py b/backend/app/services/mail_processor.py index dccca9b..208908f 100644 --- a/backend/app/services/mail_processor.py +++ b/backend/app/services/mail_processor.py @@ -597,6 +597,26 @@ class MailServerAutoDetect: "pop3_ssl": {"host": "pop.mail.de", "port": 995}, "imap_ssl": {"host": "imap.mail.de", "port": 993}, }, + "proton.me": { + "name": "Proton Mail", + "imap_ssl": {"host": "127.0.0.1", "port": 1143}, + "pop3_ssl": {"host": "127.0.0.1", "port": 1144}, + }, + "protonmail.com": { + "name": "Proton Mail", + "imap_ssl": {"host": "127.0.0.1", "port": 1143}, + "pop3_ssl": {"host": "127.0.0.1", "port": 1144}, + }, + "protonmail.ch": { + "name": "Proton Mail", + "imap_ssl": {"host": "127.0.0.1", "port": 1143}, + "pop3_ssl": {"host": "127.0.0.1", "port": 1144}, + }, + "pm.me": { + "name": "Proton Mail", + "imap_ssl": {"host": "127.0.0.1", "port": 1143}, + "pop3_ssl": {"host": "127.0.0.1", "port": 1144}, + }, } @classmethod diff --git a/docs/TODO.md b/docs/TODO.md index a563c72..ad3c077 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -4,6 +4,9 @@ Comprehensive task breakdown for repository improvements and production readines ## ✅ Recently Completed +- [x] **Pull Now**: Added "Pull Now" button on Accounts page that immediately queues a `process_mail_account` Celery task via `POST /mail-accounts/{id}/pull-now`. Button shows spinner while in flight and is disabled for inactive accounts. +- [x] **Provider logos rework**: Logos now displayed as full-width banner strips at the top of each account card using `next/image fill + object-contain`. Handles all aspect ratios (1:1 square to 6:1 wordmark) without distortion. Proton Mail added. +- [x] **Proton Mail provider**: Added Proton Mail preset in backend and ProviderWizard frontend. Domains: proton.me, protonmail.com, protonmail.ch, pm.me. Auto-detect and IMAP/POP3 Bridge settings included. - [x] Redesigned user-facing Logs page to mailbox-centric "Mailbox Activity" view: shows last check status per account + only successful pulls, suppressing noise from empty polling cycles. - [x] Added `has_emails` filter to `GET /processing-runs` and `GET /mail-accounts/{id}/processing-runs` API endpoints. - [x] Rename entire project to **InboxConverge**: all user-visible strings, Docker container/image names, DB defaults, monitoring, and docs updated. diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 01e8177..9f39a28 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1724,7 +1724,7 @@ "version": "19.2.10", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.10.tgz", "integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -2801,7 +2801,7 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/damerau-levenshtein": { diff --git a/frontend/public/providers/aol.svg b/frontend/public/providers/aol.svg index f840baf..f57d175 100644 --- a/frontend/public/providers/aol.svg +++ b/frontend/public/providers/aol.svg @@ -1,5 +1,4 @@ - - - aol - + + + diff --git a/frontend/public/providers/freenet.svg b/frontend/public/providers/freenet.svg index b6cfb99..bffa604 100644 --- a/frontend/public/providers/freenet.svg +++ b/frontend/public/providers/freenet.svg @@ -1,4 +1,65 @@ - - - freenet + + + + + + + + + + + + + + + + + + diff --git a/frontend/public/providers/gmail.svg b/frontend/public/providers/gmail.svg index c366e71..40b7175 100644 --- a/frontend/public/providers/gmail.svg +++ b/frontend/public/providers/gmail.svg @@ -1,7 +1,7 @@ - - - - - - - + + + + + + + \ No newline at end of file diff --git a/frontend/public/providers/gmx.svg b/frontend/public/providers/gmx.svg index a08aa47..b940632 100644 --- a/frontend/public/providers/gmx.svg +++ b/frontend/public/providers/gmx.svg @@ -1,4 +1,20 @@ - - - GMX + + + + + + + diff --git a/frontend/public/providers/icloud.svg b/frontend/public/providers/icloud.svg index 0865694..823d7d0 100644 --- a/frontend/public/providers/icloud.svg +++ b/frontend/public/providers/icloud.svg @@ -1,6 +1 @@ - - - - - + \ No newline at end of file diff --git a/frontend/public/providers/ionos.svg b/frontend/public/providers/ionos.svg index fbf01f4..cc4ecbe 100644 --- a/frontend/public/providers/ionos.svg +++ b/frontend/public/providers/ionos.svg @@ -1,5 +1,12 @@ - - - 1&1 - IONOS - + \ No newline at end of file diff --git a/frontend/public/providers/outlook.svg b/frontend/public/providers/outlook.svg index 938b1f1..2977e1c 100644 --- a/frontend/public/providers/outlook.svg +++ b/frontend/public/providers/outlook.svg @@ -1,10 +1,203 @@ - - - - - - - - - - + + + + diff --git a/frontend/public/providers/posteo.svg b/frontend/public/providers/posteo.svg index c42bae4..2ab5547 100644 --- a/frontend/public/providers/posteo.svg +++ b/frontend/public/providers/posteo.svg @@ -1,8 +1,69 @@ - - - - - - - - + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/public/providers/protonmail.svg b/frontend/public/providers/protonmail.svg new file mode 100644 index 0000000..c5b4fe7 --- /dev/null +++ b/frontend/public/providers/protonmail.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/public/providers/tonline.svg b/frontend/public/providers/tonline.svg index cf73cf1..135a132 100644 --- a/frontend/public/providers/tonline.svg +++ b/frontend/public/providers/tonline.svg @@ -1,10 +1 @@ - - - - - - - - - - +t-onlinede_logo \ No newline at end of file diff --git a/frontend/public/providers/webde.svg b/frontend/public/providers/webde.svg index 7baa7a1..88fa9ff 100644 --- a/frontend/public/providers/webde.svg +++ b/frontend/public/providers/webde.svg @@ -1,4 +1 @@ - - - WEB.DE - + \ No newline at end of file diff --git a/frontend/public/providers/yahoo.svg b/frontend/public/providers/yahoo.svg index 24a0f86..121ac1c 100644 --- a/frontend/public/providers/yahoo.svg +++ b/frontend/public/providers/yahoo.svg @@ -1,5 +1,20 @@ - - - - Y! + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/app/accounts/page.tsx b/frontend/src/app/accounts/page.tsx index 35e08e5..6e6aae5 100644 --- a/frontend/src/app/accounts/page.tsx +++ b/frontend/src/app/accounts/page.tsx @@ -4,13 +4,55 @@ import { AuthGuard } from '@/components/AuthGuard'; import { DashboardLayout } from '@/components/DashboardLayout'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { mailAccountsApi, MailAccount } from '@/lib/api'; -import { Plus, Edit2, Trash2, CheckCircle, XCircle, AlertTriangle, Power } from 'lucide-react'; +import { Plus, Edit2, Trash2, CheckCircle, XCircle, AlertTriangle, Power, RefreshCw } from 'lucide-react'; import { useState } from 'react'; +import Image from 'next/image'; import { AddMailAccountModal } from '@/components/AddMailAccountModal'; +// Map provider_name (from backend) to the SVG icon filename in /public/providers/ +const PROVIDER_ICON_MAP: Record = { + 'Gmail': 'gmail', + 'GMX': 'gmx', + 'WEB.DE': 'webde', + 'Outlook / Hotmail': 'outlook', + 'Yahoo Mail': 'yahoo', + 'AOL Mail': 'aol', + 'T-Online': 'tonline', + '1&1 / IONOS': 'ionos', + 'Freenet': 'freenet', + 'Posteo': 'posteo', + 'mail.de': 'mailde', + 'iCloud Mail': 'icloud', + 'Proton Mail': 'protonmail', +}; + +/** + * Full-width logo banner rendered at the top of a card. + * Uses next/image fill + object-contain so every logo – regardless of its + * native aspect ratio (1:1 square up to ~6:1 wordmark) – fits correctly + * inside the fixed-height strip without distortion. + */ +function ProviderLogoBanner({ providerName }: { providerName?: string | null }) { + const icon = providerName ? PROVIDER_ICON_MAP[providerName] : undefined; + if (!icon) return null; + return ( +
+ {`${providerName} +
+ ); +} + export default function AccountsPage() { const [isModalOpen, setIsModalOpen] = useState(false); const [editingAccount, setEditingAccount] = useState(null); + const [pullingIds, setPullingIds] = useState>(new Set()); const queryClient = useQueryClient(); const { data: accounts, isLoading } = useQuery({ @@ -55,6 +97,21 @@ export default function AccountsPage() { } }; + const handlePullNow = async (id: number) => { + setPullingIds((prev) => new Set(prev).add(id)); + try { + await mailAccountsApi.pullNow(id); + } catch { + alert('Failed to queue pull'); + } finally { + setPullingIds((prev) => { + const next = new Set(prev); + next.delete(id); + return next; + }); + } + }; + const handleCloseModal = () => { setIsModalOpen(false); setEditingAccount(null); @@ -88,15 +145,18 @@ export default function AccountsPage() { account.is_enabled ? 'border-gray-200' : 'border-gray-200 opacity-60' }`} > + {/* Provider logo banner – full-width strip that accommodates any aspect ratio */} + +
-
-

+
+

{account.name}

-

{account.email_address}

+

{account.email_address}

-
+
{account.is_enabled ? ( @@ -164,6 +224,15 @@ export default function AccountsPage() { > + ))}
diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 1c61637..5f8da14 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -318,6 +318,11 @@ export const mailAccountsApi = { return response.data; }, + async pullNow(id: number): Promise<{ message: string }> { + const response = await api.post<{ message: string }>(`/mail-accounts/${id}/pull-now`); + return response.data; + }, + async delete(id: number): Promise { await api.delete(`/mail-accounts/${id}`); },