Add virtual event sessions with Meet sync and platform-billed messaging.
Deploy Ladill Events / deploy (push) Successful in 41s

Events can define hybrid/virtual sessions synced to Meet rooms; SMS and email use wallet-billed platform APIs instead of Termii and Laravel mail.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-01 22:08:09 +00:00
co-authored by Cursor
parent 5a8a462102
commit 06fedcfc55
18 changed files with 735 additions and 30 deletions
@@ -711,6 +711,26 @@
<p class="mt-6 text-center text-sm leading-relaxed text-slate-600">{{ $content['description'] }}</p>
@endif
@php
$evFormat = $content['format'] ?? 'in_person';
$evVirtualSessions = collect($content['virtual_sessions'] ?? [])->filter(fn ($s) => is_array($s) && ! empty($s['join_url']));
@endphp
@if(in_array($evFormat, ['virtual', 'hybrid'], true) && $evVirtualSessions->isNotEmpty())
<div class="mt-6 space-y-3">
<h2 class="text-center text-sm font-bold text-slate-900">Virtual sessions</h2>
@foreach($evVirtualSessions as $session)
<a href="{{ $session['join_url'] }}" target="_blank" rel="noopener"
class="block rounded-2xl border border-indigo-100 bg-white px-4 py-3 shadow-sm transition hover:border-indigo-300">
<p class="font-semibold text-slate-900">{{ $session['title'] ?? 'Join session' }}</p>
@if(!empty($session['scheduled_at']))
<p class="mt-1 text-xs text-slate-500">{{ \App\Support\Qr\QrDateFormatter::forDisplay($session['scheduled_at']) }}</p>
@endif
<p class="mt-2 text-xs font-semibold text-indigo-600">Join virtual session </p>
</a>
@endforeach
</div>
@endif
@if(! $evOpen)
<div class="mt-6 rounded-2xl border border-amber-200 bg-amber-50 px-5 py-4 text-center text-sm font-medium text-amber-700">{{ $evMode === 'contributions' ? 'Contributions are currently closed.' : 'Registration is currently closed.' }}</div>
@else
@@ -417,6 +417,51 @@
</div>
<input type="text" name="location" value="{{ old('location') }}" placeholder="Venue / location"
class="rounded-xl border border-slate-200 px-3.5 py-2.5 text-sm placeholder:text-slate-400 focus:border-indigo-400 focus:outline-none focus:ring-1 focus:ring-indigo-400/30">
{{-- Event format + virtual sessions --}}
<div x-data="{
format: @js(old('format', $ed['format'] ?? 'in_person')),
sessions: {{ Illuminate\Support\Js::from(old('virtual_sessions', $ed['virtual_sessions'] ?? [])) }},
addSession() { this.sessions.push({ title: '', meet_room_type: 'town_hall', scheduled_at: '', duration_minutes: 60 }); },
removeSession(i) { this.sessions.splice(i, 1); }
}">
<input type="hidden" name="format" :value="format">
<p class="mb-2 text-xs font-semibold text-slate-600">Event format</p>
<div class="grid grid-cols-3 gap-2">
@foreach (['in_person' => 'In person', 'virtual' => 'Virtual', 'hybrid' => 'Hybrid'] as $value => $label)
<button type="button" @click="format = '{{ $value }}'"
:class="format === '{{ $value }}' ? 'border-indigo-500 bg-indigo-50 text-indigo-700' : 'border-slate-200 bg-white text-slate-600'"
class="rounded-xl border px-3 py-2 text-xs font-medium">{{ $label }}</button>
@endforeach
</div>
<div x-show="format === 'virtual' || format === 'hybrid'" x-cloak class="mt-4 space-y-3">
<div class="flex items-center justify-between">
<p class="text-xs font-semibold text-slate-600">Virtual sessions</p>
<button type="button" @click="addSession()" class="text-xs font-semibold text-indigo-600">+ Add session</button>
</div>
<template x-for="(session, index) in sessions" :key="index">
<div class="rounded-xl border border-slate-200 p-3 space-y-2">
<input type="hidden" :name="'virtual_sessions[' + index + '][id]'" :value="session.id || ''">
<input type="text" :name="'virtual_sessions[' + index + '][title]'" x-model="session.title" placeholder="Session title"
class="w-full rounded-lg border border-slate-200 px-3 py-2 text-sm">
<div class="grid gap-2 sm:grid-cols-3">
<select :name="'virtual_sessions[' + index + '][meet_room_type]'" x-model="session.meet_room_type"
class="rounded-lg border border-slate-200 px-3 py-2 text-sm">
<option value="town_hall">Conference</option>
<option value="webinar">Webinar</option>
</select>
<input type="date" :name="'virtual_sessions[' + index + '][scheduled_at]'" x-model="session.scheduled_at"
class="rounded-lg border border-slate-200 px-3 py-2 text-sm">
<input type="number" min="15" max="480" :name="'virtual_sessions[' + index + '][duration_minutes]'" x-model="session.duration_minutes"
class="rounded-lg border border-slate-200 px-3 py-2 text-sm" placeholder="Minutes">
</div>
<button type="button" @click="removeSession(index)" class="text-xs text-red-600">Remove</button>
</div>
</template>
</div>
</div>
<div class="grid gap-3 sm:grid-cols-2">
<input type="text" name="organizer" value="{{ old('organizer', $ed['organizer'] ?? '') }}" placeholder="Organizer name"
class="rounded-xl border border-slate-200 px-3.5 py-2.5 text-sm placeholder:text-slate-400 focus:border-indigo-400 focus:outline-none focus:ring-1 focus:ring-indigo-400/30">
@@ -426,6 +426,52 @@
</div>
<input type="text" name="location" value="{{ $c['location'] ?? '' }}" placeholder="Venue / location"
class="rounded-xl border border-slate-200 px-3.5 py-2.5 text-sm placeholder:text-slate-400 focus:border-indigo-400 focus:outline-none focus:ring-1 focus:ring-indigo-400/30">
<div x-data="{
format: @js(old('format', $c['format'] ?? 'in_person')),
sessions: {{ Illuminate\Support\Js::from(old('virtual_sessions', $c['virtual_sessions'] ?? [])) }},
addSession() { this.sessions.push({ title: '', meet_room_type: 'town_hall', scheduled_at: '', duration_minutes: 60 }); },
removeSession(i) { this.sessions.splice(i, 1); }
}">
<input type="hidden" name="format" :value="format">
<p class="mb-2 text-xs font-semibold text-slate-600">Event format</p>
<div class="grid grid-cols-3 gap-2">
@foreach (['in_person' => 'In person', 'virtual' => 'Virtual', 'hybrid' => 'Hybrid'] as $value => $label)
<button type="button" @click="format = '{{ $value }}'"
:class="format === '{{ $value }}' ? 'border-indigo-500 bg-indigo-50 text-indigo-700' : 'border-slate-200 bg-white text-slate-600'"
class="rounded-xl border px-3 py-2 text-xs font-medium">{{ $label }}</button>
@endforeach
</div>
<div x-show="format === 'virtual' || format === 'hybrid'" x-cloak class="mt-4 space-y-3">
<div class="flex items-center justify-between">
<p class="text-xs font-semibold text-slate-600">Virtual sessions</p>
<button type="button" @click="addSession()" class="text-xs font-semibold text-indigo-600">+ Add session</button>
</div>
<template x-for="(session, index) in sessions" :key="index">
<div class="rounded-xl border border-slate-200 p-3 space-y-2">
<input type="hidden" :name="'virtual_sessions[' + index + '][id]'" :value="session.id || ''">
<input type="hidden" :name="'virtual_sessions[' + index + '][meet_room_uuid]'" :value="session.meet_room_uuid || ''">
<input type="hidden" :name="'virtual_sessions[' + index + '][join_url]'" :value="session.join_url || ''">
<input type="text" :name="'virtual_sessions[' + index + '][title]'" x-model="session.title" placeholder="Session title"
class="w-full rounded-lg border border-slate-200 px-3 py-2 text-sm">
<div class="grid gap-2 sm:grid-cols-3">
<select :name="'virtual_sessions[' + index + '][meet_room_type]'" x-model="session.meet_room_type"
class="rounded-lg border border-slate-200 px-3 py-2 text-sm">
<option value="town_hall">Conference</option>
<option value="webinar">Webinar</option>
</select>
<input type="date" :name="'virtual_sessions[' + index + '][scheduled_at]'" x-model="session.scheduled_at"
class="rounded-lg border border-slate-200 px-3 py-2 text-sm">
<input type="number" min="15" max="480" :name="'virtual_sessions[' + index + '][duration_minutes]'" x-model="session.duration_minutes"
class="rounded-lg border border-slate-200 px-3 py-2 text-sm">
</div>
<p x-show="session.join_url" class="text-xs text-emerald-700">Join link ready</p>
<button type="button" @click="removeSession(index)" class="text-xs text-red-600">Remove</button>
</div>
</template>
</div>
</div>
<div class="grid gap-3 sm:grid-cols-2">
<input type="text" name="organizer" value="{{ $c['organizer'] ?? '' }}" placeholder="Organizer name"
class="rounded-xl border border-slate-200 px-3.5 py-2.5 text-sm placeholder:text-slate-400 focus:border-indigo-400 focus:outline-none focus:ring-1 focus:ring-indigo-400/30">