diff --git a/.superpowers/brainstorm/1730-1779602779/content/full-form-mockup.html b/.superpowers/brainstorm/1730-1779602779/content/full-form-mockup.html new file mode 100644 index 0000000..760119e --- /dev/null +++ b/.superpowers/brainstorm/1730-1779602779/content/full-form-mockup.html @@ -0,0 +1,139 @@ +

Full Form Mockup — New Purchase Request

+

Showing the improved header section with all dropdowns and the urgency popup. Click "Urgency" to see the popup demo.

+ +
+
Purchase Request Form — Header Section
+
+ + +
+
Project / Department Details
+ +
+ + +
+
Date *
+
2026-05-24
+
+ + +
+
Project / Site Name *
+
+ Select project… + +
+
+ + +
+
Requested By *
+
Ahmed Al-Mansoori
+
+ + +
+
Required Date / Urgency
+
+ Select urgency… + 🎯 +
+ + + +
+ + +
+
Location / Site
+
+ Select location… + +
+
+ + +
+
Department
+
e.g. Operations, Production
+
+ +
+
+ + +
+ ⚙️ New Settings section in sidebar — Admins can manage Locations, Projects, and Urgency Levels from here. Each has a simple list with Add / Edit / Delete. +
+
+
+ + diff --git a/.superpowers/brainstorm/1730-1779602779/content/urgency-picker.html b/.superpowers/brainstorm/1730-1779602779/content/urgency-picker.html new file mode 100644 index 0000000..3904987 --- /dev/null +++ b/.superpowers/brainstorm/1730-1779602779/content/urgency-picker.html @@ -0,0 +1,87 @@ +

Required Date / Urgency — which style feels right?

+

When the user clicks the field, a popup opens. Pick the style you prefer.

+ +
+ + +
+
+
Urgency
+
+
Select urgency level
+
+
🔴 Critical / Immediate
+
🟠 Urgent — Within 3 days
+
🟡 This Week
+
🟢 Normal — This Month
+
📅 Pick a specific date →
+
+
+
+
+

A — Urgency Levels

+

Colored pills with preset labels. Optionally jump to a date picker.

+
+
+ + +
+
+
Urgency
+
+
How soon is this needed?
+
+
+
🚨
+
Critical
+
Today
+
+
+
+
Urgent
+
1–3 days
+
+
+
📋
+
Normal
+
This week
+
+
+
🗓️
+
Planned
+
Pick date
+
+
+
+
+
+

B — Icon Cards

+

2×2 grid of icon cards with urgency label + subtitle. Visually distinct.

+
+
+ + +
+
+
Urgency
+
+
Quick select or pick a date
+
+ 🔴 Critical + ⚡ Urgent + 📋 This Week + ✅ Normal +
+
+
Or pick a specific date:
+
📅 Select date…
+
+
+
+
+

C — Chips + Date

+

Pill-shaped tags for quick picks, plus a date field below for exact dates.

+
+
+ +
diff --git a/.superpowers/brainstorm/1730-1779602779/content/waiting-1.html b/.superpowers/brainstorm/1730-1779602779/content/waiting-1.html new file mode 100644 index 0000000..ef07652 --- /dev/null +++ b/.superpowers/brainstorm/1730-1779602779/content/waiting-1.html @@ -0,0 +1,3 @@ +
+

Continuing in terminal...

+
diff --git a/.superpowers/brainstorm/1730-1779602779/state/server-stopped b/.superpowers/brainstorm/1730-1779602779/state/server-stopped new file mode 100644 index 0000000..ad0850f --- /dev/null +++ b/.superpowers/brainstorm/1730-1779602779/state/server-stopped @@ -0,0 +1 @@ +{"reason":"idle timeout","timestamp":1779606561971} diff --git a/.superpowers/brainstorm/1730-1779602779/state/server.pid b/.superpowers/brainstorm/1730-1779602779/state/server.pid new file mode 100644 index 0000000..78d5c2a --- /dev/null +++ b/.superpowers/brainstorm/1730-1779602779/state/server.pid @@ -0,0 +1 @@ +1730 diff --git a/.superpowers/brainstorm/2032-1779707041/content/approach-selection.html b/.superpowers/brainstorm/2032-1779707041/content/approach-selection.html new file mode 100644 index 0000000..1352834 --- /dev/null +++ b/.superpowers/brainstorm/2032-1779707041/content/approach-selection.html @@ -0,0 +1,78 @@ +

Supplier Modal — 3 Redesign Approaches

+

The core problem: the two tabs (Full Order / By Item) are mutually exclusive but look like equals. Choosing one should lock out the other. Here are three ways to fix that.

+ +
+ +
+
A
+
+

Two-Step Wizard Recommended

+

The modal opens on a "choose your method" screen with two large cards. Clicking one card transitions into that mode — no tabs ever appear. A small "← Change method" back link lets you restart if needed.

+
+
+
+
📦
+
Full Order
+
One set of suppliers for everything
+
+
+
🔀
+
By Item
+
Different suppliers per item
+
+
+
↓ clicking a card opens that flow directly
+
+
+

Pros

  • Zero ambiguity — only one path at a time
  • Clean separation of concerns
  • Escape hatch via back link
+

Cons

  • Extra click before seeing suppliers
+
+
+
+ +
+
B
+
+

Wizard with Step Indicator

+

Same two-step wizard as A, but with a visible step breadcrumb at the top: ① Choose Method → ② Select Suppliers. Adds visual progress context for users who need orientation.

+
+
+
1
+
Choose Method
+
+
2
+
Select Suppliers
+
+
step indicator lives in the modal header
+
+
+

Pros

  • Users see where they are in the flow
  • Familiar wizard UX pattern
+

Cons

  • Slightly more UI chrome
  • Overkill for a 2-step flow
+
+
+
+ +
+
C
+
+

Prominent Mode Toggle (No Wizard)

+

Keep a single-screen modal but replace the tabs with a large segmented toggle at the top. Switching mode clears all selections and shows a confirmation toast. Less structural change, but still communicates mutual exclusivity.

+
+
+
+ 📦 Full Order +
+
+ 🔀 By Item +
+
+
⚠ Switching clears your current selections
+
+
+

Pros

  • Fewest changes to existing code
  • Both options always visible
+

Cons

  • Still looks like "tabs" — same confusion risk
  • Warning toast feels like an afterthought
+
+
+
+ +
diff --git a/.superpowers/brainstorm/2032-1779707041/content/back-behavior.html b/.superpowers/brainstorm/2032-1779707041/content/back-behavior.html new file mode 100644 index 0000000..aeeb6f9 --- /dev/null +++ b/.superpowers/brainstorm/2032-1779707041/content/back-behavior.html @@ -0,0 +1,36 @@ +

Back Button Behavior

+

When the user clicks "← Change method" from step 2, what happens to their selections?

+ +
+ +
+
A
+
+

Clear selections on back Recommended

+

Going back always resets everything. Switching from Full Order → By Item (or vice versa) is a fundamentally different configuration, so a clean slate avoids stale state.

+
+ Flow: Pick method → select suppliers → back → selections cleared → pick method again → fresh start +
+
+

Pros

  • Simple, no hidden state
  • No risk of mixing Full Order + By Item data
+

Cons

  • User loses work if they go back accidentally
+
+
+
+ +
+
B
+
+

Preserve selections on back

+

Selections are remembered per-mode. If the user goes back and re-enters the same mode, their previous picks are still there. Switching to the other mode starts fresh for that mode only.

+
+ Flow: Full Order → select 3 suppliers → back → pick Full Order again → 3 suppliers still checked +
+
+

Pros

  • Forgiving — accidental back doesn't lose work
+

Cons

  • More complex state management
  • Can confuse users who expect a fresh start
+
+
+
+ +
diff --git a/.superpowers/brainstorm/2032-1779707041/content/wizard-mockup.html b/.superpowers/brainstorm/2032-1779707041/content/wizard-mockup.html new file mode 100644 index 0000000..ddfa0a2 --- /dev/null +++ b/.superpowers/brainstorm/2032-1779707041/content/wizard-mockup.html @@ -0,0 +1,125 @@ +

Wizard Flow Mockup

+

Step 1 (method selection) and what Step 2 looks like after choosing. Does this feel right?

+ +
+ +
+
Step 1 — Choose Method (modal opens here)
+
+ +
+ +
+
+
Request for Quotation
+
How do you want to assign suppliers?
+
+
×
+
+ + +
+ +
+
📦
+
+
Full Order
+
One set of suppliers handles the entire purchase request
+
+ +
+ +
+
🔀
+
+
By Item
+
Assign different suppliers to specific items in this request
+
+ +
+ +
+ + +
+ +
+
+
+
+ +
+
Step 2 — After choosing "Full Order"
+
+
+ +
+
+ +
·
+
📦 Full Order
+
+
Select Suppliers
+
Choose who receives the quote request
+
+ + +
+
+
🔍
+
Search suppliers…
+
+
+ + +
+
+
+ +
+
+
Al-Rashid Steel Trading
+
rashid@steel.com · +966 50 123 4567
+
+
+ Email + WA + Both +
+
+
+
+
+
Gulf Metals Co.
+
info@gulfmetals.com
+
+
+
+
+
+
Emirates Industrial Supply Added
+
+971 4 987 6543
+
+
+
+ + +
+
1 supplier selected
+
+ + +
+
+
+
+
+ +
diff --git a/.superpowers/brainstorm/2032-1779707041/state/server-stopped b/.superpowers/brainstorm/2032-1779707041/state/server-stopped new file mode 100644 index 0000000..21df1d7 --- /dev/null +++ b/.superpowers/brainstorm/2032-1779707041/state/server-stopped @@ -0,0 +1 @@ +{"reason":"idle timeout","timestamp":1779709143287} diff --git a/.superpowers/brainstorm/2032-1779707041/state/server.pid b/.superpowers/brainstorm/2032-1779707041/state/server.pid new file mode 100644 index 0000000..c9cef4a --- /dev/null +++ b/.superpowers/brainstorm/2032-1779707041/state/server.pid @@ -0,0 +1 @@ +2032 diff --git a/.superpowers/brainstorm/2033-1779783948/content/add-account-modal.html b/.superpowers/brainstorm/2033-1779783948/content/add-account-modal.html new file mode 100644 index 0000000..4a8a34a --- /dev/null +++ b/.superpowers/brainstorm/2033-1779783948/content/add-account-modal.html @@ -0,0 +1,168 @@ +

Add Account Modal — Both Type Variants

+

Left: Microsoft 365 (Azure AD) fields. Right: SMTP fields. The type dropdown switches which fields appear. Account name is a slug used in code.

+ +
+ + +
+
Add Mail Account — Microsoft 365
+
+ +
+
Account Name (used in code)
+
+ customer-support +
+
Lowercase, hyphens only. Used as Mail::mailer('customer-support')
+
+ +
+
Type
+
+ ✉️ Microsoft 365 (Azure AD) + +
+
+ +
+
Azure AD Credentials
+ +
+
Tenant ID
+
+ xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx +
+
+
+
Client ID
+
+ xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx +
+
+
+
Client Secret
+
+ •••••••••••• + Show +
+
+
+ +
+
Sender
+
+
From Address
+
+ support@steelco.com +
+
+
+
From Name (optional)
+
+ SteelERP +
+
+
+ +
+ 🔗 Test Connection +
+
Cancel
+
Save Account
+
+
+ +
+
+ + +
+
Add Mail Account — SMTP
+
+ +
+
Account Name (used in code)
+
+ invoices +
+
Lowercase, hyphens only. Used as Mail::mailer('invoices')
+
+ +
+
Type
+
+ 📧 SMTP + +
+
+ +
+
SMTP Server
+ +
+
+
Host
+
+ smtp.gmail.com +
+
+
+
Port
+
+ 587 +
+
+
+
Encryption
+
+ TLS + +
+
+
+ +
+
Username
+
+ invoices@steelco.com +
+
+
+
Password
+
+ •••••••••••• + Show +
+
+
+ +
+
Sender
+
+
From Address
+
+ invoices@steelco.com +
+
+
+
From Name (optional)
+
+ SteelERP Invoices +
+
+
+ +
+ 🔗 Test Connection +
+
Cancel
+
Save Account
+
+
+ +
+
+ +
+ +

Both variants use the same modal shell — only the credential section changes based on the Type dropdown. Does this look right? Let me know in the terminal.

diff --git a/.superpowers/brainstorm/2033-1779783948/content/email-tab-design.html b/.superpowers/brainstorm/2033-1779783948/content/email-tab-design.html new file mode 100644 index 0000000..191806b --- /dev/null +++ b/.superpowers/brainstorm/2033-1779783948/content/email-tab-design.html @@ -0,0 +1,133 @@ +

Email Tab — Full Design

+

Here's what the Email (Microsoft 365) tab looks like with all its fields. Does this match what you had in mind?

+ +
+
Settings — Integrations (Email tab active)
+
+
+ + +
+
Settings — Integrations
+
Configure third-party service integrations.
+
+ + +
+
+ 💬 WhatsApp +
+
+ ✉️ Email +
+
+ + +
+ + +
+
✉️
+
+
Microsoft 365 (Azure Mail)
+
Send emails via Microsoft Graph API using Azure AD
+
+
+ + +
+ + +
+
+
Enable Email Notifications
+
When disabled, no emails will be sent.
+
+
+
+
+
+ +
+ + +
+
Tenant ID
+
+ xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx +
+
+ + +
+
Client ID
+
+ xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx +
+
+ + +
+
Client Secret
+
+ •••••••••••••••• + Show +
+
+ + +
+
From Address
+
+ noreply@yourdomain.com +
+
Must be a mailbox in your Microsoft 365 tenant.
+
+ + +
+
+ 🔗 Test Connection +
+
Save Settings
+
+ +
+
+ + +
+
+
+ 📧 + Send Test Email + — verify the connection works end-to-end +
+ +
+ +
+
+
+
To
+
+ recipient@example.com +
+
+
+
Subject
+
+ Test Email from SteelERP +
+
+
+ ✉️ Send Email +
+
+
+ +
+
+
+ +

Does this look right? Let me know in the terminal if you'd like any changes — different fields, different layout, etc.

diff --git a/.superpowers/brainstorm/2033-1779783948/content/integrations-layout.html b/.superpowers/brainstorm/2033-1779783948/content/integrations-layout.html new file mode 100644 index 0000000..dc2f40f --- /dev/null +++ b/.superpowers/brainstorm/2033-1779783948/content/integrations-layout.html @@ -0,0 +1,117 @@ +

Integrations Page — Tab Layout

+

Here's how the integrations page would look with WhatsApp and Email tabs. Which tab style feels right?

+ +
+ + +
+
+
+
+
Settings — Integrations
+
Configure third-party service integrations.
+
+ +
+
+ 💬WhatsApp +
+
+ ✉️Email +
+
+ +
+
+
+ WhatsApp (UltraMSG) + Active +
+
+
+
+
+
+
+
+
+

A — Pill Tabs

+

Rounded pill-style tabs. Modern, clean. Active tab is filled dark.

+
+
+ + +
+
+
+
+
Settings — Integrations
+
Configure third-party service integrations.
+
+ +
+
+ 💬WhatsApp +
+
+ ✉️Email +
+
+ +
+
+
+ WhatsApp (UltraMSG) + Active +
+
+
+
+
+
+
+
+
+

B — Underline Tabs

+

Classic tab style with bottom border indicator. Matches settings pages in most ERP systems.

+
+
+ + +
+
+
+
+
Settings — Integrations
+
Configure third-party service integrations.
+
+ +
+
+
+ 💬WhatsApp +
+
+ ✉️Email +
+
+
+
+
+ WhatsApp (UltraMSG) +
+
+
+
+
+
+
+
+
+
+

C — Sidebar Nav

+

Left sidebar with integration list. Scales well if more integrations are added later.

+
+
+ +
diff --git a/.superpowers/brainstorm/2033-1779783948/content/mail-accounts-list.html b/.superpowers/brainstorm/2033-1779783948/content/mail-accounts-list.html new file mode 100644 index 0000000..6c33d3b --- /dev/null +++ b/.superpowers/brainstorm/2033-1779783948/content/mail-accounts-list.html @@ -0,0 +1,106 @@ +

Email Tab — Multi-Account Design

+

The Email tab becomes a list of named accounts. Each account has a type badge, from address, and actions. Here's how it looks with a few accounts configured.

+ +
+
Settings — Integrations (Email tab active)
+
+
+ + +
+
Settings — Integrations
+
Configure third-party service integrations.
+
+ + +
+
💬 WhatsApp
+
✉️ Email
+
+ + +
+
+
Mail Accounts
+
3 accounts configured
+
+
+ + Add Account +
+
+ + +
+ + +
+
✉️
+
+
+ customer-support + Microsoft 365 +
+
support@steelco.com
+
+ +
+
+
+
+ + +
+
+ + +
+
📧
+
+
+ invoices + SMTP +
+
invoices@steelco.com · smtp.gmail.com:587
+
+ +
+
+
+
+ + +
+
+ + +
+
📧
+
+
+ notifications + SMTP +
+
no-reply@steelco.com · mail.steelco.com:465
+
+ +
+
+
+
+ + +
+
+ +
+ + +
+ Use account names in code: Mail::mailer('customer-support') +
+ +
+
+
+ +

This is the Email tab redesigned as a list. Click "Add Account" opens a modal (next screen). Does this list layout look right? Let me know in the terminal.

diff --git a/.superpowers/brainstorm/2033-1779783948/content/waiting-2.html b/.superpowers/brainstorm/2033-1779783948/content/waiting-2.html new file mode 100644 index 0000000..ef07652 --- /dev/null +++ b/.superpowers/brainstorm/2033-1779783948/content/waiting-2.html @@ -0,0 +1,3 @@ +
+

Continuing in terminal...

+
diff --git a/.superpowers/brainstorm/2033-1779783948/content/waiting.html b/.superpowers/brainstorm/2033-1779783948/content/waiting.html new file mode 100644 index 0000000..d9aa2b4 --- /dev/null +++ b/.superpowers/brainstorm/2033-1779783948/content/waiting.html @@ -0,0 +1,3 @@ +
+

Design approved — writing the implementation plan in terminal...

+
diff --git a/.superpowers/brainstorm/2033-1779783948/state/server-stopped b/.superpowers/brainstorm/2033-1779783948/state/server-stopped new file mode 100644 index 0000000..bf152f4 --- /dev/null +++ b/.superpowers/brainstorm/2033-1779783948/state/server-stopped @@ -0,0 +1 @@ +{"reason":"idle timeout","timestamp":1779788510216} diff --git a/.superpowers/brainstorm/2033-1779783948/state/server.pid b/.superpowers/brainstorm/2033-1779783948/state/server.pid new file mode 100644 index 0000000..c0bc52f --- /dev/null +++ b/.superpowers/brainstorm/2033-1779783948/state/server.pid @@ -0,0 +1 @@ +2033 diff --git a/docs/superpowers/plans/2026-05-26-integrations-tabs.md b/docs/superpowers/plans/2026-05-26-integrations-tabs.md new file mode 100644 index 0000000..e4a5ab0 --- /dev/null +++ b/docs/superpowers/plans/2026-05-26-integrations-tabs.md @@ -0,0 +1,772 @@ +# Integrations Tabs Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add pill-style WhatsApp / Email tabs to the Settings → Integrations page, convert WhatsApp save to AJAX, and add a full Microsoft 365 (Azure Mail) configuration panel. + +**Architecture:** Three-file change — routes add 3 new Azure POST endpoints, SettingsController gains 3 new methods and converts `updateWhatsapp()` from redirect to JSON, and `integrations.blade.php` is fully rewritten with Alpine.js tab switching, AJAX saves, and the new Email panel. No new models or migrations needed; all Azure credentials are persisted via the existing `Setting::get/set()` key-value store. + +**Tech Stack:** Laravel 12, Alpine.js v3, `PromoSeven\AzureMailer\Graph\TokenManager` (local path package), `Illuminate\Support\Facades\Mail`, `Setting` model key-value store. + +--- + +## File Map + +| File | Change | +|------|--------| +| `routes/web.php` | Add 3 POST routes inside the existing `role:Admin` group | +| `app/Http/Controllers/SettingsController.php` | Update `integrations()`, convert `updateWhatsapp()` to JSON, add `updateAzureMail()`, `testAzureMailConnection()`, `sendTestEmail()` | +| `resources/views/settings/integrations.blade.php` | Full rewrite — pill tabs, WhatsApp panel (AJAX), Email panel with all fields + accordions | + +--- + +### Task 1: Add Azure routes + +**Files:** +- Modify: `routes/web.php` (around line 125, inside the `role:Admin` group after `send-test-message`) + +- [ ] **Step 1: Open `routes/web.php` and locate the settings group** + + Find the block that looks like: + ```php + Route::post('settings/integrations/send-test-message', [SettingsController::class, 'sendTestMessage'])->name('settings.integrations.send-test-message'); + ``` + +- [ ] **Step 2: Add the three Azure routes immediately after that line** + + ```php + Route::post('settings/integrations/azure-mail', [SettingsController::class, 'updateAzureMail'])->name('settings.integrations.azure-mail'); + Route::post('settings/integrations/test-azure-mail', [SettingsController::class, 'testAzureMailConnection'])->name('settings.integrations.test-azure-mail'); + Route::post('settings/integrations/send-test-email', [SettingsController::class, 'sendTestEmail'])->name('settings.integrations.send-test-email'); + ``` + +- [ ] **Step 3: Verify routes are registered** + + Run: + ```bash + php artisan route:list --name=settings.integrations + ``` + Expected: 7 rows — the 4 existing + the 3 new azure ones. + +- [ ] **Step 4: Commit** + + ```bash + git add routes/web.php + git commit -m "feat: add Azure Mail routes to settings integrations" + ``` + +--- + +### Task 2: Update SettingsController + +**Files:** +- Modify: `app/Http/Controllers/SettingsController.php` + +The class currently has 4 methods. This task updates `integrations()`, converts `updateWhatsapp()` return type, and adds 3 new methods. + +- [ ] **Step 1: Add the new use statements at the top of the file (after the existing ones)** + + Add these two lines alongside the existing `use` block: + ```php + use Illuminate\Support\Facades\Mail; + use PromoSeven\AzureMailer\Graph\TokenManager; + ``` + +- [ ] **Step 2: Update `integrations()` to fetch and pass Azure settings** + + Replace the entire `integrations()` method with: + ```php + public function integrations(): View + { + $whatsappSettings = [ + 'enabled' => Setting::get('ultramsg_enabled', false), + 'instance_id' => Setting::get('ultramsg_instance_id', ''), + 'token' => Setting::get('ultramsg_token', ''), + 'webhook_secret' => Setting::get('ultramsg_webhook_secret', ''), + 'webhook_path' => Setting::get('ultramsg_webhook_path', 'ultra-message/webhook'), + ]; + + $azureSettings = [ + 'enabled' => Setting::get('azure_mail_enabled', false), + 'tenant_id' => Setting::get('azure_mail_tenant_id', ''), + 'client_id' => Setting::get('azure_mail_client_id', ''), + 'client_secret' => Setting::get('azure_mail_client_secret', ''), + 'from_address' => Setting::get('azure_mail_from_address', ''), + ]; + + return view('settings.integrations', compact('whatsappSettings', 'azureSettings')); + } + ``` + +- [ ] **Step 3: Convert `updateWhatsapp()` to return JsonResponse instead of RedirectResponse** + + Replace the entire `updateWhatsapp()` method with: + ```php + public function updateWhatsapp(Request $request): JsonResponse + { + $request->validate([ + 'instance_id' => ['required', 'string', 'max:100'], + 'token' => ['required', 'string', 'max:255'], + 'webhook_secret' => ['nullable', 'string', 'max:255'], + 'webhook_path' => ['required', 'string', 'max:100'], + ]); + + Setting::set('ultramsg_enabled', $request->input('enabled') === '1' ? '1' : '0'); + Setting::set('ultramsg_instance_id', $request->instance_id); + Setting::set('ultramsg_token', $request->token); + Setting::set('ultramsg_webhook_secret', $request->webhook_secret ?? ''); + Setting::set('ultramsg_webhook_path', $request->webhook_path); + + return response()->json(['success' => true]); + } + ``` + + Note: `$request->input('enabled') === '1'` because the AJAX payload sends the string `'1'` or `'0'` from the hidden input — not a checkbox boolean. + +- [ ] **Step 4: Remove the `RedirectResponse` use statement if it is now unused** + + Check if `RedirectResponse` appears anywhere else in the file. If only `updateWhatsapp()` used it, remove: + ```php + use Illuminate\Http\RedirectResponse; + ``` + +- [ ] **Step 5: Add `updateAzureMail()` after the closing brace of `updateWhatsapp()`** + + ```php + public function updateAzureMail(Request $request): JsonResponse + { + $request->validate([ + 'tenant_id' => ['required', 'string', 'max:100'], + 'client_id' => ['required', 'string', 'max:100'], + 'client_secret' => ['required', 'string', 'max:500'], + 'from_address' => ['required', 'email', 'max:255'], + ]); + + Setting::set('azure_mail_enabled', $request->input('enabled') === '1' ? '1' : '0'); + Setting::set('azure_mail_tenant_id', $request->tenant_id); + Setting::set('azure_mail_client_id', $request->client_id); + Setting::set('azure_mail_client_secret', $request->client_secret); + Setting::set('azure_mail_from_address', $request->from_address); + + return response()->json(['success' => true]); + } + ``` + +- [ ] **Step 6: Add `testAzureMailConnection()` after `updateAzureMail()`** + + ```php + public function testAzureMailConnection(): JsonResponse + { + try { + $config = [ + 'tenant_id' => Setting::get('azure_mail_tenant_id', ''), + 'client_id' => Setting::get('azure_mail_client_id', ''), + 'client_secret' => Setting::get('azure_mail_client_secret', ''), + ]; + $tokenManager = new TokenManager($config); + $tokenManager->getToken(); + return response()->json(['success' => true]); + } catch (\Exception $e) { + return response()->json(['success' => false, 'message' => $e->getMessage()]); + } + } + ``` + +- [ ] **Step 7: Add `sendTestEmail()` after `testAzureMailConnection()`** + + ```php + public function sendTestEmail(Request $request): JsonResponse + { + $request->validate([ + 'to' => ['required', 'email', 'max:255'], + 'subject' => ['required', 'string', 'max:255'], + ]); + + try { + $to = $request->to; + $subject = $request->subject; + Mail::mailer('azure')->raw( + 'This is a test email from SteelERP.', + function ($message) use ($to, $subject) { + $message->to($to)->subject($subject); + } + ); + return response()->json(['success' => true]); + } catch (\Exception $e) { + return response()->json(['success' => false, 'message' => $e->getMessage()]); + } + } + ``` + +- [ ] **Step 8: Verify no PHP syntax errors** + + Run: + ```bash + php artisan route:list --name=settings.integrations + ``` + Expected: command succeeds with no parse errors (Laravel would fail to boot otherwise). + +- [ ] **Step 9: Commit** + + ```bash + git add app/Http/Controllers/SettingsController.php + git commit -m "feat: update SettingsController for Azure Mail tab — AJAX whatsapp save, 3 new azure methods" + ``` + +--- + +### Task 3: Rewrite integrations.blade.php + +**Files:** +- Modify: `resources/views/settings/integrations.blade.php` (full rewrite) + +This task replaces the entire file. Read the current file first to understand what the WhatsApp SVG icon markup and toggle look like, then replace with the complete tabbed layout below. + +Key design decisions: +- Outer wrapper uses `x-data="{ tab: 'whatsapp' }"` for Alpine.js tab state +- Pill tabs use `:style` bindings for active/inactive appearance (inline styles per Tailwind JIT rule) +- Email panel uses `x-show="tab==='email'" style="display:none;"` — the `style="display:none"` prevents flash before Alpine initialises +- WhatsApp panel uses `x-show="tab==='whatsapp'"` (no initial display:none because it's the default visible tab) +- Both toggles use a hidden `` with value `'1'`/`'0'` that JavaScript reads; the visual track/thumb are manipulated directly +- Password show/hide fields use Alpine `x-data` scoped to the field wrapper +- All saves use the `api()` helper (returns a Promise that rejects on non-2xx) +- Test Connection calls use raw `fetch()` (not `api()`) because the endpoint always returns HTTP 200 with a `success` flag — not a 422 on failure + +- [ ] **Step 1: Replace the entire content of `resources/views/settings/integrations.blade.php` with the following** + + ```blade + @extends('layouts.app') + + @section('title', 'Settings — Integrations') + + @section('content') +
+

Settings — Integrations

+

Configure third-party service integrations.

+
+ +
+ + {{-- Pill tabs --}} +
+ + +
+ + {{-- ===== WhatsApp tab ===== --}} +
+ +
+
+ + + +

WhatsApp (UltraMSG)

+
+ +
+ + {{-- Enable toggle --}} +
+
+

Enable WhatsApp Notifications

+

When disabled, no messages will be sent.

+
+
+ +
+
+
+
+
+ +
+ + {{-- Instance ID --}} +
+ + +
+ + {{-- API Token --}} +
+ +
+ + +
+
+ + {{-- Webhook Secret --}} +
+ +
+ + +
+
+ + {{-- Webhook Path --}} +
+ +
+ {{ url('/') }}/ + +
+

+ Paste this full URL in your UltraMSG dashboard: {{ url('/') }}/{{ $whatsappSettings['webhook_path'] }} +

+
+ + {{-- Actions --}} +
+
+ + +
+ +
+ +
+
+ + {{-- Send Test Message accordion --}} +
+ + +
+ +
{{-- end WhatsApp tab --}} + + {{-- ===== Email tab ===== --}} +
+ +
+
+
✉️
+
+
Microsoft 365 (Azure Mail)
+
Send emails via Microsoft Graph API using Azure AD
+
+
+ +
+ + {{-- Enable toggle --}} +
+
+

Enable Email Notifications

+

When disabled, no emails will be sent.

+
+
+ +
+
+
+
+
+ +
+ + {{-- Tenant ID --}} +
+ + +
+ + {{-- Client ID --}} +
+ + +
+ + {{-- Client Secret --}} +
+ +
+ + +
+
+ + {{-- From Address --}} +
+ + +

Must be a mailbox in your Microsoft 365 tenant.

+
+ + {{-- Actions --}} +
+
+ + +
+ +
+ +
+
+ + {{-- Send Test Email accordion --}} +
+ + +
+ +
{{-- end Email tab --}} + +
+ + + @endsection + ``` + +- [ ] **Step 2: Verify page renders without errors** + + With `php artisan serve` running, visit `http://localhost:8000/settings/integrations` as an Admin user. + + Expected: + - Page loads with pill tabs "💬 WhatsApp" (active, dark) and "✉️ Email" (inactive, white/bordered) + - WhatsApp card visible with all existing fields + - Clicking "✉️ Email" tab switches to the Email panel + - Email panel shows Tenant ID, Client ID, Client Secret, From Address fields + +- [ ] **Step 3: Test WhatsApp AJAX save** + + Fill in the WhatsApp fields and click "Save Settings". + Expected: green toast "WhatsApp settings saved." — no page reload. + +- [ ] **Step 4: Test Email tab save (validation)** + + Switch to Email tab, click "Save Settings" without filling in Tenant ID. + Expected: red toast showing the validation error message. + +- [ ] **Step 5: Commit** + + ```bash + git add resources/views/settings/integrations.blade.php + git commit -m "feat: rewrite integrations view with WhatsApp/Email pill tabs and AJAX saves" + ``` + +--- + +## Verification Checklist + +After all three tasks are complete: + +- [ ] `php artisan route:list --name=settings.integrations` shows 7 routes +- [ ] Integrations page loads at `/settings/integrations` with pill tabs +- [ ] Tab switching works (WhatsApp ↔ Email) without page reload +- [ ] WhatsApp save uses AJAX (no page reload, toast appears) +- [ ] Email save uses AJAX (no page reload, toast appears) +- [ ] Email validation error (empty Tenant ID) shows toast with message +- [ ] Test Connection (WhatsApp) still works +- [ ] "Send Test Message" accordion still opens/closes +- [ ] Email tab fields pre-populate from DB if previously saved diff --git a/docs/superpowers/specs/2026-05-26-integrations-tabs-design.md b/docs/superpowers/specs/2026-05-26-integrations-tabs-design.md new file mode 100644 index 0000000..f3a4994 --- /dev/null +++ b/docs/superpowers/specs/2026-05-26-integrations-tabs-design.md @@ -0,0 +1,149 @@ +# Integrations Page — Tabbed UI Design Spec + +**Date:** 2026-05-26 +**Status:** Approved + +--- + +## Overview + +Redesign the Settings → Integrations page to use pill-style tabs — **WhatsApp** and **Email** — so both integrations are accessible from a single page. The Email tab exposes the Microsoft 365 Azure AD credentials (stored via the `Setting` model) and provides test connection + send test email actions via AJAX. + +--- + +## Goals + +- Add pill tabs (WhatsApp | Email) at the top of the integrations page +- Keep the WhatsApp tab visually identical to the current design +- Add a new Email tab for Microsoft 365 / Azure AD Mail configuration +- Convert both tabs' save operations to AJAX (rule #11 — no page reloads on settings pages) +- Use the existing design system: `.card`, `.form-input`, `.form-label`, `.btn-primary`, `showToast()` +- All inline styles used for one-off values (Tailwind JIT rule) + +--- + +## UI Design + +### Tab bar + +Pill-style tabs rendered above the card. Alpine.js `x-data="{ tab: 'whatsapp' }"` switches visibility. + +- **Active tab:** `background:#1e293b; color:#fff; border-radius:999px; padding:7px 18px;` +- **Inactive tab:** `background:#fff; color:#374151; border:1px solid #d1d5db; border-radius:999px; padding:7px 18px;` + +### WhatsApp tab (unchanged fields, AJAX save) + +Same fields as today: Enable toggle, Instance ID, API Token (masked), Webhook Secret (optional, masked), Webhook Path. Save button → AJAX POST. Test Connection and Send Test Message accordion remain unchanged. + +### Email tab (new) + +Card header: envelope icon + "Microsoft 365 (Azure Mail)" title + subtitle. + +Fields: +| Field | Input type | Settings key | Validation | +|-------|-----------|-------------|------------| +| Enable Email Notifications | Toggle | `azure_mail_enabled` | boolean | +| Tenant ID | text | `azure_mail_tenant_id` | required, max:100 | +| Client ID | text | `azure_mail_client_id` | required, max:100 | +| Client Secret | password (show/hide) | `azure_mail_client_secret` | required, max:500 | +| From Address | text | `azure_mail_from_address` | required, email, max:255 | + +Actions: +- **Test Connection** link → AJAX POST → calls `TokenManager::getToken()` to verify Azure AD responds +- **Save Settings** button → AJAX POST → stores all 5 keys via `Setting::set()` + +Send Test Email accordion (same expand/collapse pattern as WhatsApp): +- **To** field (email address) +- **Subject** field (pre-filled: "Test Email from SteelERP") +- **Send Email** button → AJAX POST → `Mail::mailer('azure')->raw(...)` + +--- + +## Architecture + +### Files changed + +| File | Change | +|------|--------| +| `resources/views/settings/integrations.blade.php` | Full rewrite — pill tabs, both tab panels, AJAX JS | +| `app/Http/Controllers/SettingsController.php` | Add 3 new methods; update `integrations()` and `updateWhatsapp()` | +| `routes/web.php` | Add 3 new POST routes for azure-mail | + +### Controller changes + +**`integrations(): View`** — pass both `$whatsappSettings` and `$azureSettings` to view. + +**`updateWhatsapp(Request $request): JsonResponse`** — same validation, save logic unchanged, but return `response()->json(['success' => true])` instead of redirect. + +**`updateAzureMail(Request $request): JsonResponse`** +``` +Validates: tenant_id (required, max:100), client_id (required, max:100), + client_secret (required, max:500), from_address (required, email, max:255) +Saves: azure_mail_enabled, azure_mail_tenant_id, azure_mail_client_id, + azure_mail_client_secret, azure_mail_from_address +Returns: JSON {success: true} +``` + +**`testAzureMailConnection(): JsonResponse`** +``` +Reads azure_mail_* settings from DB +Instantiates TokenManager with those settings +Calls getToken() — success means Azure AD is reachable and credentials are valid +Returns: JSON {success: true} or {success: false, message: $e->getMessage()} +``` + +**`sendTestEmail(Request $request): JsonResponse`** +``` +Validates: to (required, email, max:255), subject (required, max:255) +Uses Mail::mailer('azure')->raw('This is a test email from SteelERP.', fn($m) => $m->to($to)->subject($subject)) +Returns: JSON {success: true} or {success: false, message: ...} +``` + +### New routes + +```php +Route::post('settings/integrations/azure-mail', [SettingsController::class, 'updateAzureMail'])->name('settings.integrations.azure-mail'); +Route::post('settings/integrations/test-azure-mail', [SettingsController::class, 'testAzureMailConnection'])->name('settings.integrations.test-azure-mail'); +Route::post('settings/integrations/send-test-email', [SettingsController::class, 'sendTestEmail'])->name('settings.integrations.send-test-email'); +``` + +--- + +## AJAX Pattern + +Both tabs use the global `api()` helper pattern from CLAUDE.md rule #11: + +```javascript +var CSRF = document.querySelector('meta[name="csrf-token"]').content; +function api(url, data) { + return fetch(url, { + method: 'POST', + headers: { 'X-CSRF-TOKEN': CSRF, 'Accept': 'application/json', 'Content-Type': 'application/json' } + body: JSON.stringify(data) + }).then(function(r) { + return r.json().then(function(body) { + if (!r.ok) return Promise.reject(body); + return body; + }); + }); +} +``` + +On success → `showToast('Settings saved.', 'success')` +On error → `showToast(err.message || 'Error', 'error')` + +--- + +## Error Handling + +- Laravel validation failure (422) → `Accept: application/json` header ensures JSON error response → `showToast()` with first validation error message +- Azure AD token fetch failure (`AuthenticationException`) → caught in `testAzureMailConnection`, message returned as JSON +- Mail send failure → caught, message returned as JSON + +--- + +## Non-Goals + +- No URL changes — integrations page stays at `/settings/integrations` +- No tab state persisted in URL +- No changes to WhatsApp fields or business logic