202 lines
10 KiB
PHP

@extends('admin.layouts.app')
@section('title', $mode === 'create' ? '메일 템플릿 생성' : '메일 템플릿 수정')
@section('page_title', $mode === 'create' ? '메일 템플릿 생성' : '메일 템플릿 수정')
@section('page_desc', '스킨을 선택하고 제목/본문을 작성한 뒤, 발송 화면에서 바로 적용할 수 있습니다.')
@push('head')
<style>
.grid{display:grid; grid-template-columns: 1fr 1fr; gap:16px;}
@media (max-width: 1100px){ .grid{grid-template-columns:1fr;} }
.btn{padding:8px 12px;font-size:13px;border-radius:12px;line-height:1.1;text-decoration:none;display:inline-flex;align-items:center;justify-content:center;gap:6px;
border:1px solid rgba(255,255,255,.10);background:rgba(255,255,255,.06);color:inherit;cursor:pointer;}
.btn:hover{background:rgba(255,255,255,.10);}
.btn--primary{background:rgba(59,130,246,.88);border-color:rgba(59,130,246,.95);color:#fff;}
.btn--ghost{background:transparent;}
.mono{padding:4px 8px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.10);
font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:12px;}
.previewBox{border:1px solid rgba(255,255,255,.10); background:rgba(0,0,0,.10); border-radius:14px; overflow:hidden;}
.previewHead{display:flex; justify-content:space-between; align-items:center; padding:10px 12px; border-bottom:1px solid rgba(255,255,255,.08);}
.previewBody{padding:12px;}
.emailFrame{background:#fff; color:#111; border-radius:12px; padding:14px;}
.emailFrame.dark{background:#0b1220; color:#e5e7eb;}
</style>
@endpush
@section('content')
<form method="POST" action="{{ $mode === 'create' ? route('admin.mail.templates.store') : route('admin.mail.templates.update', ['id'=>$tpl->id]) }}">
@csrf
@if($mode !== 'create')
@method('PUT')
@endif
<div class="grid">
<div class="a-card" style="padding:16px;">
@if($mode === 'create')
<div style="margin-bottom:12px;">
<div class="a-muted" style="margin-bottom:6px;">Code (unique)</div>
<input class="a-input" name="code" value="{{ old('code') }}" placeholder="ex) event_seol_2026">
<div class="a-muted" style="margin-top:6px;">영문/숫자/대시/언더바 3~60</div>
</div>
@else
<div style="margin-bottom:12px;">
<div class="a-muted" style="margin-bottom:6px;">Code</div>
<div><span class="mono">{{ $tpl->code }}</span></div>
</div>
@endif
<div style="margin-bottom:12px;">
<div class="a-muted" style="margin-bottom:6px;">제목(관리용)</div>
<input class="a-input" name="title" id="titleText" value="{{ old('title', $tpl->title ?? '') }}">
</div>
<div style="margin-bottom:12px;">
<div class="a-muted" style="margin-bottom:6px;">메일 제목(Subject)</div>
<input class="a-input" name="subject" id="subjectText" value="{{ old('subject', $tpl->subject_tpl ?? '') }}" placeholder="예) [PIN FOR YOU] 설맞이 혜택 안내">
</div>
<div style="margin-bottom:12px;">
<div class="a-muted" style="margin-bottom:6px;">스킨</div>
<select class="a-input" name="skin_key" id="skinKey">
@foreach(($skins ?? []) as $sk)
<option value="{{ $sk['key'] }}" @selected(old('skin_key', $tpl->skin_key ?? 'clean') === $sk['key'])>
{{ $sk['label'] }}
</option>
@endforeach
</select>
<div class="a-muted" style="font-size:12px; margin-top:6px;">발송 선택된 스킨으로 렌더링됩니다.</div>
</div>
<div style="margin-bottom:12px;">
<div class="a-muted" style="margin-bottom:6px;">본문</div>
<textarea class="a-input" name="body" id="bodyText" rows="10">{{ old('body', $tpl->body_tpl ?? '') }}</textarea>
<div style="display:flex; gap:8px; flex-wrap:wrap; align-items:center; margin-top:10px;">
<button type="button" class="btn btn--ghost" data-insert="{_text_02_}">{_text_02_}</button>
<button type="button" class="btn btn--ghost" data-insert="{_text_03_}">{_text_03_}</button>
<button type="button" class="btn btn--ghost" data-insert="{_text_04_}">{_text_04_}</button>
<span class="a-muted" style="font-size:12px;">토큰 빠른 삽입</span>
</div>
</div>
<div style="margin-bottom:12px;">
<div class="a-muted" style="margin-bottom:6px;">설명</div>
<input class="a-input" name="description" value="{{ old('description', $tpl->description ?? '') }}">
</div>
<div style="margin-bottom:16px;">
<label class="a-pill">
<input type="checkbox" name="is_active" value="1" @checked((int)old('is_active', $tpl->is_active ?? 1) === 1)>
활성
</label>
</div>
<div style="display:flex; gap:8px; flex-wrap:wrap;">
<button class="btn btn--primary" type="submit">저장</button>
<a class="btn" href="{{ route('admin.mail.templates.index') }}">목록</a>
</div>
</div>
<div class="a-card" style="padding:16px;">
<div class="previewBox">
<div class="previewHead">
<div class="a-muted">미리보기</div>
<div class="a-muted" style="font-size:12px;">스킨 + 현재 입력값</div>
</div>
<div class="previewBody">
<iframe id="tplPreviewIframe"
style="width:100%; height:720px; border:0; border-radius:12px; background:#fff;"></iframe>
</div>
</div>
<div class="a-muted" style="font-size:12px; margin-top:10px; line-height:1.6;">
* 1 버전은 SMTP “접수 성공” 기준으로 완료 처리합니다.<br>
* 안정화 SES 이벤트(Delivery/Bounce) 연동해 정확도 올리면 됩니다.
</div>
</div>
</div>
</form>
@push('scripts')
<script>
(() => {
const subjectText = document.getElementById('subjectText');
const bodyText = document.getElementById('bodyText');
const skinKeyEl = document.getElementById('skinKey');
const pvSubject = document.getElementById('pvSubject');
const pvBody = document.getElementById('pvBody');
const frame = document.getElementById('previewFrame');
const PREVIEW_URL = @json(\Illuminate\Support\Facades\Route::has('admin.mail.preview') ? route('admin.mail.preview') : '');
const csrf = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
const iframe = document.getElementById('tplPreviewIframe');
let tmr = null;
async function renderPreview(){
if(!PREVIEW_URL || !iframe) return;
const fd = new FormData();
fd.append('skin_key', skinKeyEl?.value || 'clean');
fd.append('subject', subjectText?.value || '');
fd.append('body', bodyText?.value || '');
try{
const res = await fetch(PREVIEW_URL, {
method: 'POST',
headers: csrf ? {'X-CSRF-TOKEN': csrf} : {},
body: fd,
});
iframe.srcdoc = await res.text();
}catch(e){
iframe.srcdoc = `<div style="padding:16px;font-family:system-ui">preview fail: ${String(e)}</div>`;
}
}
function debounce(){
clearTimeout(tmr);
tmr = setTimeout(renderPreview, 250);
}
[subjectText, bodyText].forEach(el => el?.addEventListener('input', debounce));
skinKeyEl?.addEventListener('change', debounce);
renderPreview();
function escapeHtml(s){
return String(s).replace(/[&<>"']/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m]));
}
function refreshPreview(){
const sub = (subjectText?.value || '').trim() || '(제목)';
const body = (bodyText?.value || '').trim();
if(pvSubject) pvSubject.textContent = sub;
if(pvBody) pvBody.innerHTML = body ? escapeHtml(body).replace(/\n/g,'<br>') : '<span style="opacity:.7;">(본문)</span>';
const k = (skinKeyEl?.value || 'clean');
frame?.classList.toggle('dark', k === 'dark');
}
[subjectText, bodyText].forEach(el => el?.addEventListener('input', refreshPreview));
skinKeyEl?.addEventListener('change', refreshPreview);
refreshPreview();
// token insert
document.querySelectorAll('[data-insert]').forEach(btn => {
btn.addEventListener('click', () => {
const token = btn.getAttribute('data-insert') || '';
const ta = bodyText;
if(!ta) return;
const start = ta.selectionStart ?? ta.value.length;
const end = ta.selectionEnd ?? ta.value.length;
ta.value = ta.value.slice(0,start) + token + ta.value.slice(end);
ta.focus();
ta.selectionStart = ta.selectionEnd = start + token.length;
refreshPreview();
});
});
})();
</script>
@endpush
@endsection