@php $tplName = $template->template_name ?? ''; $tplBody = $template->template_body ?? ''; $tplHeader = $template->header ?? ''; $tplFooter = $template->footer ?? ''; $tplCategory = $template->meta_category ?? 'utility'; $tplLang = $template->language ?? 'en_US'; $tplType = $template->template_type ?? 'standard'; $tplAttach = $template->attachment_type ?? 'none'; $tplStatus = $template->status ?? 'pending'; $tplButtons = is_array($template->buttons ?? null) ? $template->buttons : []; $tplCarousel = is_array($template->carousel_data ?? null) ? $template->carousel_data : []; $tplFile = $template->attachment_file ?? null; $tplFileUrl = $tplFile ? asset('storage/' . $tplFile) : null; // Rebuild the picker's { slot: attribute_key } JSON from the saved // variable_map's stored [{num,key}] shape so the mapping panel + // hidden `variable_map_json` field load pre-populated. Slots that // were left at the literal number (unmapped) come back as-is. $savedVmap = is_array($template->variable_map ?? null) ? $template->variable_map : []; $tplVarMapJson = []; // Per-section {slot => key} so we can both seed the picker's hidden // field AND rewrite stored positional {{ N }} back to the NAMED token // {{ key }} for display — so the editor shows meaning, not a number. $sectionSlotKey = ['header' => [], 'body' => []]; foreach (['header', 'body'] as $vmapSection) { foreach ($savedVmap[$vmapSection] ?? [] as $entry) { if (!is_array($entry)) { continue; } $num = (string) ($entry['num'] ?? ''); $key = (string) ($entry['key'] ?? ''); if ($num === '' || $key === '') { continue; } $tplVarMapJson[$num] = $key; $sectionSlotKey[$vmapSection][$num] = $key; } } $tplVarMapJson = json_encode((object) $tplVarMapJson, JSON_UNESCAPED_SLASHES); // Display-only: rewrite each {{ N }} in the stored body/header to the // mapped attribute key {{ key }}. Storage stays positional — the // controller re-normalizes named → positional on save. A slot with no // mapped key (or that maps to its own number) is left as {{ N }}. $namedFor = function (?string $text, array $slotKey): string { $text = (string) $text; if ($text === '') { return $text; } return preg_replace_callback( '/\{\{\s*(\d+)\s*\}\}/u', function ($m) use ($slotKey) { $slot = $m[1]; $key = $slotKey[$slot] ?? null; return $key !== null && $key !== '' && !ctype_digit($key) ? '{{ ' . $key . ' }}' : $m[0]; }, $text, ); }; $tplBodyDisplay = $namedFor($tplBody, $sectionSlotKey['body']); $tplHeaderDisplay = $namedFor($tplHeader, $sectionSlotKey['header']); @endphp
Templates / Edit / tpl_{{ str_pad($template->id, 4, '0', STR_PAD_LEFT) }}
{{ __('Edit') }} {{ $tplName }}
@php $statusBadge = match ($tplStatus) { 'approved' => ['bg-wa-green/15 text-wa-deep border-wa-green/40', 'Approved'], 'rejected' => ['bg-accent-coral/10 text-accent-coral border-accent-coral/40', 'Rejected'], default => ['bg-amber-100 text-amber-800 border-amber-300', 'Pending'], }; @endphp {{ $statusBadge[1] }}
@if ($errors->any())
{{ __('Could not save the template:') }}
    @foreach ($errors->all() as $msg)
  • {{ $msg }}
  • @endforeach
@endif
@csrf @method('PUT')
01 {{ __('Identity') }} {{ __('required') }}
{{ __('a-z, 0-9, _ only / max 60.') }}
{{ __('Determines Meta review path.') }}
{{ __('One locale per template.') }}
{{-- Twilio ContentSid: per-template pointer to a Twilio Content Builder template. Required for Twilio MARKETING / UTILITY / AUTHENTICATION sends to remain compliant. Leave blank for Baileys-only / WABA-only workspaces. Only shown when the workspace's active engine is Twilio. --}} @if ($isTwilio ?? false)
{{ __('Paste the HX… ContentSid from Twilio Content Builder. Required for compliant Twilio sends of MARKETING / UTILITY / AUTHENTICATION templates.') }}
@endif
{{-- Format is fixed at create-time and not editable; we just keep the hidden input so the controller still receives the original template_type on submit, and an empty #type-seg so the JS that toggles standard/carousel sections still has its expected hooks (it only acts on click). --}}
03 {{ __('Header') }} {{ __('optional') }}
04 {{ __('Body') }} {{ __('required') }} / 0/1024
B I S ‹› {{-- Named-only authoring: this pill opens the `/` attribute picker on the body so the operator inserts a NAMED token ({{name}}) — the server normalizes it to positional {{1}} on save. (Replaces the old {{1}}–{{4}} numbered chips.) --}} {{ __('Variable') }} 0/1024
{{-- Variable mapping — pre-populated from the saved template's variable_map. Records which attribute each {{N}} slot resolves to at send time. Hidden `variable_map_json` is what the controller turns back into `variable_map`. --}}
{{ __('Markdown:') }} *bold* _italic_ ~strike~ ```code```
05 {{ __('Attachment') }} {{ __('optional / image, video, or PDF') }}
{{ $tplFile ? basename($tplFile) : 'Choose sample file' }}
{{ $tplFile ? 'tap to replace' : 'required by Meta for media templates' }}
{{ $tplFile ? 'Replace' : 'Browse' }}
06 {{ __('Footer') }} {{ __('optional / max 60') }}
{{ __('Plain text under the body. No variables.') }}
07 {{ __('Buttons') }} {{ __('optional / up to 10') }}
{{ __('Call to action') }} {{ __('Quick reply') }} {{ __('Mix') }}
@foreach ($tplButtons as $btn) @php $btnType = $btn['type'] ?? 'visit_website'; $btnText = $btn['text'] ?? ''; $btnValue = $btn['value'] ?? ''; @endphp
@endforeach
{{-- The "Interactive (List menu / Poll)" section that lived here was removed — those controls aren't part of the WhatsApp Business template surface. Buttons + Quick replies in section 06 above cover every interactive option that actually goes to WhatsApp. --}}
@php $carHeader = $tplCarousel['header'] ?? ''; $carFooter = $tplCarousel['footer'] ?? ''; $carBody = $tplCarousel['body'] ?? ''; $carCards = $tplCarousel['cards'] ?? []; @endphp