{% extends 'base.html' %}
{% block title %}Edit {{ site.name }} — AgentForms{% endblock %}
{% block head_extra %}
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style nonce="{{ csp_nonce }}">
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--bg: #f8f8f8;
--bg-card: #ffffff;
--text: #1a1a2e;
--text-muted: #666;
--text-dim: #999;
--accent: #1a1a2e;
--accent-hover: #2d2d44;
--border: #e8e8e8;
--danger: #dc3545;
--success: #10b981;
--warning: #f59e0b;
--info: #4a9eff;
--info-bg: #e8f4fd;
}
body { font-family: 'Inter', system-ui, sans-serif; background: var(--bg); color: var(--text); line-height: 1.5; }
.container { max-width: 960px !important; margin: 0 auto; padding: 24px 24px !important; }
/* LAYOUT */
.edit-header { margin-bottom: 32px; }
.edit-header h1 { font-size: 24px; font-weight: 700; margin-bottom: 4px; }
.edit-header p { color: var(--text-muted); font-size: 14px; }
.edit-header .token-line { font-family: monospace; font-size: 13px; color: var(--text-dim); margin-top: 8px; }
.tabs { display: flex; gap: 0; border-bottom: 2px solid var(--border); margin-bottom: 32px; overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none; }
.tabs::-webkit-scrollbar { display: none; }
.tab {
padding: 10px 20px; font-size: 14px; font-weight: 600; color: var(--text-muted);
text-decoration: none; border-bottom: 2px solid transparent; margin-bottom: -2px; cursor: pointer;
transition: all 0.15s; flex-shrink: 0; white-space: nowrap;
}
.tab:hover { color: var(--text); }
.tab.active { color: var(--accent); border-bottom-color: var(--accent); }
.panel { display: none; }
.panel.active { display: block; }
/* CARDS */
.card {
background: var(--bg-card); border: 1px solid var(--border);
border-radius: 8px; padding: 24px; margin-bottom: 20px;
}
.card h3 { font-size: 16px; font-weight: 600; margin-bottom: 16px; }
/* FIELD BUILDER */
.field-list { display: flex; flex-direction: column; gap: 12px; margin-bottom: 16px; }
.field-search {
margin-bottom: 12px; position: relative;
}
.field-search input {
width: 100%; padding: 8px 12px 8px 36px; border: 1px solid var(--border); border-radius: 6px;
font-size: 13px; font-family: inherit; background: white;
}
.field-search input:focus { outline: none; border-color: var(--accent); }
.field-search .search-icon {
position: absolute; left: 12px; top: 50%; transform: translateY(-50%);
color: var(--text-dim); font-size: 14px; pointer-events: none;
}
.field-search .search-clear {
position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
background: none; border: none; color: var(--text-dim); cursor: pointer;
font-size: 16px; padding: 2px 6px; border-radius: 3px; display: none;
}
.field-search .search-clear:hover { background: var(--bg); color: var(--text); }
.field-search .search-count {
font-size: 11px; color: var(--text-dim); margin-top: 4px;
}
.field-row { position: relative; }
.field-row.hidden { display: none !important; }
.field-error-bar {
position: absolute; right: 0; top: 0; bottom: 0; width: 3px;
background: #ef4444; border-radius: 0 6px 6px 0;
}
.field-row.has-error .field-item { border-color: #ef4444; }
.field-error-list {
display: flex; flex-wrap: wrap; gap: 6px;
padding: 6px 16px 8px; margin: 0;
list-style: none;
}
.field-error-list li {
font-size: 11px; color: #ef4444;
background: rgba(239, 68, 68, 0.1);
padding: 2px 8px; border-radius: 3px;
border: 1px solid rgba(239, 68, 68, 0.2);
}
.field-item {
display: flex; align-items: center; gap: 12px; padding: 12px 16px;
background: var(--bg); border: 1px solid var(--border); border-radius: 6px;
}
.field-item input, .field-item select {
padding: 6px 10px; border: 1px solid var(--border); border-radius: 4px;
font-size: 13px; font-family: inherit; background: white;
}
.field-item .field-key { width: 140px; }
.field-item .field-type { width: 120px; }
.field-item .field-label { width: 160px; }
.field-item .field-required { width: 80px; }
.field-actions { display: flex; gap: 4px; margin-left: auto; }
.field-expand-btn {
background: none; border: 1px solid var(--border); color: var(--text-muted); cursor: pointer;
font-size: 12px; padding: 4px 10px; border-radius: 4px; font-family: inherit;
}
.field-expand-btn:hover { border-color: var(--accent); color: var(--text); }
.field-expand-btn.active { background: var(--accent); color: white; border-color: var(--accent); }
.field-remove {
background: none; border: none; color: var(--danger); cursor: pointer;
font-size: 18px; padding: 4px 8px; border-radius: 4px;
}
.field-remove:hover { background: #fde8ea; }
/* ADVANCED FIELD PANEL */
.field-advanced {
display: none; margin-top: 8px; padding: 16px;
background: #fafafa; border: 1px solid var(--border); border-radius: 6px;
}
.field-advanced.open { display: block; }
.field-advanced h4 {
font-size: 13px; font-weight: 600; color: var(--text-muted);
margin-bottom: 12px; text-transform: uppercase; letter-spacing: 0.5px;
}
.field-advanced-grid {
display: grid; grid-template-columns: 1fr 1fr; gap: 12px;
}
.field-advanced-grid .full-width { grid-column: 1 / -1; }
.field-adv-group { margin-bottom: 0; }
.field-adv-group label {
display: block; font-size: 12px; font-weight: 500; color: var(--text-muted);
margin-bottom: 4px;
}
.field-adv-group input, .field-adv-group select, .field-adv-group textarea {
width: 100%; padding: 6px 10px; border: 1px solid var(--border);
border-radius: 4px; font-size: 13px; font-family: inherit; background: white;
}
.field-adv-group textarea { resize: vertical; min-height: 48px; }
.field-adv-hint { font-size: 11px; color: var(--text-dim); margin-top: 2px; }
.field-adv-section {
margin-bottom: 16px; padding-bottom: 16px;
border-bottom: 1px solid var(--border);
}
.field-adv-section:last-child { margin-bottom: 0; padding-bottom: 0; border-bottom: none; }
.field-adv-section-title {
font-size: 12px; font-weight: 600; color: var(--info);
margin-bottom: 10px; text-transform: uppercase; letter-spacing: 0.3px;
}
.add-field-btn {
display: inline-flex; align-items: center; gap: 6px; padding: 8px 16px;
background: none; border: 1px dashed var(--border); border-radius: 6px;
color: var(--text-muted); cursor: pointer; font-size: 13px; font-family: inherit;
}
.add-field-btn:hover { border-color: var(--accent); color: var(--text); }
/* FORM ELEMENTS */
input[type="text"], input[type="email"], input[type="url"], textarea, select {
width: 100%; padding: 10px 12px; border: 1px solid var(--border);
border-radius: 6px; font-size: 14px; font-family: inherit;
}
input:focus, textarea:focus, select:focus { outline: none; border-color: var(--accent); }
label { display: block; font-size: 13px; font-weight: 500; margin-bottom: 4px; color: var(--text-muted); }
.form-group { margin-bottom: 16px; }
.form-hint { font-size: 12px; color: var(--text-dim); margin-top: 4px; }
.btn {
display: inline-flex; align-items: center; gap: 6px; padding: 10px 20px;
background: var(--accent); color: white; border: none; border-radius: 6px;
font-size: 14px; font-weight: 600; cursor: pointer; font-family: inherit;
}
.btn:hover { background: var(--accent-hover); }
.btn-outline {
background: none; border: 1px solid var(--border); color: var(--text);
}
.btn-outline:hover { background: var(--bg); }
.checkbox-group { display: flex; align-items: center; gap: 8px; }
.checkbox-group input[type="checkbox"] { width: 18px; height: 18px; }
.flash { padding: 12px 16px; border-radius: 6px; margin-bottom: 16px; font-size: 14px; }
.flash-success { background: #d1fae5; color: #065f46; }
.flash-error { background: #fee2e2; color: #991b1b; }
.flash-warning { background: #fef3c7; color: #92400e; }
.empty-state {
text-align: center; padding: 40px 20px; color: var(--text-dim);
}
.empty-state p { font-size: 14px; }
/* ANALYTICS SUMMARY */
.stats-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
.stat-box { background: var(--bg); border: 1px solid var(--border); border-radius: 6px; padding: 16px; text-align: center; }
.stat-label { font-size: 12px; font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px; }
.stat-value { font-size: 28px; font-weight: 700; color: var(--text); }
.bar-segment { background: var(--accent); border-radius: 3px 3px 0 0; min-width: 4px; transition: height 0.2s; }
.bar-segment:hover { opacity: 0.8; }
.bar-label { flex: 1; text-align: center; font-size: 10px; color: var(--text-dim); }
/* MOBILE */
@media (max-width: 640px) {
.container { padding: 16px 12px !important; }
.nav-inner { padding: 0 12px; flex-direction: column; gap: 8px; text-align: center; }
.nav-inner > div { display: flex; flex-wrap: wrap; justify-content: center; gap: 4px; }
.nav-inner a { padding: 8px 4px; min-height: 44px; display: inline-block; }
.tabs { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.tab { padding: 10px 14px; font-size: 13px; white-space: nowrap; }
.field-item { flex-direction: column; align-items: stretch; gap: 8px; }
.field-item .field-key,
.field-item .field-type,
.field-item .field-label,
.field-item .field-required { width: 100%; }
.field-actions { margin-left: 0; justify-content: flex-end; }
.field-remove { align-self: flex-end; }
.field-advanced-grid { grid-template-columns: 1fr; }
.card { padding: 16px; }
.edit-header h1 { font-size: 20px; }
}
/* INTEGRATIONS */
.int-header { display: flex; justify-content: space-between; align-items: flex-start; flex-wrap: wrap; gap: 12px; margin-bottom: 20px; }
.int-header h3 { margin: 0; }
.int-header p { font-size: 13px; color: var(--text-muted); margin-top: 4px; }
.int-grid { display: grid; gap: 16px; }
.int-card {
background: var(--bg-card); border: 1px solid var(--border); border-radius: 8px;
padding: 20px; display: flex; flex-direction: column; gap: 12px;
transition: box-shadow 0.15s;
}
.int-card:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.06); }
.int-card-header { display: flex; align-items: center; gap: 12px; }
.int-card-icon {
width: 44px; height: 44px; border-radius: 8px; display: flex; align-items: center;
justify-content: center; font-size: 22px; flex-shrink: 0;
}
.int-card-title { font-size: 15px; font-weight: 600; }
.int-card-type { font-size: 12px; color: var(--text-dim); text-transform: capitalize; }
.int-card-actions { display: flex; gap: 8px; margin-left: auto; }
.int-card-body { font-size: 13px; color: var(--text-muted); }
.int-card-body .int-url {
font-family: monospace; font-size: 12px; background: var(--bg); padding: 6px 10px;
border-radius: 4px; word-break: break-all; max-width: 100%; overflow-x: auto;
}
.int-status-row { display: flex; gap: 16px; flex-wrap: wrap; font-size: 12px; }
.int-status-item { display: flex; align-items: center; gap: 4px; }
.int-status-item .dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.dot-success { background: var(--success); }
.dot-failure { background: var(--danger); }
.dot-neutral { background: var(--text-dim); }
.int-footer { display: flex; gap: 8px; margin-top: 4px; }
.int-btn-sm {
display: inline-flex; align-items: center; gap: 4px; padding: 5px 12px;
border: 1px solid var(--border); border-radius: 4px; background: white;
color: var(--text-muted); cursor: pointer; font-size: 12px; font-family: inherit; font-weight: 500;
transition: all 0.15s;
}
.int-btn-sm:hover { border-color: var(--accent); color: var(--text); }
.int-btn-sm.danger:hover { border-color: var(--danger); color: var(--danger); background: #fef2f2; }
.int-btn-sm.primary { background: var(--accent); color: white; border-color: var(--accent); }
.int-btn-sm.primary:hover { background: var(--accent-hover); }
.int-btn-sm:disabled { opacity: 0.5; cursor: not-allowed; }
/* Toggle switch */
.int-toggle { position: relative; width: 40px; height: 22px; flex-shrink: 0; }
.int-toggle input { opacity: 0; width: 0; height: 0; }
.int-toggle .slider {
position: absolute; inset: 0; background: #ccc; border-radius: 22px; cursor: pointer;
transition: 0.2s;
}
.int-toggle .slider::before {
content: ''; position: absolute; width: 16px; height: 16px; border-radius: 50%;
background: white; left: 3px; top: 3px; transition: 0.2s;
}
.int-toggle input:checked + .slider { background: var(--success); }
.int-toggle input:checked + .slider::before { transform: translateX(18px); }
/* Modal overlay */
.modal-overlay {
display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.45);
z-index: 1000; align-items: center; justify-content: center; padding: 20px;
}
.modal-overlay.open { display: flex; }
.modal-box {
background: var(--bg-card); border-radius: 12px; width: 100%; max-width: 520px;
max-height: 85vh; overflow-y: auto; box-shadow: 0 20px 60px rgba(0,0,0,0.15);
}
.modal-header {
display: flex; align-items: center; justify-content: space-between;
padding: 20px 24px 16px; border-bottom: 1px solid var(--border);
}
.modal-header h3 { font-size: 17px; font-weight: 600; margin: 0; }
.modal-close {
background: none; border: none; font-size: 22px; cursor: pointer; color: var(--text-muted);
padding: 4px 8px; border-radius: 4px; line-height: 1;
}
.modal-close:hover { background: var(--bg); color: var(--text); }
.modal-body { padding: 20px 24px; }
.modal-footer {
display: flex; gap: 8px; justify-content: flex-end; padding: 16px 24px;
border-top: 1px solid var(--border);
}
.modal-footer .btn-outline { background: white; }
.setup-guide {
background: var(--bg); border: 1px solid var(--border); border-radius: 6px;
padding: 0; overflow: hidden; margin-bottom: 16px;
}
.setup-guide summary {
padding: 10px 14px; font-size: 13px; font-weight: 600; cursor: pointer;
color: var(--info); user-select: none;
}
.setup-guide .guide-content {
padding: 0 14px 14px; font-size: 13px; color: var(--text-muted); line-height: 1.6;
}
.int-type-option {
display: flex; align-items: center; gap: 10px; padding: 10px 12px;
border: 1px solid var(--border); border-radius: 6px; cursor: pointer;
transition: all 0.15s; margin-bottom: 6px;
}
.int-type-option:hover { border-color: var(--accent); }
.int-type-option.selected { border-color: var(--accent); background: #f0f0ff; }
.int-type-option .type-icon { font-size: 20px; width: 28px; text-align: center; }
.int-type-option .type-name { font-size: 13px; font-weight: 600; }
.int-type-option .type-desc { font-size: 11px; color: var(--text-dim); }
@media (max-width: 640px) {
.int-card { padding: 16px; }
.int-card-header { flex-wrap: wrap; }
.int-card-actions { margin-left: 0; width: 100%; }
.int-status-row { gap: 10px; }
.int-footer { flex-wrap: wrap; }
}
/* VERSIONS TIMELINE */
.versions-timeline { display: flex; flex-direction: column; gap: 0; position: relative; }
.version-item {
display: flex; gap: 16px; padding: 16px 0;
border-bottom: 1px solid var(--border);
}
.version-item:last-child { border-bottom: none; }
.version-dot-col {
display: flex; flex-direction: column; align-items: center; width: 28px; flex-shrink: 0;
}
.version-dot {
width: 12px; height: 12px; border-radius: 50%; background: var(--accent);
border: 2px solid white; box-shadow: 0 0 0 1px var(--border);
}
.version-line {
width: 2px; flex: 1; background: var(--border); margin-top: 4px;
}
.version-content { flex: 1; min-width: 0; }
.version-header { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; margin-bottom: 4px; }
.version-meta { font-size: 13px; color: var(--text-muted); }
.version-meta strong { color: var(--text); font-weight: 600; }
.version-reason { font-size: 13px; color: var(--text-muted); margin-bottom: 6px; }
.version-diff { font-size: 12px; color: var(--text-dim); font-family: monospace; margin-bottom: 8px; }
.version-diff .field-added { color: var(--success); }
.version-diff .field-removed { color: var(--danger); }
.version-diff .field-modified { color: var(--info); }
.version-actions { display: flex; gap: 6px; }
.version-btn {
display: inline-flex; align-items: center; gap: 4px; padding: 5px 12px;
border: 1px solid var(--border); border-radius: 4px; background: white;
color: var(--text-muted); cursor: pointer; font-size: 12px; font-family: inherit; font-weight: 500;
transition: all 0.15s;
}
.version-btn:hover { border-color: var(--accent); color: var(--text); }
.version-btn.danger:hover { border-color: var(--danger); color: var(--danger); background: #fef2f2; }
.version-btn.primary { background: var(--accent); color: white; border-color: var(--accent); }
.version-btn.primary:hover { background: var(--accent-hover); }
.version-btn:disabled { opacity: 0.5; cursor: not-allowed; }
/* DIFF MODAL */
.diff-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.diff-table th, .diff-table td { padding: 8px 12px; text-align: left; border-bottom: 1px solid var(--border); }
.diff-table th { font-weight: 600; color: var(--text-muted); font-size: 12px; text-transform: uppercase; letter-spacing: 0.3px; }
.diff-added { background: #d1fae5; }
.diff-removed { background: #fee2e2; }
.diff-modified { background: #e8f4fd; }
.diff-unchanged { color: var(--text-dim); }
.diff-badge {
display: inline-block; padding: 2px 6px; border-radius: 3px; font-size: 11px; font-weight: 600; text-transform: uppercase;
}
.diff-badge.added { background: #d1fae5; color: #065f46; }
.diff-badge.removed { background: #fee2e2; color: #991b1b; }
.diff-badge.modified { background: #e8f4fd; color: #1e40af; }
/* DOCUMENTS PANEL */
.doc-subtabs { display: flex; gap: 0; }
.doc-subtab {
padding: 10px 20px; font-size: 14px; font-weight: 600; color: var(--text-muted);
cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -1px;
transition: all 0.15s;
}
.doc-subtab:hover { color: var(--text); }
.doc-subtab.active { color: var(--accent); border-bottom-color: var(--accent); }
.doc-status-badge {
display: inline-block; padding: 3px 8px; border-radius: 4px; font-size: 11px; font-weight: 600; text-transform: uppercase;
}
.doc-status-badge.pending { background: #fef3c7; color: #92400e; }
.doc-status-badge.ready { background: #d1fae5; color: #065f46; }
.doc-status-badge.failed { background: #fee2e2; color: #991b1b; }
.doc-status-badge.draft { background: #f3f4f6; color: #374151; }
.doc-status-badge.sent { background: #dbeafe; color: #1e40af; }
.doc-status-badge.viewed { background: #e0e7ff; color: #3730a3; }
.doc-status-badge.paid { background: var(--success); color: #fff; }
.doc-status-badge.void { background: var(--danger); color: #fff; }
.doc-status-badge.overdue { background: var(--warning); color: #fff; border: 1px solid #f59e0b; }
.doc-row-overdue td { background: #fef2f2 !important; }
/* History table */
.history-table { width: 100%; border-collapse: collapse; }
.history-table th { text-align: left; padding: 8px 10px; font-size: 12px; font-weight: 600; color: var(--text-muted); text-transform: uppercase; border-bottom: 2px solid var(--border); }
.history-table td { padding: 8px 10px; font-size: 13px; border-bottom: 1px solid var(--border); vertical-align: middle; }
.history-table tr:hover td { background: rgba(0,0,0,0.02); }
.history-table input[type="checkbox"] { width: 16px; height: 16px; cursor: pointer; accent-color: var(--accent); }
/* Gallery grid */
.gallery-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 16px; }
.gallery-card { background: var(--bg); border: 1px solid var(--border); border-radius: 8px; padding: 20px; transition: all 0.15s; display: flex; flex-direction: column; }
.gallery-card:hover { border-color: var(--accent); box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
.gallery-card h4 { margin: 0 0 8px; font-size: 15px; }
.gallery-card p { margin: 0 0 12px; font-size: 13px; color: var(--text-muted); flex: 1; }
.gallery-badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 600; background: var(--accent); color: #fff; margin-bottom: 12px; text-transform: uppercase; }
/* Schedule table */
.schedule-table { width: 100%; border-collapse: collapse; }
.schedule-table th { text-align: left; padding: 8px 12px; font-size: 12px; font-weight: 600; color: var(--text-muted); text-transform: uppercase; border-bottom: 1px solid var(--border); }
.schedule-table td { padding: 10px 12px; font-size: 14px; border-bottom: 1px solid var(--border); vertical-align: middle; }
.schedule-table tr:hover td { background: rgba(0,0,0,0.02); }
/* Toggle switch */
.toggle-switch { position: relative; display: inline-block; width: 40px; height: 22px; }
.toggle-switch input { opacity: 0; width: 0; height: 0; }
.toggle-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background: #ccc; border-radius: 22px; transition: 0.2s; }
.toggle-slider:before { position: absolute; content: ""; height: 16px; width: 16px; left: 3px; bottom: 3px; background: #fff; border-radius: 50%; transition: 0.2s; }
.toggle-switch input:checked + .toggle-slider { background: var(--success); }
.toggle-switch input:checked + .toggle-slider:before { transform: translateX(18px); }
@media (max-width: 640px) {
.version-item { flex-direction: column; gap: 8px; }
.version-dot-col { display: none; }
}
</style>
{% endblock %}
{% block content %}
<div class="container">
<div class="edit-header">
<h1>{{ site.name }}</h1>
<p>Configure your form fields and webhook settings.</p>
<div class="token-line">Token: <code>{{ site.token }}</code></div>
</div>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="flash flash-{{ category }}">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<div class="tabs">
<a class="tab active" data-onclick="showPanel('fields')">Form Fields</a>
<a class="tab" data-onclick="showPanel('webhook')">Webhook</a>
<a class="tab" data-onclick="showPanel('protection')">Protection</a>
<a class="tab" data-onclick="showPanel('embed')">Embed</a>
<a class="tab" data-onclick="showPanel('share')">Share</a>
<a class="tab" data-onclick="showPanel('analytics')">Analytics</a>
<a class="tab" data-onclick="showPanel('integrations')">Integrations</a>
<a class="tab" data-onclick="showPanel('versions')">Versions</a>
<a class="tab" data-onclick="showPanel('abtest')">A/B Test</a>
<a class="tab" data-onclick="showPanel('branding')">Branding</a>
<a class="tab" data-onclick="showPanel('documents')">Documents</a>
</div>
<!-- FIELDS PANEL -->
<div id="panel-fields" class="panel active">
<form method="POST" action="/sites/{{ site.id }}/edit" id="edit-form">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"/>
<input type="hidden" name="webhook_url" id="hidden_webhook_url">
<input type="hidden" name="webhook_enabled" id="hidden_webhook_enabled">
<input type="hidden" name="webhook_events" id="hidden_webhook_events">
<input type="hidden" name="field_config" id="field_config_json">
<div class="card">
<h3>Form Fields</h3>
<p style="font-size:13px;color:var(--text-muted);margin-bottom:16px;">
Define the fields your form collects. Click <strong>⚙ Advanced</strong> on any field for validation rules, conditional visibility, calculations, and multi-step grouping.
</p>
<div class="field-search">
<span class="search-icon">🔍</span>
<input type="text" id="field-search-input" placeholder="Filter fields by key, label, or type..." data-oninput="filterFields(this.value)">
<button type="button" class="search-clear" id="field-search-clear" data-onclick="clearFieldFilter()">×</button>
<div class="search-count" id="field-search-count"></div>
</div>
{% if field_config and field_config|length > 0 %}
<div class="field-list" id="field-list">
{% for field in field_config %}
<div class="field-row">
<div class="field-item">
<input type="text" class="field-key" value="{{ field.key }}" placeholder="Field key (e.g. name)">
<select class="field-type" data-onchange="onFieldTypeChange(this)">
<option value="text" {% if field.type == 'text' %}selected{% endif %}>Text</option>
<option value="email" {% if field.type == 'email' %}selected{% endif %}>Email</option>
<option value="phone" {% if field.type == 'phone' %}selected{% endif %}>Phone</option>
<option value="number" {% if field.type == 'number' %}selected{% endif %}>Number</option>
<option value="textarea" {% if field.type == 'textarea' %}selected{% endif %}>Textarea</option>
<option value="select" {% if field.type == 'select' %}selected{% endif %}>Select</option>
<option value="radio" {% if field.type == 'radio' %}selected{% endif %}>Radio</option>
<option value="checkbox" {% if field.type == 'checkbox' %}selected{% endif %}>Checkbox</option>
<option value="date" {% if field.type == 'date' %}selected{% endif %}>Date</option>
<option value="computed" {% if field.type == 'computed' %}selected{% endif %}>Computed</option>
</select>
<input type="text" class="field-label" value="{{ field.label }}" placeholder="Display label">
<select class="field-required">
<option value="0" {% if not field.required %}selected{% endif %}>Optional</option>
<option value="1" {% if field.required %}selected{% endif %}>Required</option>
</select>
<div class="field-actions">
<button type="button" class="field-expand-btn" data-onclick="toggleAdvanced(this)">⚙ Advanced</button>
<button type="button" class="field-remove" data-onclick="removeField(this)">×</button>
</div>
</div>
<div class="field-advanced">
{% include 'field_advanced.html' %}
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="empty-state" id="empty-state">
<p>No custom fields defined. Add your first field below.</p>
</div>
<div class="field-list" id="field-list" style="display:none;"></div>
{% endif %}
<button type="button" class="add-field-btn" data-onclick="addField()">+ Add Field</button>
</div>
<div style="display:flex;gap:12px;">
<button type="submit" class="btn">Save Fields</button>
</div>
</form>
</div>
<!-- WEBHOOK PANEL -->
<div id="panel-webhook" class="panel">
<form method="POST" action="/sites/{{ site.id }}/edit" id="webhook-form">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"/>
<input type="hidden" name="field_config" id="hidden_field_config">
<div class="card">
<h3>Webhook Configuration</h3>
<p style="font-size:13px;color:var(--text-muted);margin-bottom:20px;">
When enabled, every form submission will be POSTed to your webhook URL in real time. This lets you integrate with automation tools like n8n, Make, Zapier, or your own API.
</p>
<div class="form-group">
<label for="webhook_enabled_label">Enable Webhook</label>
<div class="checkbox-group">
<input type="checkbox" id="webhook_enabled" name="webhook_enabled"
{% if site.webhook_enabled %}checked{% endif %}>
<span style="font-size:14px;">Send submissions to webhook URL</span>
</div>
</div>
<div class="form-group">
<label for="webhook_url">Webhook URL</label>
<input type="url" id="webhook_url" name="webhook_url"
value="{{ site.webhook_url or '' }}"
placeholder="https://your-server.com/webhook/agentforms">
<div class="form-hint">POST request with JSON payload on every submission.</div>
</div>
<div class="form-group">
<label for="webhook_events">Trigger Events</label>
<select id="webhook_events" name="webhook_events">
<option value="submission" {% if not site.webhook_events or site.webhook_events == 'submission' %}selected{% endif %}>On Submission</option>
</select>
</div>
<div style="background:var(--bg);padding:16px;border-radius:6px;font-family:monospace;font-size:12px;margin-top:16px;overflow-x:auto;">
<div style="color:var(--text-dim);margin-bottom:8px;">Payload Example:</div>
{
"event": "submission",
"site_id": 1,
"site_name": "My Contact Form",
"submission_id": 42,
"submitted_at": "2025-07-06 12:00:00",
"fields": {
"name": "Alice Chen",
"email": "alice@example.com",
"message": "Hello!"
}
}
</div>
</div>
<div style="display:flex;gap:12px;">
<button type="submit" class="btn">Save Webhook</button>
</div>
</form>
</div>
<!-- PROTECTION PANEL -->
<div id="panel-protection" class="panel">
<form method="POST" action="/sites/{{ site.id }}/edit" id="protection-form">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"/>
<input type="hidden" name="field_config" id="hidden_protection_field_config">
<div class="card">
<h3>Bot Protection</h3>
<p style="font-size:13px;color:var(--text-muted);margin-bottom:20px;">
Configure honeypot and spam detection to filter bot submissions automatically.
</p>
<div class="form-group">
<label for="honeypot_enabled_label">Honeypot Field</label>
<div class="checkbox-group">
<input type="checkbox" id="honeypot_enabled" name="honeypot_enabled"
{% if site.honeypot_enabled %}checked{% endif %}>
<span style="font-size:14px;">Enable honeypot (invisible field bots fill, humans don't)</span>
</div>
<div class="form-hint">Injects an invisible <code>_fr_hp</code> field. If it has a value, the submission is rejected silently.</div>
</div>
<div class="form-group">
<label for="spam_filter_enabled_label">Spam Filter</label>
<div class="checkbox-group">
<input type="checkbox" id="spam_filter_enabled" name="spam_filter_enabled"
{% if site.spam_filter_enabled %}checked{% endif %}>
<span style="font-size:14px;">Enable keyword-based spam detection</span>
</div>
<div class="form-hint">Checks submission content against common spam patterns.</div>
</div>
<hr style="border:none;border-top:1px solid var(--border);margin:20px 0;">
<div class="form-group">
<label for="rate_limit_enabled_label">Rate Limiting</label>
<div class="checkbox-group">
<input type="checkbox" id="rate_limit_enabled" name="rate_limit_enabled"
{% if site.rate_limit_enabled %}checked{% endif %}>
<span style="font-size:14px;">Enable per-site rate limiting</span>
</div>
<div class="form-hint">Prevents rapid-fire submissions to your form. Configurable below.</div>
</div>
<div id="rate_limit_config" style="{% if not site.rate_limit_enabled %}display:none;{% endif %}">
<div class="form-group">
<label for="rate_limit_burst">Burst Limit</label>
<input type="number" id="rate_limit_burst" name="rate_limit_burst"
value="{{ site.rate_limit_burst or 20 }}"
min="1" max="1000" step="1">
<div class="form-hint">Maximum submissions allowed in a burst before throttling.</div>
</div>
<div class="form-group">
<label for="rate_limit_refill">Refill Rate</label>
<input type="number" id="rate_limit_refill" name="rate_limit_refill"
value="{{ site.rate_limit_refill or 0.5 }}"
min="0.01" max="100" step="0.01">
<div class="form-hint">Submissions added per second. E.g. 0.5 = one submission every 2 seconds.</div>
</div>
</div>
</div>
<div style="display:flex;gap:12px;">
<button type="submit" class="btn">Save Protection Settings</button>
</div>
</form>
<div class="card" style="margin-top:24px;">
<h3>Client Embed Snippet</h3>
<p style="font-size:13px;color:var(--text-muted);margin-bottom:16px;">
Add this to your client form to enable honeypot protection. Paste it inside the form tag:
</p>
<div style="background:var(--bg);padding:16px;border-radius:6px;font-family:monospace;font-size:12px;overflow-x:auto;margin-bottom:12px;">
<div style="color:var(--text-dim);margin-bottom:8px;">Option 1 — Inline HTML:</div>
<input type="text" name="_fr_hp" tabindex="-1" autocomplete="off"
style="position:absolute;left:-9999px;opacity:0;height:1px;width:1px;" />
</div>
<div style="background:var(--bg);padding:16px;border-radius:6px;font-family:monospace;font-size:12px;overflow-x:auto;margin-bottom:12px;">
<div style="color:var(--text-dim);margin-bottom:8px;">Option 2 — JavaScript auto-inject:</div>
<script>
(function(){
var form = document.querySelector('form[action*="/api/submit"]');
if (form) {
var inp = document.createElement('input');
inp.type = 'hidden'; inp.name = '_fr_hp'; inp.tabIndex = -1;
inp.style.cssText = 'position:absolute;left:-9999px;opacity:0;height:1px;width:1px;';
form.appendChild(inp);
}
})();
</script>
</div>
<div style="background:var(--bg);padding:16px;border-radius:6px;font-family:monospace;font-size:12px;overflow-x:auto;">
<div style="color:var(--text-dim);margin-bottom:8px;">API endpoint:</div>
POST /api/submit?token={{ site.token }}
</div>
</div>
</div>
<!-- EMBED PANEL -->
<div id="panel-embed" class="panel">
<div class="card">
<h3>Embed This Form on Your Website</h3>
<p style="font-size:13px;color:var(--text-muted);margin-bottom:20px;">
Drop this snippet into any HTML page. The SDK fetches your form config, renders a styled form, handles submission, and includes honeypot protection — no extra setup needed.
</p>
<div style="margin-bottom:16px;">
<label style="display:flex;align-items:center;gap:8px;">
<input type="checkbox" id="embed-theme-check" checked>
Use dark theme
</label>
</div>
<div style="background:var(--bg);padding:16px;border-radius:6px;font-family:monospace;font-size:12px;overflow-x:auto;margin-bottom:12px;white-space:pre;" id="embed-snippet"><script src="{{ base_url }}/embed.js"></script>
<div id="agentform"></div>
<script>
AF.render('agentform', {
token: '{{ site.token }}',
theme: 'light',
successMessage: "Thank you! Your submission was received.",
onSubmit: function(data) { /* analytics hook */ }
});
</script></div>
<button type="button" class="btn" data-onclick="copyEmbedCode()">📋 Copy Embed Code</button>
<span id="copy-feedback" style="margin-left:12px;font-size:13px;color:var(--success);display:none;">Copied!</span>
</div>
<div class="card">
<h3>Configuration Options</h3>
<div style="font-size:13px;color:var(--text-muted);">
<div style="display:grid;gap:12px;">
<div><strong style="color:var(--text);">token</strong> <span style="color:var(--text-dim);">required</span> — Your form token: <code>{{ site.token }}</code></div>
<div><strong style="color:var(--text);">theme</strong> — <code>'light'</code>, <code>'dark'</code>, or <code>'transparent'</code></div>
<div><strong style="color:var(--text);">successMessage</strong> — Shown after successful submission</div>
<div><strong style="color:var(--text);">errorMessage</strong> — Shown on submission failure</div>
<div><strong style="color:var(--text);">submitLabel</strong> — Button text (default: "Submit")</div>
<div><strong style="color:var(--text);">colors</strong> — Override any CSS variable: <code>{ primary: '#4a9eff', bg: '#f5f5f5' }</code></div>
<div><strong style="color:var(--text);">onSubmit</strong> — Callback function after successful submission</div>
<div><strong style="color:var(--text);">onError</strong> — Callback function on submission failure</div>
</div>
</div>
</div>
<div class="card">
<h3>WordPress Plugin</h3>
<p style="font-size:13px;color:var(--text-muted);margin-bottom:16px;">
Embed this form natively in WordPress — no JavaScript required. Supports shortcodes, Gutenberg blocks, and automatic theme integration.
</p>
<div style="display:flex;flex-wrap:wrap;gap:8px;align-items:center;">
<a href="{{ request.host_url }}wp-plugin" class="btn" style="text-decoration:none;">⬇ Download Plugin (.zip)</a>
<a href="https://developer.wordpress.org/plugins/" target="_blank" style="font-size:12px;color:var(--text-muted);text-decoration:underline;">Install Guide →</a>
</div>
<div style="margin-top:16px;padding:12px;background:var(--bg);border-radius:6px;font-size:12px;color:var(--text-muted);">
<strong>Install:</strong> Upload the .zip via <code>Plugins → Add New → Upload Plugin</code>, activate, then paste your form token on the AgentForms settings page.
</div>
</div>
<div class="card">
<h3>What's Included</h3>
<ul style="font-size:13px;color:var(--text-muted);padding-left:20px;display:grid;gap:6px;">
<li>Auto-fetches field configuration from your form</li>
<li>Styled form matching your chosen theme</li>
<li>Client-side validation for required fields</li>
<li>Honeypot anti-bot protection (if enabled)</li>
<li>Loading spinner on submit</li>
<li>Success/error message handling</li>
<li>Impression tracking</li>
<li>Multi-step forms with progress bar</li>
<li>Conditional field visibility</li>
<li>Computed/calculated fields</li>
<li>Mobile-responsive layout</li>
</ul>
</div>
</div>
<!-- SHARE PANEL -->
<div id="panel-share" class="panel">
<div class="card">
<h3>Share Your Form</h3>
<p style="font-size:13px;color:var(--text-muted);margin-bottom:20px;">
Share your form via direct link, QR code, or embed it in an email campaign.
</p>
<div style="display:grid;gap:20px;">
<!-- HOSTED FORM URL -->
<div>
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;">
<strong style="font-size:14px;">Hosted Form URL</strong>
<button type="button" class="btn" style="padding:6px 14px;font-size:12px;" data-onclick="copyShareUrl('{{ site.token }}')">📋 Copy URL</button>
</div>
<div style="background:var(--bg);padding:12px 16px;border-radius:6px;font-family:monospace;font-size:13px;word-break:break-all;" id="share-url-display">
{{ request.scheme }}://{{ request.host }}/f/{{ site.token }}
</div>
<div class="form-hint" id="share-url-feedback" style="color:var(--success);display:none;">✓ Copied to clipboard!</div>
</div>
<!-- QR CODE -->
<div>
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;">
<strong style="font-size:14px;">QR Code</strong>
<div style="display:flex;gap:8px;">
<a href="/api/v2/forms/{{ site.token }}/qr?format=svg&size=300" download="{{ site.name | replace(' ', '_') }}-qr.svg" class="btn" style="padding:6px 14px;font-size:12px;text-decoration:none;">⬇ SVG</a>
<a href="/api/v2/forms/{{ site.token }}/qr?format=png&size=300" download="{{ site.name | replace(' ', '_') }}-qr.png" class="btn" style="padding:6px 14px;font-size:12px;text-decoration:none;">⬇ PNG</a>
</div>
</div>
<div style="background:white;padding:24px;border-radius:6px;text-align:center;border:1px solid var(--border);">
<img src="/api/v2/forms/{{ site.token }}/qr"
alt="QR Code for {{ site.name }}"
id="share-qr-img"
style="width:180px;height:180px;"
data-onerror="this.parentElement.innerHTML='<p style=color:var(--danger);font-size:13px>QR code failed to load</p>'">
</div>
<div class="form-hint">Scan with any smartphone camera to open the form.</div>
</div>
<!-- EMAIL HTML -->
<div>
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;">
<strong style="font-size:14px;">Email Campaign HTML</strong>
<button type="button" class="btn" style="padding:6px 14px;font-size:12px;" data-onclick="copyEmailHtml('{{ site.token }}')">📋 Copy Email HTML</button>
</div>
<div style="background:var(--bg);padding:12px 16px;border-radius:6px;font-family:monospace;font-size:11px;overflow-x:auto;max-height:200px;white-space:pre-wrap;word-break:break-all;opacity:0.7;" id="email-html-preview">
Loading...
</div>
<div class="form-hint" id="email-html-feedback" style="color:var(--success);display:none;">✓ Copied to clipboard!</div>
<div class="form-hint">Paste into email marketing tools (Mailchimp, SendGrid, etc.).</div>
</div>
</div>
</div>
</div>
<!-- BRANDING PANEL -->
<div id="panel-branding" class="panel">
<div class="card">
<h3>Branding & Custom Domain</h3>
<p style="font-size:13px;color:var(--text-muted);margin-bottom:20px;">
Customize your hosted form appearance and set up a custom domain. Custom domains require <strong>Pro</strong> tier.
</p>
<div style="display:grid;gap:20px;">
<!-- BRANDING COLOR -->
<div>
<label style="font-size:14px;font-weight:600;display:block;margin-bottom:8px;">Brand Color</label>
<div style="display:flex;gap:12px;align-items:center;">
<input type="color" id="branding-color-picker" value="#2563eb" style="width:48px;height:36px;border:1px solid var(--border);border-radius:4px;cursor:pointer;">
<input type="text" id="branding-color-input" placeholder="#2563eb" style="flex:1;padding:8px 12px;border:1px solid var(--border);border-radius:6px;font-family:monospace;" maxlength="7">
<span id="branding-color-preview" style="width:24px;height:24px;border-radius:50%;background:#2563eb;border:2px solid var(--border);"></span>
</div>
<div class="form-hint">Used for form headers, buttons, and focus states.</div>
</div>
<!-- LOGO URL -->
<div>
<label for="branding-logo-url" style="font-size:14px;font-weight:600;display:block;margin-bottom:8px;">Logo URL</label>
<input type="url" id="branding-logo-url" placeholder="https://example.com/logo.png" style="width:100%;padding:8px 12px;border:1px solid var(--border);border-radius:6px;">
<div class="form-hint">Displayed in the hosted form header.</div>
</div>
<!-- FAVICON URL -->
<div>
<label for="branding-favicon-url" style="font-size:14px;font-weight:600;display:block;margin-bottom:8px;">Favicon URL</label>
<input type="url" id="branding-favicon-url" placeholder="https://example.com/favicon.ico" style="width:100%;padding:8px 12px;border:1px solid var(--border);border-radius:6px;">
<div class="form-hint">Browser tab icon for your hosted form.</div>
</div>
<!-- OG TITLE -->
<div>
<label for="branding-og-title" style="font-size:14px;font-weight:600;display:block;margin-bottom:8px;">Open Graph Title</label>
<input type="text" id="branding-og-title" placeholder="{{ site.name }}" style="width:100%;padding:8px 12px;border:1px solid var(--border);border-radius:6px;">
<div class="form-hint">Used when sharing on social media.</div>
</div>
<!-- OG DESCRIPTION -->
<div>
<label for="branding-og-description" style="font-size:14px;font-weight:600;display:block;margin-bottom:8px;">Open Graph Description</label>
<textarea id="branding-og-description" placeholder="Describe your form..." rows="2" style="width:100%;padding:8px 12px;border:1px solid var(--border);border-radius:6px;resize:vertical;"></textarea>
</div>
<!-- OG IMAGE -->
<div>
<label for="branding-og-image" style="font-size:14px;font-weight:600;display:block;margin-bottom:8px;">Open Graph Image URL</label>
<input type="url" id="branding-og-image" placeholder="https://example.com/og-image.png" style="width:100%;padding:8px 12px;border:1px solid var(--border);border-radius:6px;">
<div class="form-hint">Recommended: 1200×630px for optimal social sharing.</div>
</div>
<!-- CUSTOM DOMAIN (Pro only) -->
<div id="branding-custom-domain-section" style="display:none;">
<label for="branding-custom-domain" style="font-size:14px;font-weight:600;display:block;margin-bottom:8px;">
Custom Domain
<span style="background:#fbbf24;color:#92400e;padding:2px 8px;border-radius:12px;font-size:11px;font-weight:600;margin-left:8px;">PRO</span>
</label>
<div style="display:flex;gap:12px;align-items:center;">
<input type="text" id="branding-custom-domain" placeholder="forms.yourdomain.com" style="flex:1;padding:8px 12px;border:1px solid var(--border);border-radius:6px;">
<button type="button" class="btn" style="padding:8px 16px;font-size:13px;" data-onclick="saveBranding()">Save All</button>
</div>
<div class="form-hint">
Point a CNAME record from <code>forms</code> to <code>agentforms.io</code>.
<a href="#" data-onclick="event.preventDefault(); alert('1. Add CNAME: forms.yourdomain.com → agentforms.io\\n2. Wait for DNS propagation (up to 48h)\\n3. Save above to enable')" style="color:var(--accent);">Setup Instructions →</a>
</div>
</div>
<!-- SAVE BUTTON -->
<div style="display:flex;gap:12px;align-items:center;">
<button type="button" class="btn" style="padding:8px 20px;font-size:14px;" data-onclick="saveBranding()">Save Branding</button>
<span id="branding-save-feedback" style="color:var(--success);font-size:13px;display:none;">✓ Branding saved!</span>
</div>
</div>
</div>
</div>
<!-- ANALYTICS PANEL -->
<div id="panel-analytics" class="panel">
<div class="card">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;">
<h3 style="margin:0;">Form Performance</h3>
<a href="/sites/{{ site.id }}/analytics" class="btn btn-outline" style="font-size:12px;padding:6px 12px;">Full Analytics →</a>
</div>
<div class="stats-row">
<div class="stat-box">
<div class="stat-label">Total Submissions</div>
<div class="stat-value" id="analytics-total">—</div>
</div>
<div class="stat-box">
<div class="stat-label">Last 7 Days</div>
<div class="stat-value" id="analytics-7d">—</div>
</div>
<div class="stat-box">
<div class="stat-label">Avg/Day</div>
<div class="stat-value" id="analytics-avg">—</div>
</div>
</div>
<div style="margin-top:24px;padding:16px;background:var(--bg);border-radius:6px;text-align:center;color:var(--text-muted);font-size:13px;" id="analytics-loading">
Loading analytics...
</div>
<div id="analytics-chart" style="display:none;">
<div style="display:flex;gap:4px;align-items:flex-end;height:120px;padding-top:8px;" id="bar-chart"></div>
<div style="display:flex;gap:4px;" id="bar-labels"></div>
</div>
</div>
</div>
<!-- INTEGRATIONS PANEL -->
<div id="panel-integrations" class="panel">
<div class="int-header">
<div>
<h3>Integrations</h3>
<p>Connect your form to external services. Each submission will be automatically sent to all enabled destinations.</p>
</div>
<button class="btn" data-onclick="openIntModal()">+ Add Integration</button>
</div>
<div id="int-loading" class="empty-state">
<p>Loading integrations...</p>
</div>
<div id="int-empty" class="card empty-state" style="display:none;">
<p>No integrations configured yet. Add one to start forwarding form submissions.</p>
</div>
<div id="int-grid" class="int-grid" style="display:none;"></div>
<div class="card" style="margin-top:24px;">
<h3>Export Submissions</h3>
<p style="font-size:13px;color:var(--text-muted);margin-bottom:12px;">
Download all form submissions as CSV or JSON.
</p>
<div style="display:flex;gap:8px;flex-wrap:wrap;">
<a href="/api/export?token={{ site.token }}&format=csv" class="int-btn-sm primary" download>📄 Export CSV</a>
<a href="/api/export?token={{ site.token }}&format=json" class="int-btn-sm primary" download>📋 Export JSON</a>
</div>
</div>
</div>
</div>
<!-- DOCUMENTS PANEL -->
<div id="panel-documents" class="panel">
<div class="int-header">
<div>
<h3>Documents</h3>
<p>Generate invoices, quotes, receipts, and custom documents from form submissions.</p>
</div>
<button class="btn" data-onclick="openDocTemplateModal()">+ New Template</button>
</div>
<!-- DOCUMENT SUB-TABS -->
<div class="doc-subtabs" style="display:flex;gap:0;margin-bottom:20px;border-bottom:1px solid var(--border);">
<a class="doc-subtab active" data-onclick="showDocSection('templates')">Templates</a>
<a class="doc-subtab" data-onclick="showDocSection('generate')">Generate</a>
<a class="doc-subtab" data-onclick="showDocSection('history')">History</a>
<a class="doc-subtab" data-onclick="showDocSection('schedules')">Schedules</a>
<a class="doc-subtab" data-onclick="showDocSection('gallery')">Gallery</a>
</div>
<!-- TEMPLATES SECTION -->
<div id="doc-templates" class="doc-section">
<div id="doc-templates-loading" style="text-align:center;padding:40px;color:var(--text-dim);">Loading templates...</div>
<div id="doc-templates-empty" class="card empty-state" style="display:none;">
<p>No document templates yet. Create one to start generating invoices, quotes, or custom documents.</p>
</div>
<div id="doc-templates-grid" class="int-grid" style="display:none;"></div>
</div>
<!-- GENERATE SECTION -->
<div id="doc-generate" class="doc-section" style="display:none;">
<div class="card">
<div class="form-group">
<label for="doc-gen-template">Template</label>
<select id="doc-gen-template" data-onchange="onDocTemplateChange()">
<option value="">Select a template...</option>
</select>
</div>
<div class="form-group">
<label for="doc-gen-type">Document Type</label>
<select id="doc-gen-type">
<option value="invoice">Invoice</option>
<option value="quote">Quote</option>
<option value="receipt">Receipt</option>
</select>
</div>
<div class="form-group">
<label for="doc-gen-submission">From Submission (auto-fill)</label>
<select id="doc-gen-submission" data-onchange="onDocSubmissionChange()">
<option value="">Manual entry...</option>
</select>
<div class="form-hint">Select a past submission to auto-fill customer data.</div>
</div>
<div id="doc-gen-fields" style="display:grid;gap:12px;"></div>
<div style="margin-top:16px;display:flex;gap:8px;">
<button class="btn" data-onclick="generateDoc()">Generate & Download</button>
<button class="btn btn-outline" data-onclick="previewDoc()">Preview</button>
</div>
</div>
</div>
<!-- HISTORY SECTION -->
<div id="doc-history" class="doc-section" style="display:none;">
<div id="doc-history-loading" style="text-align:center;padding:40px;color:var(--text-dim);">Loading history...</div>
<div id="doc-history-empty" class="card empty-state" style="display:none;">
<p>No documents generated yet.</p>
</div>
<div id="doc-history-actions" style="display:none;margin-bottom:12px;display:flex;align-items:center;gap:12px;">
<button class="btn" data-onclick="bulkGenerateSelected()">📄 Generate Selected</button>
<span id="doc-history-progress" style="color:var(--text-muted);font-size:13px;"></span>
</div>
<div id="doc-history-list" style="display:none;"></div>
</div>
<!-- SCHEDULES SECTION -->
<div id="doc-schedules" class="doc-section" style="display:none;">
<div class="int-header">
<div>
<h4>Recurring Schedules</h4>
<p>Automatically generate documents on a recurring basis.</p>
</div>
<div style="display:flex;gap:8px;">
<button class="btn btn-outline" data-onclick="runDueSchedules()">▶ Run Due</button>
<button class="btn" data-onclick="openScheduleModal()">+ New Schedule</button>
</div>
</div>
<div id="doc-schedules-loading" style="text-align:center;padding:40px;color:var(--text-dim);">Loading schedules...</div>
<div id="doc-schedules-empty" class="card empty-state" style="display:none;">
<p>No recurring schedules set up yet.</p>
</div>
<div id="doc-schedules-list" style="display:none;"></div>
</div>
<!-- GALLERY SECTION -->
<div id="doc-gallery" class="doc-section" style="display:none;">
<div class="int-header">
<div>
<h4>Template Gallery</h4>
<p>Browse pre-built layouts to get started quickly.</p>
</div>
</div>
<div id="doc-gallery-loading" style="text-align:center;padding:40px;color:var(--text-dim);">Loading gallery...</div>
<div id="doc-gallery-empty" class="card empty-state" style="display:none;">
<p>Gallery is currently unavailable.</p>
</div>
<div id="doc-gallery-grid" class="gallery-grid" style="display:none;"></div>
</div>
<!-- TEMPLATE MODAL -->
<div class="modal-overlay" id="doc-template-modal">
<div class="modal-box" style="max-width:600px;">
<div class="modal-header">
<h3 id="doc-modal-title">New Template</h3>
<button class="modal-close" data-onclick="closeDocTemplateModal()">×</button>
</div>
<div class="modal-body">
<div class="form-group">
<label for="doc-tpl-name">Template Name</label>
<input type="text" id="doc-tpl-name" placeholder="e.g. Standard Invoice">
</div>
<div class="form-group">
<label for="doc-tpl-layout">Layout</label>
<select id="doc-tpl-layout">
<option value="classic">Classic</option>
<option value="modern">Modern</option>
<option value="receipt">Receipt</option>
</select>
</div>
<div class="form-group">
<label for="doc-tpl-from-name">From (Business Name)</label>
<input type="text" id="doc-tpl-from-name" placeholder="Your Business Name">
</div>
<div class="form-group">
<label for="doc-tpl-from-email">From (Email)</label>
<input type="email" id="doc-tpl-from-email" placeholder="you@business.com">
</div>
<div class="form-group">
<label for="doc-tpl-from-address">From (Address)</label>
<textarea id="doc-tpl-from-address" rows="2" placeholder="123 Main St City, State 12345"></textarea>
</div>
<div class="form-group">
<label for="doc-tpl-currency">Currency</label>
<select id="doc-tpl-currency">
<option value="$">$ (USD)</option>
<option value="€">€ (EUR)</option>
<option value="£">£ (GBP)</option>
</select>
</div>
<div class="form-group">
<label for="doc-tpl-tax-rate">Default Tax Rate (%)</label>
<input type="number" id="doc-tpl-tax-rate" value="0" min="0" max="100" step="0.1">
</div>
<div class="form-group">
<label for="doc-tpl-notes">Default Notes</label>
<textarea id="doc-tpl-notes" rows="2" placeholder="Payment terms, thank you message, etc."></textarea>
</div>
<div class="form-group">
<label for="doc-tpl-primary">Brand Color</label>
<input type="color" id="doc-tpl-primary" value="#2563eb" style="width:48px;height:36px;border:1px solid var(--border);border-radius:4px;cursor:pointer;">
</div>
<div class="form-group">
<label for="doc-tpl-logo">Logo URL</label>
<input type="url" id="doc-tpl-logo" placeholder="https://example.com/logo.png">
</div>
<div class="form-group">
<label for="doc-tpl-payment-link">Payment Link URL</label>
<input type="url" id="doc-tpl-payment-link" placeholder="https://payment.example.com/...">
<div class="form-hint">URL for a payment button included in generated documents.</div>
</div>
<div class="form-group">
<label for="doc-tpl-payment-link-text">Payment Link Button Text</label>
<input type="text" id="doc-tpl-payment-link-text" value="Pay Now" placeholder="Pay Now">
<div class="form-hint">Label shown on the payment button in generated documents.</div>
</div>
<div class="form-group">
<label for="doc-tpl-invoice-format">Invoice Number Format</label>
<input type="text" id="doc-tpl-invoice-format" value="INV-YYYY-####" placeholder="INV-YYYY-####">
<div class="form-hint">Format: <code>INV-YYYY-####</code>, <code>INV-YYYYMM-###</code>, <code>####</code>. Supports YYYY, YY, MM, #### (sequential).</div>
</div>
<div class="form-group">
<label for="doc-tpl-next-invoice">Next Invoice Number</label>
<input type="text" id="doc-tpl-next-invoice" placeholder="Auto-generated from format">
<div class="form-hint">Leave blank to auto-generate from the format above.</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-outline" data-onclick="closeDocTemplateModal()">Cancel</button>
<div class="form-group">
<label for="doc-tpl-auto-gen">
<div class="checkbox-group">
<input type="checkbox" id="doc-tpl-auto-gen">
<span style="font-size:14px;">Auto-generate on form submission</span>
</div>
</label>
<div class="form-hint">When enabled, this template generates a PDF and emails it to the customer automatically when someone submits the form.</div>
</div>
<button class="btn" id="doc-tpl-save-btn" data-onclick="saveDocTemplate()">Save Template</button>
</div>
</div>
</div>
<!-- SCHEDULE MODAL -->
<div class="modal-overlay" id="doc-schedule-modal">
<div class="modal-box" style="max-width:600px;">
<div class="modal-header">
<h3 id="schedule-modal-title">New Schedule</h3>
<button class="modal-close" data-onclick="closeScheduleModal()">×</button>
</div>
<div class="modal-body">
<input type="hidden" id="schedule-edit-id" value="">
<div class="form-group">
<label for="schedule-template">Template</label>
<select id="schedule-template">
<option value="">Select a template...</option>
</select>
</div>
<div class="form-group">
<label for="schedule-customer-name">Customer Name</label>
<input type="text" id="schedule-customer-name" placeholder="Customer name">
</div>
<div class="form-group">
<label for="schedule-customer-email">Customer Email</label>
<input type="email" id="schedule-customer-email" placeholder="customer@example.com">
</div>
<div class="form-group">
<label for="schedule-interval">Interval</label>
<select id="schedule-interval">
<option value="daily">Daily</option>
<option value="weekly">Weekly</option>
<option value="monthly" selected>Monthly</option>
<option value="yearly">Yearly</option>
</select>
</div>
<div class="form-group">
<label for="schedule-interval-count">Interval Count</label>
<input type="number" id="schedule-interval-count" value="1" min="1">
<div class="form-hint">e.g., every 2 weeks</div>
</div>
<div class="form-group">
<label for="schedule-start-date">Start Date</label>
<input type="date" id="schedule-start-date">
</div>
<div class="form-group">
<label for="schedule-doc-prefix">Document Number Prefix</label>
<input type="text" id="schedule-doc-prefix" value="INV" placeholder="INV">
</div>
<div class="form-group">
<label for="schedule-line-items">Line Items (JSON)</label>
<textarea id="schedule-line-items" rows="3" placeholder='[{"description":"Service","quantity":1,"unit_price":100}]'></textarea>
</div>
<div class="form-group">
<label>
<div class="checkbox-group">
<input type="checkbox" id="schedule-enabled" checked>
<span style="font-size:14px;">Enabled</span>
</div>
</label>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-outline" data-onclick="closeScheduleModal()">Cancel</button>
<button class="btn" data-onclick="saveSchedule()">Save Schedule</button>
</div>
</div>
</div>
</div>
<!-- VERSIONS PANEL -->
<div id="panel-versions" class="panel">
<div class="card">
<div class="int-header">
<div>
<h3>Version History</h3>
<p>Track changes to your form configuration. Click any version to compare or rollback.</p>
</div>
<button class="btn" data-onclick="createSnapshot()" id="snapshot-btn">+ Save Snapshot</button>
</div>
<div id="versions-loading" class="empty-state">
<p>Loading version history...</p>
</div>
<div id="versions-empty" class="card empty-state" style="display:none;">
<p>No version history yet. Snapshots are automatically saved when you change form fields, or you can save one manually.</p>
</div>
<div id="versions-timeline" class="versions-timeline" style="display:none;"></div>
</div>
</div>
<!-- INTEGRATION MODAL -->
<div class="modal-overlay" id="int-modal">
<div class="modal-box">
<div class="modal-header">
<h3 id="int-modal-title">Add Integration</h3>
<button class="modal-close" data-onclick="closeIntModal()">×</button>
</div>
<div class="modal-body">
<div class="form-group">
<label>Integration Type</label>
<div id="int-type-list"></div>
</div>
<div id="int-setup-guide" style="display:none;"></div>
<div class="form-group">
<label for="int-name">Name</label>
<input type="text" id="int-name" placeholder="My Integration">
</div>
<div class="form-group" id="int-url-group">
<label for="int-url">URL</label>
<input type="url" id="int-url" placeholder="https://...">
<div class="form-hint">The endpoint or API URL for this integration.</div>
</div>
<div id="int-config-fields"></div>
<div class="form-group" style="margin-top:16px;">
<div class="checkbox-group">
<input type="checkbox" id="int-enabled" checked>
<span style="font-size:14px;">Enabled</span>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-outline" data-onclick="closeIntModal()">Cancel</button>
<button class="btn" id="int-save-btn" data-onclick="saveIntegration()">Add Integration</button>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script src="/static/js/edit-site-inline.js"></script>
{% endblock %}