diff --git a/STATUS.md b/STATUS.md index 0effc99..6f06615 100644 --- a/STATUS.md +++ b/STATUS.md @@ -3,7 +3,17 @@ **Tier:** build **Board:** [PlugPress HQ](https://github.com/orgs/plugpressco/projects/3) -## Last session (2026-10-06) — simple SMTP: Pro removed, Meow Mailer parity (#17, PR #18) +## Last session (2026-10-09) — Settings → SMTP + declutter (#20, PR #21 — awaiting your merge) + +- **User ask:** remove the top-level menu, move the page to Settings → SMTP, clean up and declutter the admin UI. Filed as #20; PR #21 is ready, **CI green, NOT merged** (two hard stops below). +- **Menu:** `add_options_page()` at `options-general.php?page=mailyard`, labelled "SMTP" (page title "Mailyard SMTP"); screen id `settings_page_mailyard` (`Settings::SCREEN`). The top-level menu, its 5 submenu entries, the menu icon and the submenu click interceptor are gone. `Settings::url( $route )` is now the one way to link into the app. Old `admin.php?page=mailyard…` URLs 302 there with query args kept, and the `#/…` fragment survives. +- **UI:** the app sidebar became the design system's **topbar** shell (Overview · Connections · Email log · Deliverability · Settings · Help, no icons). Settings' grouped rail became one header + `Tabs` row, with a `SectionIntro` line per section. Dashboard is labelled **Overview**; "Data & danger" is now "Data". +- **Notices:** the send-failure notice shows on the WP Dashboard + Plugins screens only, with one dismiss path (the non-persistent × is gone). +- **CSS gotcha fixed:** `#mailyard-admin * { border-color }` had id specificity and silently overrode every authored `border-*`/`divide-*` colour plus library component borders. It's now `:where(#mailyard-admin) *`, so authored colours (e.g. the blue selected preset chip) show for the first time. +- **QA:** WP Playground via `npx @wp-playground/cli` (needs **node@22**: `PATH=/opt/homebrew/opt/node@22/bin:$PATH`; Node 26 has no fs-ext prebuild) with a scratchpad auto-login mu-plugin. All views + tabs, the legacy redirect, the OAuth-style return, the action link, notice screens + Dismiss, 600/380px widths. The local Studio site (`plugpess.wp.local`) wasn't running. Note: on that site use `studio wp …`, not bare `wp`. +- **Found + filed:** #22, the OAuth error reason never reaches the toast (core's canonical-URL script strips the `message` query arg). This was already happening before #20. + +## Earlier session (2026-10-06) — simple SMTP: Pro removed, Meow Mailer parity (#17, PR #18) - **Direction change (user):** Mailyard is a standalone, simple SMTP plugin — no Mailyard Pro, no marketing layer — at feature parity with [Meow Mailer](https://wordpress.org/plugins/meow-mailer/), keeping Mailyard's own edges (failover chain, sender routing, bounce webhooks, deliverability checker, MCP tools). Scope choice "option b": drop Pro hooks + Marketing purpose, keep everything SMTP. - **All 11 checklist items of #17 landed via PR #18, squash-merged to `main` as `c0d9d22` with `[skip actions]`, so WordPress.org was NOT synced and still shows 1.0.1: Pro shell/filters + Marketing purpose removed; send pipeline rewritten (`Message` parser — **Cc/Bcc in headers were silently dropped for every provider**, now fixed; one message to all recipients); Offline mode; Background sending; PHP Mail as a real connection; log filters/paging/CSV/retention/top errors; alerts (email unrouted, Slack/Discord/Teams/JSON webhook, weekly summary); WordPress email switches; Return path; importer (WP Mail SMTP/Easy WP SMTP/FluentSMTP/Post SMTP incl. their encryption); wp-config constants + sodium encryption + settings backup; 6 API providers + 4 OAuth (Gmail, M365, M365 app-only, Zoho); multisite shared settings; dashboard setup checks. @@ -78,6 +88,9 @@ - Regenerated `languages/mailyard.pot`; earlier full audit: 0 blockers (hardening backlog: mask connection secrets on read, webhook signature verification, React i18n — file as board issues). ## Next up +- **Merge PR #21 (needs your OK):** it touches `readme.txt`, so a plain merge to `main` fires `assets.yml` and syncs the readme (unreleased 1.1.0 changelog) to WordPress.org right away. Squash with `[skip actions]` in the title, as #18 did, unless you want it live. It's also ~520 changed lines. After merge: remove the `in-progress` label from #20 and move the card to Done. +- **#23:** add `'default' => array()` to the optional-input ability schemas. Today a plain Abilities REST call with no input 400s; MCP/Saddle are unaffected. Verified 2026-10-09: on `main` + Saddle 1.5.1 all 4 read-only tools register as `saddle/mailyard-*` and run, and live plugpress.io answers through Saddle MCP. +- **#22:** rename the OAuth `message` return arg (e.g. `mailyard_oauth_message`) in `class-oauth.php` + `Connections.jsx`. - **Release 1.1.0 when ready:** set `Version:` + `MAILYARD_VERSION` + `Stable tag` to 1.1.0 (changelog already written; header still says 1.0.2), merge, then tag `v1.1.0`. That deploys the code and syncs the readme to WordPress.org. Do the live-send QA below first. - **Delete Pro branches on origin (blocked for me):** `git push origin --delete feat/merge-pro feat/freemius-parent feat/smtp-ux-pass feat/universal-shell`. - With reads unblocked: delete the now-unused `src/hooks/useLogs.js`; have `Data_Deleter`/`uninstall.php` also clear `mailyard_network` (site option), the `mailyard_alerted_*` transients and the `mailyard_weekly_summary` cron; consider folding `Conflicts` into the new `Checks`. diff --git a/docs/documentation.md b/docs/documentation.md index 202dda5..73adf33 100644 --- a/docs/documentation.md +++ b/docs/documentation.md @@ -33,7 +33,7 @@ Amazon's Simple Email Service. Very low cost at volume ($0.10 per 1,000 emails). 1. In AWS Console → SES → Verified identities, verify your sending domain. 2. Create an IAM user with `ses:SendRawEmail` permission. 3. Generate an access key and secret for that user. -4. In Mailyard → Connections → Add → Amazon SES, paste the key, secret, and region. +4. In Settings → SMTP → Connections → Add → Amazon SES, paste the key, secret, and region. **Required fields:** Access Key ID, Secret Access Key, AWS Region (e.g. `us-east-1`). @@ -48,7 +48,7 @@ Premium transactional email. Known for the fastest delivery and best inbox place **Setup:** 1. In Postmark → Sender Signatures, add and verify your sending domain. 2. Create a Server and copy its Server API Token. -3. In Mailyard → Connections → Add → Postmark, paste the token. +3. In Settings → SMTP → Connections → Add → Postmark, paste the token. **Required fields:** Server API Token. @@ -65,7 +65,7 @@ Modern email API with a generous free tier (3,000 emails/month free). Simple RES **Setup:** 1. In Resend → Domains, add and verify your sending domain. 2. In Resend → API Keys, create a key. -3. In Mailyard → Connections → Add → Resend, paste the key. +3. In Settings → SMTP → Connections → Add → Resend, paste the key. **Required fields:** API Key. @@ -78,7 +78,7 @@ Free tier includes 300 emails per day with no monthly cap on contacts. **Setup:** 1. In Brevo → Senders & Domains, authenticate your domain (adds DKIM and SPF records). 2. In Brevo → SMTP & API → API Keys, create a key. -3. In Mailyard → Connections → Add → Brevo, paste the key. +3. In Settings → SMTP → Connections → Add → Brevo, paste the key. **Required fields:** API Key. @@ -90,7 +90,7 @@ Use any SMTP server: Gmail app passwords, Microsoft 365, Mailgun SMTP, your own **Setup:** 1. Gather your SMTP host, port, encryption type, and credentials from your provider. -2. In Mailyard → Connections → Add → Custom SMTP, fill in the fields. +2. In Settings → SMTP → Connections → Add → Custom SMTP, fill in the fields. **Required fields:** SMTP Host, Port (default 587). @@ -113,7 +113,7 @@ Uses WordPress's own `wp_mail()` without modification — whatever your hosting ### Priority order -Connections are attempted in priority order (1 = primary, 2 = first backup, etc.). Drag and drop in Mailyard → Connections to reorder them. +Connections are attempted in priority order (1 = primary, 2 = first backup, etc.). Drag and drop in Settings → SMTP → Connections to reorder them. The **primary** connection handles every email under normal conditions. If it returns an error, Mailyard immediately tries the next enabled connection — still inside the same `wp_mail()` call. @@ -144,7 +144,7 @@ The routing engine filters connections by purpose before selecting the chain. ## Email logging -When logging is enabled (Settings → Email logging), every outgoing email is recorded to a custom database table (`wp_mailyard_logs`). +When logging is enabled (Settings → SMTP → Settings → Delivery → Email logging), every outgoing email is recorded to a custom database table (`wp_mailyard_logs`). ### What is logged @@ -161,7 +161,7 @@ When logging is enabled (Settings → Email logging), every outgoing email is re ### Viewing logs -Go to Mailyard → Logs. You can: +Go to Settings → SMTP → Email log. You can: - Filter by status (All / Sent / Failed) - Search by recipient address or subject - Page through results (20 per page) @@ -170,13 +170,13 @@ Go to Mailyard → Logs. You can: Logs older than 30 days are deleted automatically via a daily WP-Cron job (`mailyard_daily_cleanup`). This keeps the database from growing unbounded. -To disable logging entirely, go to Settings → Email logging and toggle it off. Existing logs are kept until they age out or you uninstall the plugin. +To disable logging entirely, go to Settings → SMTP → Settings → Delivery and switch Email logging off. Existing logs are kept until they age out or you uninstall the plugin. --- ## Deliverability checker -Mailyard → Deliverability scans your sending domain's DNS records and scores your authentication setup from 0–100. +Settings → SMTP → Deliverability scans your sending domain's DNS records and scores your authentication setup from 0–100. ### What it checks @@ -249,7 +249,7 @@ Click **Send test** to send a real email through your live routing chain. Leave ## Connect AI (Abilities API / MCP) -Mailyard registers its delivery tools on the **WordPress Abilities API** (WordPress 7.0+), so an AI assistant can diagnose email problems for you. Control everything in **Settings → Connect AI**. +Mailyard registers its delivery tools on the **WordPress Abilities API** (WordPress 7.0+), so an AI assistant can diagnose email problems for you. Control everything in **Settings → SMTP → Settings → Connect AI**. ### The tools @@ -265,14 +265,14 @@ Every tool requires the `manage_options` capability. Credentials (`config`), and ### Permissions -**Settings → Connect AI** has a master switch ("AI access") and a switch per tool. Turning the master off unregisters every Mailyard ability immediately. The four read-only tools are on by default; `mailyard/send-test-email` is OFF by default because it delivers a real email — turn it on deliberately. +**Settings → SMTP → Settings → Connect AI** has a master switch ("AI access") and a switch per tool. Turning the master off unregisters every Mailyard ability immediately. The four read-only tools are on by default; `mailyard/send-test-email` is OFF by default because it delivers a real email — turn it on deliberately. ### Connecting a client Mailyard exposes abilities but ships **no MCP server** — install a bridge, e.g. the free [WordPress MCP Adapter](https://github.com/wordpress/mcp-adapter). Then: 1. Create an Application Password (**Users → Profile → Application Passwords**). -2. Copy the endpoint from Settings → Connect AI (the "How to connect" guide has copy buttons). +2. Copy the endpoint from Settings → SMTP → Settings → Connect AI (the "How to connect" guide has copy buttons). 3. Add it to your client, e.g. Claude Code: ```bash diff --git a/includes/class-alerts.php b/includes/class-alerts.php index 2e5e680..af8a982 100644 --- a/includes/class-alerts.php +++ b/includes/class-alerts.php @@ -136,7 +136,7 @@ public function test( string $channel, string $target = '' ) { */ public static function webhook_payload( string $url, string $event, string $text, array $extra = array() ): array { $host = strtolower( (string) wp_parse_url( $url, PHP_URL_HOST ) ); - $logs = admin_url( 'admin.php?page=mailyard#/logs' ); + $logs = Settings::url( 'logs' ); if ( preg_match( '/(^|\.)slack\.com$/', $host ) ) { return array( 'text' => $text . "\n" . $logs ); @@ -183,7 +183,7 @@ private function claim( string $kind ): bool { private function notify( string $subject, array $lines, string $event, array $extra ): void { if ( ! empty( Options::settings()['alert_email'] ) ) { - $this->send_unrouted( $this->recipient(), $subject, implode( "\n\n", $lines ) . "\n\n" . __( 'The email log:', 'mailyard' ) . ' ' . admin_url( 'admin.php?page=mailyard#/logs' ) . $this->footer() ); + $this->send_unrouted( $this->recipient(), $subject, implode( "\n\n", $lines ) . "\n\n" . __( 'The email log:', 'mailyard' ) . ' ' . Settings::url( 'logs' ) . $this->footer() ); } $this->post_webhook( $event, $subject . ' — ' . implode( ' ', array_slice( $lines, 1 ) ), $extra ); } @@ -232,6 +232,7 @@ private function site_name(): string { } private function footer(): string { - return "\n\n— " . __( 'Sent by Mailyard. Change alerts in Mailyard → Settings → Alerts.', 'mailyard' ); + /* translators: %s: URL of Mailyard's alert settings. */ + return "\n\n— " . sprintf( __( 'Sent by Mailyard. Change alerts: %s', 'mailyard' ), Settings::url( 'settings/alerts' ) ); } } diff --git a/includes/class-conflicts.php b/includes/class-conflicts.php index 52ad5f6..5f5e407 100644 --- a/includes/class-conflicts.php +++ b/includes/class-conflicts.php @@ -49,7 +49,7 @@ public function maybe_show_notice(): void { } $screen = get_current_screen(); - if ( $screen && ! in_array( $screen->id, array( 'toplevel_page_mailyard', 'plugins' ), true ) ) { + if ( $screen && ! in_array( $screen->id, array( Settings::SCREEN, 'plugins' ), true ) ) { return; } diff --git a/includes/class-failure-notice.php b/includes/class-failure-notice.php index 2420661..9528705 100644 --- a/includes/class-failure-notice.php +++ b/includes/class-failure-notice.php @@ -4,8 +4,8 @@ defined( 'ABSPATH' ) || exit; /** - * Surfaces email send failures as a dismissible admin notice with a link to the - * log. Records the latest failure (fed by the `mailyard_send_failed` action) and + * Surfaces email send failures as a dismissible admin notice (Dashboard and + * Plugins screens) with a link to the log. Records the latest failure (fed by the `mailyard_send_failed` action) and * auto-clears it when a later send succeeds or the connection config changes, so * a fixed setup dismisses the warning on its own — it isn't nagware. */ @@ -82,9 +82,10 @@ public function render(): void { return; } - // The log is already on the Mailyard admin page — don't double up there. + // Only where an admin lands — the Dashboard and Plugins screens — not + // on every admin page. Mailyard's own page shows it in its overview. $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; - if ( $screen && false !== strpos( (string) $screen->id, 'page_mailyard' ) ) { + if ( ! $screen || ! in_array( $screen->id, array( 'dashboard', 'plugins' ), true ) ) { return; } @@ -98,7 +99,7 @@ public function render(): void { $title = isset( $failure['title'] ) ? $failure['title'] : __( 'Email could not be sent', 'mailyard' ); $guidance = isset( $failure['guidance'] ) ? $failure['guidance'] : ''; - $log_url = admin_url( 'admin.php?page=mailyard#/logs?status=failed' ); + $log_url = Settings::url( 'logs?status=failed' ); $dismiss_url = wp_nonce_url( add_query_arg( 'mailyard_dismiss_failure', '1' ), 'mailyard_dismiss_failure' @@ -109,11 +110,12 @@ public function render(): void { ? sprintf( _n( 'Mailyard: %d email failed to send', 'Mailyard: %d emails failed to send recently', $count, 'mailyard' ), $count ) : __( 'Mailyard: an email failed to send', 'mailyard' ); + // One dismiss path: the link (per user, until the next failure). No + // `is-dismissible` × — it only hides the notice until the next page load. printf( - '

%s — %s %s

%s %s

', + '

%s — %s

%s %s

', esc_html( $heading . ( $title ? ': ' . $title : '' ) ), esc_html( $guidance ), - '', esc_url( $log_url ), esc_html__( 'View email log', 'mailyard' ), esc_url( $dismiss_url ), diff --git a/includes/class-oauth.php b/includes/class-oauth.php index b3779ff..3ef927a 100644 --- a/includes/class-oauth.php +++ b/includes/class-oauth.php @@ -141,7 +141,7 @@ public static function return_url( $result ): string { $args = is_wp_error( $result ) ? array( 'mailyard_oauth' => 'error', 'message' => $result->get_error_message() ) : array( 'mailyard_oauth' => 'ok' ); - return admin_url( 'admin.php?page=mailyard&' . http_build_query( $args, '', '&', PHP_QUERY_RFC3986 ) . '#/connections' ); + return add_query_arg( rawurlencode_deep( $args ), admin_url( 'options-general.php?page=' . Settings::PAGE ) ) . '#/connections'; } /** diff --git a/includes/class-plugin.php b/includes/class-plugin.php index 149735c..906e90f 100644 --- a/includes/class-plugin.php +++ b/includes/class-plugin.php @@ -130,7 +130,7 @@ public function run_cleanup(): void { } public function plugin_action_links( array $links ): array { - $url = admin_url( 'admin.php?page=mailyard' ); + $url = Settings::url(); $label = __( 'Settings', 'mailyard' ); array_unshift( $links, '' . $label . '' ); return $links; diff --git a/includes/class-settings.php b/includes/class-settings.php index 49fa7da..113d582 100644 --- a/includes/class-settings.php +++ b/includes/class-settings.php @@ -6,6 +6,11 @@ // Admin page and asset loading for the Mailyard React UI. class Settings { + const PAGE = 'mailyard'; + + // The page's screen id / hook suffix under Settings. + const SCREEN = 'settings_page_mailyard'; + private static $instance = null; public static function instance(): self { @@ -22,72 +27,58 @@ public function init() { add_action( 'admin_init', array( $this, 'redirect_legacy_url' ) ); } + /** + * The admin page URL, optionally at an app route ('logs', 'connections', …). + * Every link into the app goes through here. + */ + public static function url( string $route = '' ): string { + $url = admin_url( 'options-general.php?page=' . self::PAGE ); + return '' === $route ? $url : $url . '#/' . ltrim( $route, '#/' ); + } + /** * PlugPress design system token scope on , so portaled overlays * (dialogs, dropdowns, toasts) inherit the --pp-* custom properties. */ public function body_class( $classes ) { $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; - if ( $screen && 'toplevel_page_mailyard' === $screen->id ) { + if ( $screen && self::SCREEN === $screen->id ) { $classes .= ' pp-scope'; } return $classes; } + // Settings → SMTP: one page, the app's own top bar handles the sections. public function add_menu() { - // '58.14' is Mailyard's reserved slot in the PlugPress admin-menu band - // (plugpress-ui php/menu-positions.php) — a decimal STRING so it can - // never clobber a third-party menu registered at the same integer. - add_menu_page( - __( 'Mailyard', 'mailyard' ), - __( 'Mailyard', 'mailyard' ), + add_options_page( + __( 'Mailyard SMTP', 'mailyard' ), + __( 'SMTP', 'mailyard' ), 'manage_options', - 'mailyard', - array( $this, 'render' ), - $this->menu_icon(), - '58.14' - ); - - // One native submenu entry per page, mirroring the app's sidebar (NAV in - // src/App.jsx). Keys are the SPA hash route each entry lands on. - $submenus = array( - 'dashboard' => __( 'Dashboard', 'mailyard' ), - 'connections' => __( 'Connections', 'mailyard' ), - 'logs' => __( 'Email log', 'mailyard' ), - 'deliverability' => __( 'Deliverability', 'mailyard' ), - 'settings' => __( 'Settings', 'mailyard' ), + self::PAGE, + array( $this, 'render' ) ); - - $first = true; - foreach ( $submenus as $key => $label ) { - add_submenu_page( - 'mailyard', - $label, - $label, - 'manage_options', - $first ? 'mailyard' : 'mailyard#/' . $key, - array( $this, 'render' ) - ); - $first = false; - } } /** - * 302 the pre-1.2 Settings-page URL to the top-level menu. Browsers carry - * the URL fragment across the redirect, so old deep bookmarks - * (options-general.php?page=mailyard#/logs) keep working. + * 302 the old top-level menu URL (admin.php?page=mailyard) to Settings → + * SMTP, keeping its query args (an OAuth return carries its outcome there). + * Browsers carry the URL fragment across the redirect, so old deep links + * (…page=mailyard#/logs, in alert emails already sent) keep working. */ public function redirect_legacy_url() { global $pagenow; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only redirect. - if ( 'options-general.php' === $pagenow && 'mailyard' === sanitize_key( wp_unslash( $_GET['page'] ?? '' ) ) ) { - wp_safe_redirect( admin_url( 'admin.php?page=mailyard' ) ); - exit; + if ( 'admin.php' !== $pagenow || self::PAGE !== sanitize_key( wp_unslash( $_GET['page'] ?? '' ) ) ) { + return; } + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only redirect. + $args = map_deep( wp_unslash( $_GET ), 'sanitize_text_field' ); + wp_safe_redirect( add_query_arg( rawurlencode_deep( $args ), admin_url( 'options-general.php' ) ) ); + exit; } public function enqueue( $hook ) { - if ( 'toplevel_page_mailyard' !== $hook ) { + if ( self::SCREEN !== $hook ) { return; } @@ -128,43 +119,6 @@ private function locked_fields(): object { } public function render() { - ?> -
- - ' - . '' - . ''; - - return 'data:image/svg+xml;base64,' . base64_encode( $svg ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode + echo '
'; } } diff --git a/readme.txt b/readme.txt index a1aad4f..d8fa66a 100644 --- a/readme.txt +++ b/readme.txt @@ -111,9 +111,9 @@ Terms: https://www.cloudflare.com/website-terms/ — Privacy: https://developers == Installation == 1. Search for "Mailyard" in Plugins → Add New (or upload the zip), then activate. -2. Open the Mailyard menu in your admin sidebar. +2. Open Settings → SMTP. 3. Pick a provider, enter your API key, set your sender address. -4. Hit Send test on the Dashboard to confirm it works. +4. Hit Send test on the Overview to confirm it works. = Adding a backup provider = @@ -172,7 +172,7 @@ Yes. Every feature you can see is yours — failover, routing, bounce tracking, == Screenshots == 1. Setup — pick a provider, paste your key, set your sender address. -2. Dashboard — sending health, the last 14 days of volume, recent activity. +2. Overview — sending health, the last 14 days of volume, recent activity. 3. Connections — add providers and drag to choose the primary and its backup. 4. Deliverability — every sending domain graded A–F, with the exact DNS fix for anything that fails. 5. Email Logs — every send, with status and the error if it failed. @@ -199,11 +199,12 @@ Yes. Every feature you can see is yours — failover, routing, bounce tracking, * Add: Optional encryption of stored credentials, with a clear warning if the site's security keys change. * Add: Multisite shared settings — set email up once on the main site and let every site use it; the sender and delivery options are each optional to share. Logs always stay per site. * Add: Settings backup — export and import settings and connections as one file; empty the log on its own. -* Add: Dashboard panel with the week's most common failure reasons, in plain words. -* Add: Setup checks on the Dashboard — another plugin replacing wp_mail(), a From address on another domain, and Contact Form 7 / WPForms / Gravity Forms set to send "from" the visitor. +* Add: Overview panel with the week's most common failure reasons, in plain words. +* Add: Setup checks on the Overview — another plugin replacing wp_mail(), a From address on another domain, and Contact Form 7 / WPForms / Gravity Forms set to send "from" the visitor. * Update: Any logged email can be resent, not only failed ones. * Add: Send test names the provider that delivered it, and says so when a backup had to take over. -* Update: Simpler admin — one sidebar (Dashboard, Connections, Email log, Deliverability, Settings), mirrored in the WordPress menu. +* Update: Mailyard now lives under Settings → SMTP instead of its own top-level menu, with one slim top bar (Overview, Connections, Email log, Deliverability, Settings) and settings sections as tabs. Old links and bookmarks redirect. +* Update: The "email failed to send" notice shows on the WordPress Dashboard and Plugins screens only, with a single Dismiss link. * Update: Connections no longer have a Marketing/Transactional purpose; every enabled connection carries all mail. A connection that was set to Marketing only is switched off on update (its settings are kept). * Update: An email to several recipients goes out as one message, as WordPress sends it, instead of one copy per address. * Fix: Cc and Bcc addresses set in email headers were dropped by every provider. diff --git a/src/App.jsx b/src/App.jsx index d7b934a..11dcadc 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,7 +1,6 @@ import { useState, useCallback, useEffect, Suspense, lazy } from 'react'; import { AppShell, Toaster } from '@plugpress/ui'; -import Sidebar from './components/Sidebar'; -import { GridIcon, RouteIcon, ShieldIcon, ListIcon, GearIcon } from './components/Icons'; +import Nav from './components/Nav'; import { DashboardSkeleton, ConnectionsSkeleton, @@ -15,24 +14,14 @@ const Deliverability = lazy( () => import( './views/Deliverability' ) ); const Logs = lazy( () => import( './views/Logs' ) ); const Settings = lazy( () => import( './views/Settings' ) ); -// The whole navigation: one list of pages, Settings pinned to the footer. -// Keys are the hash routes; nested routes (settings/connect-ai) belong to the -// first segment. +// The whole navigation: one row of pages in the top bar. Routes are the hash +// routes; nested routes (settings/connect-ai) belong to the first segment. const NAV = [ - { - id: 'main', - items: [ - { id: 'dashboard', label: 'Dashboard', icon: GridIcon, route: 'dashboard' }, - { id: 'connections', label: 'Connections', icon: RouteIcon, route: 'connections' }, - { id: 'logs', label: 'Email log', icon: ListIcon, route: 'logs' }, - { id: 'deliverability', label: 'Deliverability', icon: ShieldIcon, route: 'deliverability' }, - ], - }, - { - id: 'system', - footer: true, - items: [ { id: 'settings', label: 'Settings', icon: GearIcon, route: 'settings' } ], - }, + { label: 'Overview', route: 'dashboard' }, + { label: 'Connections', route: 'connections' }, + { label: 'Email log', route: 'logs' }, + { label: 'Deliverability', route: 'deliverability' }, + { label: 'Settings', route: 'settings' }, ]; const SKELETONS = { @@ -71,33 +60,20 @@ export default function App() { return () => window.removeEventListener( 'hashchange', handler ); }, [] ); - // Mirror the active route onto the WP admin submenu highlight for - // programmatic navigation (menu CLICKS are handled by the PHP-side - // interceptor). The submenu mirrors NAV, one entry per page. - useEffect( () => { - const seg = route.split( '/' )[ 0 ] || 'dashboard'; - const items = document.querySelectorAll( '#adminmenu .wp-submenu li' ); - items.forEach( ( li ) => { - const href = li.querySelector( 'a' )?.getAttribute( 'href' ) || ''; - const hit = href.includes( 'page=mailyard#/' + seg ) || ( 'dashboard' === seg && /page=mailyard$/.test( href ) ); - li.classList.toggle( 'current', hit ); - } ); - }, [ route ] ); - const navigate = useCallback( ( id ) => { window.location.hash = '#/' + id; }, [] ); const Skeleton = SKELETONS[ route.split( '/' )[ 0 ] ] || DashboardSkeleton; - // AppShell owns the sidebar frame (sticky rail, <782px icon-rail collapse) - // and pads the content column; the outlet only centers itself. + // AppShell owns the top bar (sticky under the admin bar) and pads the + // content column; the outlet only centers itself. return ( <> } + variant="topbar" + nav={