Add per-customer SMS and Bird Integrations for event messaging.
Deploy Ladill Events / deploy (push) Successful in 28s
Deploy Ladill Events / deploy (push) Successful in 28s
Attendee and speaker comms use encrypted tenant relay credentials and skip platform-key fallback and Bird wallet double-debit. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -54,12 +54,12 @@
|
||||
</span>
|
||||
<div>
|
||||
<p class="text-sm font-semibold text-slate-900">Message attendees</p>
|
||||
<p class="text-xs text-slate-500">Send programme links, virtual join links, or both. SMS and email are billed from your wallet.</p>
|
||||
<p class="text-xs text-slate-500">Send programme links, virtual join links, or both. Uses your connected Ladill SMS and Bird keys. <a href="{{ route('account.integrations') }}" class="font-medium text-indigo-600 hover:text-indigo-800">Manage integrations</a></p>
|
||||
<template x-if="preview">
|
||||
<p class="mt-2 text-xs text-slate-600">
|
||||
<span x-text="preview.recipients"></span> recipients ·
|
||||
est. GHS <span x-text="preview.estimated_ghs.toFixed(2)"></span>
|
||||
<span x-show="!preview.affordable" class="font-semibold text-red-600"> — insufficient balance</span>
|
||||
<span x-show="preview.integrations_error" class="font-semibold text-red-600" x-text="' — ' + preview.integrations_error"></span>
|
||||
</p>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@php
|
||||
$mobileFullScreenPage = request()->routeIs('account.settings')
|
||||
|| request()->routeIs('account.integrations')
|
||||
|| request()->routeIs('events.create')
|
||||
|| request()->routeIs('programmes.create')
|
||||
|| request()->routeIs('events.show')
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
<x-user-layout>
|
||||
<x-slot name="title">Integrations</x-slot>
|
||||
|
||||
<div class="mx-auto max-w-2xl">
|
||||
<h1 class="text-xl font-semibold tracking-tight text-slate-900">Messaging integrations</h1>
|
||||
<p class="mt-0.5 text-sm text-slate-500">
|
||||
Connect your own
|
||||
<a href="https://sms.ladill.com" target="_blank" rel="noopener" class="font-medium text-indigo-600 hover:text-indigo-800">Ladill SMS</a>
|
||||
and
|
||||
<a href="https://bird.ladill.com" target="_blank" rel="noopener" class="font-medium text-indigo-600 hover:text-indigo-800">Ladill Bird</a>
|
||||
API keys so attendee messages bill your wallets and send under your brand.
|
||||
</p>
|
||||
|
||||
@if (session('success'))
|
||||
<div class="mt-4 rounded-xl border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm text-emerald-800">{{ session('success') }}</div>
|
||||
@endif
|
||||
@if (session('error'))
|
||||
<div class="mt-4 rounded-xl border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-800">{{ session('error') }}</div>
|
||||
@endif
|
||||
|
||||
<div class="mt-6 space-y-4">
|
||||
<div class="rounded-2xl border border-slate-200 bg-white p-5">
|
||||
<h2 class="text-sm font-semibold text-slate-900">Ladill SMS</h2>
|
||||
<p class="mt-1 text-xs text-slate-500">Paste an SMS API key (lsk_sms_live_…) and an approved sender ID from sms.ladill.com.</p>
|
||||
|
||||
@if ($credential->hasValidSms())
|
||||
<div class="mt-4 rounded-xl border border-emerald-200 bg-emerald-50 px-3 py-2 text-sm text-emerald-800">
|
||||
Connected · key {{ $credential->sms_api_key_prefix }}… · sender <strong>{{ $credential->sms_sender_id }}</strong>
|
||||
@if ($credential->sms_validated_at)
|
||||
· validated {{ $credential->sms_validated_at->diffForHumans() }}
|
||||
@endif
|
||||
</div>
|
||||
@elseif ($credential->sms_status === \App\Models\MessagingCredential::STATUS_INVALID)
|
||||
<div class="mt-4 rounded-xl border border-rose-200 bg-rose-50 px-3 py-2 text-sm text-rose-800">
|
||||
{{ $credential->sms_last_error ?: 'SMS credentials are invalid.' }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form method="POST" action="{{ route('account.integrations.sms.save') }}" class="mt-4 space-y-4">
|
||||
@csrf
|
||||
<div>
|
||||
<label class="block text-[11px] font-medium text-slate-500">SMS API key</label>
|
||||
<input type="password" name="sms_api_key" autocomplete="off" required
|
||||
placeholder="lsk_sms_live_…"
|
||||
class="mt-1 w-full rounded-lg border border-slate-200 bg-slate-50 px-3 py-2 font-mono text-sm focus:border-indigo-300 focus:bg-white focus:outline-none focus:ring-2 focus:ring-indigo-100">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[11px] font-medium text-slate-500">Sender ID</label>
|
||||
<input type="text" name="sms_sender_id" maxlength="11" required
|
||||
value="{{ old('sms_sender_id', $credential->sms_sender_id) }}"
|
||||
placeholder="Approved sender ID"
|
||||
class="mt-1 w-full rounded-lg border border-slate-200 bg-slate-50 px-3 py-2 text-sm focus:border-indigo-300 focus:bg-white focus:outline-none focus:ring-2 focus:ring-indigo-100">
|
||||
<p class="mt-1 text-[11px] text-slate-400">Must match an approved sender ID on your SMS service (or the platform default).</p>
|
||||
</div>
|
||||
<button type="submit" class="btn-primary">Validate & save</button>
|
||||
</form>
|
||||
|
||||
@if ($credential->sms_api_key_encrypted || $credential->hasValidSms())
|
||||
<form method="POST" action="{{ route('account.integrations.sms.disconnect') }}" class="mt-3" onsubmit="return confirm('Disconnect Ladill SMS?')">
|
||||
@csrf
|
||||
<button type="submit" class="text-sm text-rose-700 hover:underline">Disconnect SMS</button>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="rounded-2xl border border-slate-200 bg-white p-5">
|
||||
<h2 class="text-sm font-semibold text-slate-900">Ladill Bird</h2>
|
||||
<p class="mt-1 text-xs text-slate-500">Paste a Bird API key (lsk_live_…) plus a from address on a verified domain from bird.ladill.com.</p>
|
||||
|
||||
@if ($credential->hasValidBird())
|
||||
<div class="mt-4 rounded-xl border border-emerald-200 bg-emerald-50 px-3 py-2 text-sm text-emerald-800">
|
||||
Connected · key {{ $credential->bird_api_key_prefix }}… · from
|
||||
<strong>{{ $credential->bird_from_name ? $credential->bird_from_name.' <'.$credential->bird_from_email.'>' : $credential->bird_from_email }}</strong>
|
||||
@if ($credential->bird_validated_at)
|
||||
· validated {{ $credential->bird_validated_at->diffForHumans() }}
|
||||
@endif
|
||||
</div>
|
||||
@elseif ($credential->bird_status === \App\Models\MessagingCredential::STATUS_INVALID)
|
||||
<div class="mt-4 rounded-xl border border-rose-200 bg-rose-50 px-3 py-2 text-sm text-rose-800">
|
||||
{{ $credential->bird_last_error ?: 'Bird credentials are invalid.' }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form method="POST" action="{{ route('account.integrations.bird.save') }}" class="mt-4 space-y-4">
|
||||
@csrf
|
||||
<div>
|
||||
<label class="block text-[11px] font-medium text-slate-500">Bird API key</label>
|
||||
<input type="password" name="bird_api_key" autocomplete="off" required
|
||||
placeholder="lsk_live_…"
|
||||
class="mt-1 w-full rounded-lg border border-slate-200 bg-slate-50 px-3 py-2 font-mono text-sm focus:border-indigo-300 focus:bg-white focus:outline-none focus:ring-2 focus:ring-indigo-100">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[11px] font-medium text-slate-500">From email</label>
|
||||
<input type="email" name="bird_from_email" required
|
||||
value="{{ old('bird_from_email', $credential->bird_from_email) }}"
|
||||
placeholder="events@yourdomain.com"
|
||||
class="mt-1 w-full rounded-lg border border-slate-200 bg-slate-50 px-3 py-2 text-sm focus:border-indigo-300 focus:bg-white focus:outline-none focus:ring-2 focus:ring-indigo-100">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[11px] font-medium text-slate-500">From name</label>
|
||||
<input type="text" name="bird_from_name" maxlength="100"
|
||||
value="{{ old('bird_from_name', $credential->bird_from_name ?: $account->name) }}"
|
||||
placeholder="{{ $account->name }}"
|
||||
class="mt-1 w-full rounded-lg border border-slate-200 bg-slate-50 px-3 py-2 text-sm focus:border-indigo-300 focus:bg-white focus:outline-none focus:ring-2 focus:ring-indigo-100">
|
||||
</div>
|
||||
<button type="submit" class="btn-primary">Validate & save</button>
|
||||
</form>
|
||||
|
||||
@if ($credential->bird_api_key_encrypted || $credential->hasValidBird())
|
||||
<form method="POST" action="{{ route('account.integrations.bird.disconnect') }}" class="mt-3" onsubmit="return confirm('Disconnect Ladill Bird?')">
|
||||
@csrf
|
||||
<button type="submit" class="text-sm text-rose-700 hover:underline">Disconnect Bird</button>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="mt-6 text-xs text-slate-400">
|
||||
<a href="{{ route('account.settings') }}" class="font-medium text-indigo-600 hover:text-indigo-800">← Back to settings</a>
|
||||
</p>
|
||||
</div>
|
||||
</x-user-layout>
|
||||
@@ -252,6 +252,12 @@
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="mt-4 rounded-2xl border border-slate-200 bg-white p-5">
|
||||
<h2 class="text-sm font-semibold text-slate-900">Messaging integrations</h2>
|
||||
<p class="mt-1 text-xs text-slate-500">Connect your Ladill SMS and Bird API keys for attendee and speaker messaging.</p>
|
||||
<a href="{{ route('account.integrations') }}" class="mt-3 inline-block text-sm font-semibold text-indigo-600 hover:text-indigo-800">Open messaging integrations →</a>
|
||||
</div>
|
||||
|
||||
<p class="mt-6 text-xs text-slate-400">
|
||||
Profile, password, and security are managed on
|
||||
<a href="{{ ladill_account_url('account-settings') }}" class="font-medium text-indigo-600 hover:text-indigo-800">account.ladill.com</a>.
|
||||
|
||||
Reference in New Issue
Block a user