@extends('layouts.admin') @section('title', 'Campaign — ' . $campaign->name) @push('styles') {{-- Flatpickr (date/time picker) — CDN'd to avoid touching the Vite build. --}} @endpush @section('admin') @php $statusColors = [ 'draft' => ['text'=>'text-gray-300', 'bg'=>'bg-gray-800', 'border'=>'border-gray-700'], 'scheduled' => ['text'=>'text-blue-300', 'bg'=>'bg-blue-900/30', 'border'=>'border-blue-700/50'], 'queued' => ['text'=>'text-purple-300','bg'=>'bg-purple-900/30','border'=>'border-purple-700/50'], 'sending' => ['text'=>'text-emerald-300','bg'=>'bg-emerald-900/30','border'=>'border-emerald-700/50'], 'paused' => ['text'=>'text-amber-300', 'bg'=>'bg-amber-900/20', 'border'=>'border-amber-700/40'], 'sent' => ['text'=>'text-emerald-300','bg'=>'bg-emerald-900/20','border'=>'border-emerald-700/40'], 'canceled' => ['text'=>'text-gray-500', 'bg'=>'bg-gray-900', 'border'=>'border-gray-700'], 'failed' => ['text'=>'text-red-300', 'bg'=>'bg-red-900/20', 'border'=>'border-red-700/40'], ]; $sc = $statusColors[$campaign->status] ?? $statusColors['draft']; $errorIssues = array_values(array_filter($issues, fn($i) => $i['level'] === 'error')); $warnings = array_values(array_filter($issues, fn($i) => $i['level'] === 'warning')); $infos = array_values(array_filter($issues, fn($i) => $i['level'] === 'info')); $errCount = count($errorIssues); $warnCount = count($warnings); $totalChecks = max(1, count($issues)); $passedChecks = count($infos); $score = empty($issues) ? 100 : max(0, 100 - $errCount * 25 - $warnCount * 10); $scoreColor = $score >= 80 ? '#4ade80' : ($score >= 50 ? '#fcd34d' : '#f87171'); $sentPct = $campaign->total_recipients > 0 ? min(100, ($campaign->sent_count / max(1, $campaign->total_recipients)) * 100) : 0; @endphp
@include('admin._nav') {{-- Breadcrumb --}} {{-- Header --}}
@if($campaign->status === 'sending')@endif {{ ucfirst($campaign->status) }} @if($campaign->approval_status && $campaign->approval_status !== 'none') @php $apv = [ 'pending' => ['bg'=>'bg-amber-900/30','text'=>'text-amber-300','border'=>'border-amber-700/50','icon'=>'fa-hourglass-half','label'=>'Pending Approval'], 'approved' => ['bg'=>'bg-emerald-900/30','text'=>'text-emerald-300','border'=>'border-emerald-700/50','icon'=>'fa-circle-check','label'=>'Approved'], 'rejected' => ['bg'=>'bg-red-900/30','text'=>'text-red-300','border'=>'border-red-700/50','icon'=>'fa-circle-xmark','label'=>'Rejected'], ][$campaign->approval_status] ?? null; @endphp @if($apv) {{ $apv['label'] }} @endif @endif @if($campaign->subject_b && $campaign->ab_split_percent) A/B {{ $campaign->ab_split_percent }}% @endif @if($campaign->is_quick_send)Quick Send@endif

{{ $campaign->name }}

{{ $campaign->subject }}

@if($campaign->preheader)

{{ $campaign->preheader }}

@endif
@if($campaign->isEditable()) Edit @endif Report Preview
@if(session('success'))
{{ session('success') }}
@endif @if(session('error'))
{{ session('error') }}
@endif {{-- Key metrics --}}
Audience
{{ number_format($audience) }}
active subscribers
Sent
{{ number_format($campaign->sent_count) }}
of {{ number_format($campaign->total_recipients) }} queued
Failed
{{ number_format($campaign->failed_count) }}
delivery errors
Progress
{{ number_format($sentPct, 0) }}%
{{-- Preflight checklist --}}

Pre-send Checklist

@if(empty($issues)) All clear @elseif($errCount > 0) {{ $errCount }} error{{ $errCount > 1 ? 's' : '' }} @else {{ $warnCount }} warning{{ $warnCount > 1 ? 's' : '' }} @endif
@if(empty($issues))

All checks passed

This campaign is ready to send.

@else
@foreach($errorIssues as $i)

{{ $i['message'] }}

@if(!empty($i['detail']))

{{ $i['detail'] }}

@endif
@endforeach @foreach($warnings as $i)

{{ $i['message'] }}

@if(!empty($i['detail']))

{{ $i['detail'] }}

@endif
@endforeach @foreach($infos as $i)

{{ $i['message'] }}

@endforeach
@endif
{{-- Readiness score --}}

Readiness Score

{{ $score }} /100
@if($score === 100)

Launch ready

All systems go.

@elseif($score >= 60)

Nearly ready

Review warnings above.

@else

Needs attention

Fix errors before sending.

@endif
{{-- Approval workflow + A/B winner --}} @php $reviewer = $campaign->approved_by ? \App\Models\User::find($campaign->approved_by) : null; @endphp

Approval workflow

Lock down the bytes a reviewer signs off on.

@if($campaign->approval_status === 'pending')

Awaiting approval. The campaign body and schedule are locked until this review completes.

@if($campaign->approval_notes)

Author notes: "{{ $campaign->approval_notes }}"

@endif
@csrf
@csrf
@elseif($campaign->approval_status === 'approved')

Approved {{ optional($campaign->approved_at)->diffForHumans() }} @if($reviewer) by {{ $reviewer->name }}@endif. Ready to launch.

@if($campaign->approval_notes)

"{{ $campaign->approval_notes }}"

@endif @elseif($campaign->approval_status === 'rejected')

Rejected {{ optional($campaign->approved_at)->diffForHumans() }} @if($reviewer) by {{ $reviewer->name }}@endif.

@if($campaign->approval_notes)

{{ $campaign->approval_notes }}

@endif

Edit the campaign and resubmit when ready.

@else

No review requested yet.

@if(in_array($campaign->status, ['draft']))
@csrf
@else

Only drafts can be submitted for approval.

@endif @endif
@if($campaign->subject_b && $campaign->ab_split_percent && !empty($abStats))

A/B winner

{{ $campaign->ab_split_percent }}% of recipients receive variant B; pick winner by opens or clicks.

@foreach(['a' => 'Subject A', 'b' => 'Subject B'] as $key => $label) @php $s = $abStats[$key]; $isWinner = $campaign->winner_variant === $key; @endphp
{{ $label }} @if($isWinner)★ winner@endif {{ $s['sent'] }} sent

{{ $key === 'b' ? $campaign->subject_b : $campaign->subject }}

{{ $s['opens'] }} ({{ $s['open_rate'] }}%) {{ $s['clicks'] }} ({{ $s['click_rate'] }}%)
@endforeach
@if($campaign->winner_variant)

Variant {{ strtoupper($campaign->winner_variant) }} picked on {{ $campaign->winner_metric }} {{ optional($campaign->winner_picked_at)->diffForHumans() }}.

@else
@csrf
@endif
@endif
{{-- Schedule & launch --}} @php // Curated common zones; the JS layer below will inject the browser's // detected zone if it isn't already in this list, so any user gets a // sensible default without having to scroll through 400+ IANA names. $tzOptions = [ 'America/New_York' => 'Eastern (New York)', 'America/Chicago' => 'Central (Chicago)', 'America/Denver' => 'Mountain (Denver)', 'America/Phoenix' => 'Mountain (Phoenix — no DST)', 'America/Los_Angeles' => 'Pacific (Los Angeles)', 'America/Anchorage' => 'Alaska (Anchorage)', 'Pacific/Honolulu' => 'Hawaii (Honolulu)', 'UTC' => 'UTC', 'Europe/London' => 'London', 'Europe/Berlin' => 'Berlin / Paris', 'Asia/Tokyo' => 'Tokyo', 'Australia/Sydney' => 'Sydney', ]; $defaultTz = $campaign->schedule_timezone ?: config('app.timezone', 'America/Denver'); // datetime-local default: 15 minutes from now in the chosen tz. try { $defaultWhen = \Illuminate\Support\Carbon::now($defaultTz)->addMinutes(15)->format('Y-m-d\TH:i'); } catch (\Throwable) { $defaultWhen = \Illuminate\Support\Carbon::now()->addMinutes(15)->format('Y-m-d\TH:i'); } @endphp

Schedule & Launch

Mode: {{ ucfirst($campaign->schedule_mode) }} @if($campaign->scheduled_at) · @if($campaign->schedule_timezone) {{ $campaign->scheduled_at->copy()->setTimezone($campaign->schedule_timezone)->format('M j, Y · g:i A') }} ({{ $campaign->schedule_timezone }}) @else {{ $campaign->scheduled_at->format('M j, Y · g:i A') }} UTC @endif @endif @if($campaign->throttle_per_minute) · throttle {{ $campaign->throttle_per_minute }}/min @endif @if($campaign->cron_expression)·{{ $campaign->cron_expression }}@endif @if($campaign->next_run_at)·next {{ $campaign->next_run_at->diffForHumans() }}@endif
@if(in_array($campaign->status, ['draft','paused','scheduled'])) @php $approvalBlocks = in_array($campaign->approval_status, ['pending','rejected'], true); @endphp @endif @if($campaign->isPausable())
@csrf
@endif @if($campaign->status==='paused')
@csrf
@endif @if($campaign->isCancelable())
@csrf
@endif @if(!in_array($campaign->status, ['sent','sending']))
@csrf @method('DELETE')
@endif
{{-- Unified Send / Schedule modal --}}

Send this campaign

@csrf
{{-- Auto-detected browser timezone banner. Populated by the script below so users never have to guess. --}}

Stored as UTC; displayed back in this zone.

Caps Resend API pressure during large blasts.

{{-- Send test --}}

Send a Test

Send yourself a preview before launching.

@csrf
@endsection @push('scripts') @endpush