5.7 KiB
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
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:
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/jsonheader ensures JSON error response →showToast()with first validation error message - Azure AD token fetch failure (
AuthenticationException) → caught intestAzureMailConnection, 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