/* global React, Icon, StatusPill, Avatar, fmtAED */
const { useState } = React;
/* ============================================================
CONTRACTS / PAYMENTS / SETTINGS / CMD-K / BOOKING-PUBLIC
============================================================ */
function Contracts({ goto }) {
return (
| Title | Counterparty |
Value | Status |
Sent | |
{window.CONTRACTS.map(c => {
const deal = window.DEALS.find(d => d.id === c.dealId);
const counterparty = deal ? deal.company : '—';
const value = deal ? deal.value : 0;
return (
goto('contract-editor', c.id)} style={{ borderBottom: '1px solid var(--divider)', cursor: 'pointer' }}>
{c.name} |
{counterparty} |
AED {fmtAED(value)} |
|
{c.sent || '—'} |
|
);
})}
);
}
const th = { padding: '10px 20px', textAlign: 'left', fontSize: 9, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.08em', color: 'var(--text-dim)' };
const td = { padding: '12px 20px', verticalAlign: 'middle', color: 'var(--text-secondary)' };
function ContractEditor({ contractId, goto }) {
const c = window.CONTRACTS.find(x => x.id === contractId) || window.CONTRACTS[0];
const deal = window.DEALS.find(d => d.id === c.dealId) || {};
const counterparty = deal.company || '—';
const value = deal.value || 0;
return (
{c.name}
Linea Group · Services Agreement
{c.name}
This Agreement is entered into between Linea Group FZE ("Provider") and {c.counterparty} ("Client"), effective from the Acceptance Date below.
1. Scope of Services
Provider will deliver funnel design, paid-acquisition setup and email infrastructure as described in the attached Statement of Work.
2. Fees
The total fee for the engagement is AED {fmtAED(value)}, invoiced according to the schedule in Schedule A.
3. Term & Termination
Either party may terminate with thirty (30) days written notice. Pre-paid fees are non-refundable.
{['Provider · Linea Group', 'Client · ' + counterparty].map((label, i) => (
{i === 0 && c.status !== 'draft' && S. Mariotti}
{label}
))}
Variables
Signers
{[
{ name: 'Sebastiano Mariotti', email: 'sebastiano@lineagroup.ae', role: 'Provider', signed: c.status !== 'draft' },
{ name: 'Counterparty signer', email: 'tbd@example.com', role: 'Client', signed: c.status === 'signed' },
].map(s => (
))}
);
}
function Field({ label, value }) {
return (
);
}
/* ============================================================
PAYMENTS
============================================================ */
function Payments({ goto }) {
const total = window.PAYMENTS.filter(p => p.status === 'paid').reduce((a, p) => a + p.amount, 0);
const outstanding = window.PAYMENTS.filter(p => p.status !== 'paid').reduce((a, p) => a + p.amount, 0);
return (
| Invoice | Deal |
Amount | Paid |
Status | Date |
{window.PAYMENTS.map((p, i) => (
| INV-{String(2026000 + i + 1)} |
{p.deal} |
AED {fmtAED(p.amount)} |
AED {fmtAED(p.paid)} |
|
{p.paidDate || '—'} |
))}
);
}
function SummaryStat({ label, value, accent }) {
return (
);
}
/* ============================================================
SETTINGS
============================================================ */
function Settings() {
const [tab, setTab] = useState('channels');
return (
{['channels','templates','branding','team','billing'].map(t => (
setTab(t)} style={{ textTransform: 'capitalize' }}>{t}
))}
{tab === 'channels' && }
{tab === 'templates' && }
{tab === 'branding' && }
{tab === 'team' && }
{tab === 'billing' && }
);
}
function ChannelsSettings() {
const [channels, setChannels] = React.useState(null);
const [error, setError] = React.useState(null);
React.useEffect(() => {
fetch('/api/channels', { credentials: 'include' })
.then((r) => r.ok ? r.json() : Promise.reject(r.statusText))
.then(setChannels)
.catch((e) => setError(String(e)));
}, []);
const TYPE_TO_KIND = {
LINKEDIN: 'linkedin',
WHATSAPP: 'whatsapp',
INSTAGRAM: 'instagram',
GOOGLE_OAUTH: 'email',
GOOGLE: 'email',
OUTLOOK: 'email',
IMAP: 'email',
};
const iconMap = { linkedin: 'linkedinIcon', whatsapp: 'whatsappIcon', instagram: 'instagramIcon', email: 'emailIcon' };
const colorMap = { linkedin: '#0a66c2', whatsapp: '#25d366', instagram: '#e4405f', email: '#a3a3a3' };
return (
Connected channels
Inboxes, social DMs and email that flow into your unified inbox via Unipile.
{channels === null && !error && (
Loading…
)}
{error && (
Failed to load channels: {error}
)}
{channels && channels.length === 0 && (
No channels connected yet. Add one in Unipile dashboard.
)}
{channels && channels.map((ch) => {
const kind = TYPE_TO_KIND[ch.type] || 'email';
const status = ch.status || 'active';
const statusColor = status === 'active' || status === 'OK' ? 'var(--green)' : status === 'paused' ? 'var(--text-dim)' : 'var(--text-secondary)';
return (
{ch.name}
{kind} · Unipile id {ch.unipile_account_id}
{status}
);
})}
);
}
function TemplatesSettings() {
const tmpls = [
{ name: 'Cold intro · DTC', uses: 142, last: '2d ago' },
{ name: 'Cold intro · SaaS', uses: 87, last: '5d ago' },
{ name: 'Bump · 3-day', uses: 211, last: '1d ago' },
{ name: 'Break-up · 7-day', uses: 167, last: '3d ago' },
{ name: 'Recap · post-discovery', uses: 64, last: '6d ago' },
{ name: 'Proposal sent · nudge', uses: 42, last: '4d ago' },
];
return (
Email templates
Reusable snippets, available across inbox + sequences.
{tmpls.map(t => (
{t.name}
{t.uses} uses · last {t.last}
))}
);
}
function BrandingSettings() {
return (
Workspace brand
Used on funnels, booking pages, documents and contracts.
Primary color
{['#22c55e','#0a0a0a','#f97316','#3b82f6','#a855f7'].map(c => (
))}
Display font
Email authentication
{[
{ label: 'SPF', status: 'pass' },
{ label: 'DKIM', status: 'pass' },
{ label: 'DMARC', status: 'pass' },
{ label: 'Custom tracking domain', status: 'fail' },
].map(r => (
{r.label}
{r.status}
))}
);
}
function TeamSettings() {
const team = [
{ name: 'Sebastiano Mariotti', email: 'sebastiano@lineagroup.ae', role: 'Owner', initials: 'SM', color: '#22c55e' },
{ name: 'Marco Russo', email: 'marco@lineagroup.ae', role: 'Admin', initials: 'MR', color: '#3b82f6' },
{ name: 'Layla Saeed', email: 'layla@lineagroup.ae', role: 'Closer', initials: 'LS', color: '#f97316' },
];
return (
Team
{team.map(m => (
))}
);
}
function BillingSettings() {
return (
Plan
Suite · Annual
3 seats · renews Apr 28, 2027
AED 14,400 /yr
);
}
/* ============================================================
CMD-K palette
============================================================ */
function CmdK({ open, onClose, goto }) {
const [q, setQ] = useState('');
const inputRef = React.useRef(null);
React.useEffect(() => {
if (open && inputRef.current) inputRef.current.focus();
}, [open]);
if (!open) return null;
const all = [
{ kind: 'nav', label: 'Go to Dashboard', icon: 'dashboard', action: () => goto('dashboard') },
{ kind: 'nav', label: 'Go to Inbox', icon: 'inbox', action: () => goto('inbox') },
{ kind: 'nav', label: 'Go to Pipeline', icon: 'pipeline', action: () => goto('pipeline') },
{ kind: 'nav', label: 'Go to Calendar', icon: 'calendar', action: () => goto('calendar') },
{ kind: 'nav', label: 'Go to Funnels', icon: 'funnels', action: () => goto('funnels') },
{ kind: 'nav', label: 'Go to Campaigns', icon: 'send', action: () => goto('campaigns') },
{ kind: 'nav', label: 'Go to LinkedIn Campaign', icon: 'linkedinIcon', action: () => goto('linkedin-campaigns') },
{ kind: 'nav', label: 'Go to Documents', icon: 'document', action: () => goto('documents') },
{ kind: 'nav', label: 'Go to Contracts', icon: 'contract', action: () => goto('contracts') },
{ kind: 'nav', label: 'Go to Payments', icon: 'payments', action: () => goto('payments') },
{ kind: 'nav', label: 'Go to Settings', icon: 'settings', action: () => goto('settings') },
{ kind: 'create', label: 'New contact', icon: 'userPlus' },
{ kind: 'create', label: 'New deal', icon: 'plus' },
{ kind: 'create', label: 'New email campaign', icon: 'send', action: () => goto('campaigns') },
{ kind: 'create', label: 'New LinkedIn campaign', icon: 'linkedinIcon', action: () => goto('linkedin-builder', 'li4') },
{ kind: 'create', label: 'New funnel', icon: 'funnels', action: () => goto('funnel-builder', 'f1') },
{ kind: 'create', label: 'New document', icon: 'document', action: () => goto('document-builder', 'doc1') },
{ kind: 'create', label: 'New contract', icon: 'contract', action: () => goto('contract-editor', 'ct1') },
...window.CONTACTS.slice(0, 20).map(c => ({ kind: 'contact', label: c.name, sub: c.title + ' · ' + c.company, icon: 'user', action: () => goto('contact', c.id) })),
...window.DEALS.map(d => ({ kind: 'deal', label: d.name, sub: 'AED ' + fmtAED(d.value) + ' · ' + d.stage, icon: 'pipeline', action: () => goto('contact', d.contactId) })),
];
const filtered = q
? all.filter(x => x.label.toLowerCase().includes(q.toLowerCase()) || (x.sub || '').toLowerCase().includes(q.toLowerCase()))
: all.slice(0, 14);
const groups = {};
filtered.forEach(it => { groups[it.kind] = groups[it.kind] || []; groups[it.kind].push(it); });
const groupLabels = { nav: 'Navigate', create: 'Create new', contact: 'Contacts', deal: 'Deals' };
return (
<>
setQ(e.target.value)} />
esc
{Object.keys(groups).map(g => (
{groupLabels[g] || g}
{groups[g].map((it, i) => (
{ it.action && it.action(); onClose(); }} style={{
padding: '8px 10px', display: 'flex', alignItems: 'center', gap: 10, cursor: 'pointer',
}}
onMouseEnter={e => e.currentTarget.style.background = 'var(--raised)'}
onMouseLeave={e => e.currentTarget.style.background = 'transparent'}>
{it.label}
{it.sub &&
{it.sub}
}
))}
))}
{filtered.length === 0 &&
No results for "{q}"
}
↑↓ navigate
↵ select
⌘K close
>
);
}
/* ============================================================
PUBLIC BOOKING PAGE (shown via "Public Booking Page" route)
============================================================ */
function BookingPublic({ goto }) {
const [date, setDate] = useState(28);
const [slot, setSlot] = useState(null);
const [confirmed, setConfirmed] = useState(false);
const slots = ['09:00','09:30','10:00','11:00','14:00','14:30','15:00','16:00','16:30'];
const days = Array.from({ length: 14 }, (_, i) => 28 + i).map(d => d > 30 ? d - 30 : d);
return (
Linea Group · Booking
{!confirmed ? (
Sebastiano Mariotti
30-min Discovery Call
30 minutes
Zoom (auto-generated)
Asia/Dubai (GMT+4)
A no-pressure intro. We'll look at your funnel together, identify what's leaking, and decide if it makes sense to keep talking.
Pick a date · April 2026
{days.map((d, i) => (
setDate(d)} style={{
aspectRatio: '1', display: 'flex', alignItems: 'center', justifyContent: 'center',
border: '1px solid', borderColor: date === d ? 'var(--accent)' : 'var(--divider)',
background: date === d ? 'var(--accent-bg)' : 'var(--surface)',
color: date === d ? 'var(--accent)' : 'var(--text-secondary)',
cursor: 'pointer', fontSize: 13, fontWeight: 500,
}} className="mono">{d}
))}
Pick a time
{slots.map(s => (
))}
{slot && (
)}
) : (
You're booked.
{date} April · {slot} (Asia/Dubai). Calendar invite + Zoom link sent to your inbox.
)}
);
}
Object.assign(window, {
Contracts, ContractEditor, Payments, Settings, CmdK, BookingPublic,
});