SARO Smart Link — Generator
Live Preview
QR for this customer's smart link
Copy HTML
Copy for WordPress
Download .html
"Copy for WordPress" adds a short instructions comment at the top — paste the result straight into a WordPress Custom HTML block or Elementor HTML widget.
Quick Tips
Fill in the form, pick a theme, and the preview updates live. Click Save Customer Record to add this customer to your Dashboard, where you can track trial/expiry status and export your full list to Excel.
`;
}
function buildBusiness(name, tagline, desc, loc, photoUrl, links){
const rows = links.map(l => `
${l.icon} ${esc(l.label)} › `).join('');
return `
${esc(name)}
Let's Connect
${avatarBlock(name, photoUrl, '', '#141414', '#D4AF37')}
${esc(name)||'Your Name'}
${tagline?`
${esc(tagline)}
`:''}
${desc?`
${esc(desc)}
`:''}
${loc?`
${esc(loc)}
`:''}
${rows}
Powered By Saro Smart Link
`;
}
function generatePageHtml(){
const name = val('bizName');
const tag = val('tagline');
const desc = val('desc');
const loc = val('location');
const photoUrl = val('photoUrl');
const links = buildLinks();
if(currentTheme === 'glassy') return buildGlassy(name, tag, desc, loc, photoUrl, links);
return buildBusiness(name, tag, desc, loc, photoUrl, links);
}
function render(){
const html = generatePageHtml();
document.getElementById('previewFrame').srcdoc = html;
const live = val('liveUrl');
const qrBox = document.getElementById('qrBox');
if(live){
document.getElementById('qrImg').src = 'https://api.qrserver.com/v1/create-qr-code/?size=140x140&data=' + encodeURIComponent(live);
qrBox.style.display = 'block';
} else {
qrBox.style.display = 'none';
}
return html;
}
['bizName','tagline','desc','location','photoUrl','phone','whatsapp','email','website','maps','instagram','facebook','youtube','liveUrl']
.forEach(id => document.getElementById(id).addEventListener('input', render));
function flashCopied(btn){
const old = btn.textContent;
btn.textContent = 'Copied ✓';
btn.classList.add('copied');
setTimeout(()=>{ btn.textContent = old; btn.classList.remove('copied'); }, 1400);
}
document.getElementById('copyHtml').addEventListener('click', (e)=>{ navigator.clipboard.writeText(generatePageHtml()); flashCopied(e.target); });
document.getElementById('copyWp').addEventListener('click', (e)=>{
const wpHtml = `\n` + generatePageHtml();
navigator.clipboard.writeText(wpHtml);
flashCopied(e.target);
});
document.getElementById('downloadHtml').addEventListener('click', ()=>{
const html = generatePageHtml();
const name = (val('bizName') || 'smart-link').toLowerCase().replace(/[^a-z0-9]+/g,'-').replace(/(^-|-$)/g,'');
const blob = new Blob([html], {type:'text/html'});
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = name + '.html';
a.click();
});
// ---------- plan dates default ----------
function todayIso(offsetDays){
const d = new Date();
d.setDate(d.getDate() + (offsetDays||0));
return d.toISOString().slice(0,10);
}
function setDefaultDates(){
setVal('planStart', todayIso(0));
setVal('planEnd', todayIso(7));
}
setDefaultDates();
document.getElementById('planStart').addEventListener('change', ()=>{
// keep a 7-day span if end hasn't been manually pushed further
if(!document.getElementById('planEnd').dataset.touched){
const start = new Date(val('planStart'));
start.setDate(start.getDate()+7);
setVal('planEnd', start.toISOString().slice(0,10));
}
});
document.getElementById('planEnd').addEventListener('change', ()=>{ document.getElementById('planEnd').dataset.touched = '1'; });
// ---------- save / clear / edit ----------
function clearForm(){
['bizName','tagline','desc','location','photoUrl','phone','whatsapp','email','website','maps','instagram','facebook','youtube','liveUrl']
.forEach(id => setVal(id, ''));
document.getElementById('manualLinks').innerHTML = '';
document.getElementById('planEnd').dataset.touched = '';
setDefaultDates();
editingId = null;
document.getElementById('saveBtn').textContent = 'Save Customer Record';
render();
}
document.getElementById('clearBtn').addEventListener('click', clearForm);
document.getElementById('saveBtn').addEventListener('click', ()=>{
const record = {
id: editingId || (Date.now().toString(36)),
name: val('bizName'), tagline: val('tagline'), desc: val('desc'), location: val('location'),
photoUrl: val('photoUrl'), phone: val('phone'), whatsapp: val('whatsapp'), email: val('email'),
website: val('website'), maps: val('maps'), instagram: val('instagram'), facebook: val('facebook'),
youtube: val('youtube'), manualLinks: getManualLinks(), theme: currentTheme,
planStart: val('planStart'), planEnd: val('planEnd'), liveUrl: val('liveUrl'),
createdAt: editingId ? (loadCustomers().find(c=>c.id===editingId)||{}).createdAt || new Date().toISOString() : new Date().toISOString()
};
let list = loadCustomers();
if(editingId){
list = list.map(c => c.id === editingId ? record : c);
} else {
list.push(record);
}
saveCustomers(list);
editingId = record.id;
document.getElementById('saveBtn').textContent = 'Update Customer Record';
renderDashboard();
});
function loadRecordIntoForm(rec){
setVal('bizName', rec.name); setVal('tagline', rec.tagline); setVal('desc', rec.desc);
setVal('location', rec.location); setVal('photoUrl', rec.photoUrl); setVal('phone', rec.phone);
setVal('whatsapp', rec.whatsapp); setVal('email', rec.email); setVal('website', rec.website);
setVal('maps', rec.maps); setVal('instagram', rec.instagram); setVal('facebook', rec.facebook);
setVal('youtube', rec.youtube); setVal('liveUrl', rec.liveUrl);
setVal('planStart', rec.planStart); setVal('planEnd', rec.planEnd);
document.getElementById('planEnd').dataset.touched = '1';
document.getElementById('manualLinks').innerHTML = '';
(rec.manualLinks||[]).forEach(m => addManualLinkRow(m));
currentTheme = rec.theme || 'glassy';
document.querySelectorAll('.theme-btn').forEach(b => b.classList.toggle('active', b.dataset.theme === currentTheme));
editingId = rec.id;
document.getElementById('saveBtn').textContent = 'Update Customer Record';
render();
}
// ---------- import from Google Sheet ----------
function mapSheetThemeToGenerator(sheetTheme){
// Trial form's "Selected Theme" radio already uses these exact labels.
return (sheetTheme === 'Premium') ? 'business' : 'glassy';
}
function sheetRowToRecord(row, existing){
const stamp = row['Timestamp'] ? new Date(row['Timestamp']).toISOString() : '';
return {
id: existing ? existing.id : 'sheet-' + (stamp || row['Business Name'] || Math.random().toString(36).slice(2)),
name: row['Business Name'] || row['Full Name'] || '',
tagline: existing ? existing.tagline : '',
desc: existing ? existing.desc : (row['Full Name'] ? ('Contact: ' + row['Full Name']) : ''),
location: row['Business Location'] || '',
photoUrl: row['Photo Link'] || (existing ? existing.photoUrl : ''),
phone: row['Contact Number'] || '',
whatsapp: row['Contact Number'] || '',
email: row['Email'] || '',
website: row['Website'] || '',
maps: existing ? existing.maps : '',
instagram: row['Instagram'] || '',
facebook: row['Facebook'] || '',
youtube: row['YouTube'] || '',
manualLinks: existing ? (existing.manualLinks || []) : [],
theme: mapSheetThemeToGenerator(row['Selected Theme']),
planStart: row['Trial Start'] || '',
planEnd: row['Trial End'] || '',
liveUrl: existing ? existing.liveUrl : '', // Sheet doesn't collect this — preserve if set locally
createdAt: existing ? existing.createdAt : (stamp || new Date().toISOString()),
source: 'sheet'
};
}
async function importFromSheet(){
const statusEl = document.getElementById('sheetSyncStatus');
if(!SHEET_WEBAPP_URL || SHEET_WEBAPP_URL.includes('PASTE_YOUR')){
if(statusEl) statusEl.textContent = 'Sheet URL not configured.';
return;
}
if(statusEl) statusEl.textContent = 'Syncing from Sheet…';
try {
const res = await fetch(SHEET_WEBAPP_URL);
const rows = await res.json();
if(!Array.isArray(rows)){
if(statusEl) statusEl.textContent = 'Sheet sync failed.';
return;
}
const list = loadCustomers();
const byId = {};
list.forEach(c => byId[c.id] = c);
rows.forEach(row => {
const stamp = row['Timestamp'] ? new Date(row['Timestamp']).toISOString() : '';
const id = 'sheet-' + (stamp || row['Business Name'] || '');
const existing = byId[id];
byId[id] = sheetRowToRecord(row, existing);
});
saveCustomers(Object.values(byId));
if(statusEl) statusEl.textContent = 'Synced ' + rows.length + ' record(s) from Sheet.';
} catch(err){
if(statusEl) statusEl.textContent = 'Sheet sync failed — check console.';
console.error('Sheet import error', err);
}
renderDashboard();
}
document.getElementById('refreshSheetBtn').addEventListener('click', importFromSheet);
// ---------- dashboard ----------
function computeStatus(rec){
const today = new Date(todayIso(0));
const end = rec.planEnd ? new Date(rec.planEnd) : null;
if(end && end < today) return 'expired';
if(rec.liveUrl) return 'live';
if(end){
const diffDays = Math.ceil((end - today) / (1000*60*60*24));
if(diffDays <= 2) return 'expiring';
}
return 'trial';
}
const STATUS_LABEL = { trial:'Trial Active', expiring:'Expiring Soon', live:'Live', expired:'Expired' };
function renderDashboard(){
const list = loadCustomers();
const search = (document.getElementById('searchBox').value || '').toLowerCase();
const statusFilter = document.getElementById('statusFilter').value;
const withStatus = list.map(r => ({ ...r, status: computeStatus(r) }));
document.getElementById('kpiTotal').textContent = withStatus.length;
document.getElementById('kpiTrial').textContent = withStatus.filter(r=>r.status==='trial').length;
document.getElementById('kpiExpiring').textContent = withStatus.filter(r=>r.status==='expiring').length;
document.getElementById('kpiLive').textContent = withStatus.filter(r=>r.status==='live').length;
const filtered = withStatus.filter(r=>{
const matchesSearch = !search || (r.name||'').toLowerCase().includes(search);
const matchesStatus = statusFilter === 'all' || r.status === statusFilter;
return matchesSearch && matchesStatus;
});
const wrap = document.getElementById('recordsList');
if(filtered.length === 0){
wrap.innerHTML = '
No customers match this filter yet.
';
return;
}
wrap.innerHTML = filtered.map(r => `
${esc(r.name)||'Untitled'}
${esc(r.tagline)||''}
Ends: ${r.planEnd||'—'}
${r.source === 'sheet' ? 'From Sheet ' : ''}
${STATUS_LABEL[r.status]}
Location ${esc(r.location)||'—'}
Theme ${r.theme}
Phone ${esc(r.phone)||'—'}
WhatsApp ${esc(r.whatsapp)||'—'}
Email ${esc(r.email)||'—'}
Website ${esc(r.website)||'—'}
Instagram ${esc(r.instagram)||'—'}
Facebook ${esc(r.facebook)||'—'}
YouTube ${esc(r.youtube)||'—'}
Extra Links ${(r.manualLinks||[]).map(m=>m.label).join(', ')||'—'}
Live URL ${esc(r.liveUrl)||'Not published'}
Start / End ${r.planStart||'—'} → ${r.planEnd||'—'}
Edit
Delete
`).join('');
wrap.querySelectorAll('.record').forEach(el=>{
const id = el.dataset.id;
el.querySelector('.edit').addEventListener('click', (e)=>{
e.preventDefault();
const rec = loadCustomers().find(c=>c.id===id);
if(rec){
loadRecordIntoForm(rec);
document.querySelector('.tab-btn[data-tab="create"]').click();
}
});
el.querySelector('.del').addEventListener('click', (e)=>{
e.preventDefault();
if(confirm('Delete this customer record?')){
saveCustomers(loadCustomers().filter(c=>c.id!==id));
renderDashboard();
}
});
});
}
document.getElementById('searchBox').addEventListener('input', renderDashboard);
document.getElementById('statusFilter').addEventListener('change', renderDashboard);
document.getElementById('exportExcelBtn').addEventListener('click', ()=>{
const list = loadCustomers().map(r => ({...r, status: computeStatus(r)}));
const search = (document.getElementById('searchBox').value || '').toLowerCase();
const statusFilter = document.getElementById('statusFilter').value;
const filtered = list.filter(r=>{
const matchesSearch = !search || (r.name||'').toLowerCase().includes(search);
const matchesStatus = statusFilter === 'all' || r.status === statusFilter;
return matchesSearch && matchesStatus;
});
const rows = filtered.map(r => ({
'Name': r.name, 'Tagline': r.tagline, 'Location': r.location,
'Phone': r.phone, 'WhatsApp': r.whatsapp, 'Email': r.email,
'Website': r.website, 'Instagram': r.instagram, 'Facebook': r.facebook, 'YouTube': r.youtube,
'Extra Links': (r.manualLinks||[]).map(m=>`${m.label}: ${m.url}`).join(' | '),
'Theme': r.theme, 'Start Date': r.planStart, 'End Date': r.planEnd,
'Live URL': r.liveUrl, 'Status': STATUS_LABEL[r.status], 'Created': r.createdAt
}));
const ws = XLSX.utils.json_to_sheet(rows);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, 'Customers');
XLSX.writeFile(wb, 'SARO_Smart_Link_Customers.xlsx');
});
render();
renderDashboard();
importFromSheet();