# 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')
Configure third-party service integrations.
Enable WhatsApp Notifications
When disabled, no messages will be sent.
Paste this full URL in your UltraMSG dashboard: {{ url('/') }}/{{ $whatsappSettings['webhook_path'] }}