Site Builder
Editing:
index.php
writable 0666
<?php /************************************************************************** * ADD BUSINESS – /ph/add.php (profile.json edition) * ----------------------------------------------------- * Saves social links in a canonicalised "channels" block identical to * edit‑business / edit‑social. * © 2025 BestDealOn – Free to modify. Requires PHP 8.1+ *************************************************************************/ declare(strict_types=1); /* ---------- redirect if no ?ph= supplied ---------------------------- */ if (empty($_GET['ph'])) { //header('Location: /members/dashboard.php'); exit; } $phRaw = preg_replace('/\D/', '', (string)$_GET['ph']); if (strlen($phRaw) !== 10) { //header('Location: /members/dashboard.php'); exit; } $initialPhone = $phRaw; // safe, 10‑digit string $phDir = $_SERVER['DOCUMENT_ROOT'] . '/ph/'; $templateFile = $_SERVER['DOCUMENT_ROOT'] . '/ph/profile-index.php'; $msg = ''; /* ---------- helper (same as other editors) ------------------------- */ function canonical_url(string $platform, string $val): string { $val = trim($val); if ($val === '') return ''; if (str_starts_with($val, 'http://') || str_starts_with($val, 'https://')) return $val; $slug = ltrim($val, '@/ '); return match ($platform) { 'twitter' => "https://twitter.com/$slug", 'tiktok' => "https://www.tiktok.com/@$slug", 'youtube' => "https://youtube.com/@$slug", 'rumble' => "https://rumble.com/user/$slug", 'odysee' => "https://odysee.com/@$slug", 'instagram' => "https://instagram.com/$slug", 'facebook' => "https://facebook.com/$slug", 'linkedin' => "https://linkedin.com/in/$slug", 'patreon' => "https://patreon.com/$slug", 'spotify' => "https://open.spotify.com/show/$slug", 'apple' => "https://podcasts.apple.com/podcast/$slug", default => $val }; } /* ---------- handle form post -------------------------------------- */ if ($_SERVER['REQUEST_METHOD'] === 'POST') { /* basic validation ---------------------------- */ if (empty($_POST['phone']) || empty($_POST['name']) || empty($_POST['email'])) { $msg = "❌ Phone, name, and email are required."; } elseif (empty($_POST['lat']) || empty($_POST['lon'])) { $msg = "❌ Please use “Locate Me” to fill latitude / longitude before submitting."; } else { /* sanitise phone + folder ---------------- */ $phone10 = substr(preg_replace('/\D/', '', $_POST['phone']), -10); $dir = $phDir . $phone10; if (is_dir($dir)) { $msg = "❌ That business already exists! <a href='/ph/$phone10/'>View page</a>"; } else { /* build profile payload --------------- */ /* —— channels block (video | podcast | social) */ $channels = [ 'video' => [ 'youtube' => canonical_url('youtube', $_POST['youtube'] ?? ''), 'rumble' => canonical_url('rumble', $_POST['rumble'] ?? ''), 'odysee' => canonical_url('odysee', $_POST['odysee'] ?? '') ], 'podcast' => [ 'spotify' => canonical_url('spotify', $_POST['spotify'] ?? ''), 'apple' => canonical_url('apple', $_POST['apple'] ?? ''), 'rss' => trim($_POST['podcast_rss'] ?? '') ], 'social' => [ 'twitter' => canonical_url('twitter', $_POST['twitter'] ?? ''), 'tiktok' => canonical_url('tiktok', $_POST['tiktok'] ?? ''), 'instagram' => canonical_url('instagram', $_POST['instagram'] ?? ''), 'facebook' => canonical_url('facebook', $_POST['facebook'] ?? ''), 'linkedin' => canonical_url('linkedin', $_POST['linkedin'] ?? ''), 'patreon' => canonical_url('patreon', $_POST['patreon'] ?? '') ] ]; $data = [ 'name' => trim($_POST['name']), 'phone' => $phone10, 'email' => trim($_POST['email']), 'slogan' => mb_substr(trim($_POST['slogan']), 0, 160), 'description' => mb_substr(trim($_POST['description']), 0, 240), 'website' => trim($_POST['website']), 'website_rss' => trim($_POST['website_rss']), 'address' => trim($_POST['address']), 'city' => trim($_POST['city']), 'state' => trim($_POST['state']), 'zip' => trim($_POST['zip']), 'lat' => trim($_POST['lat']), 'lon' => trim($_POST['lon']), 'tags' => array_filter(array_map('trim', explode(',', $_POST['tags'] ?? ''))), 'location_tags' => array_filter(array_map('trim', explode(',', $_POST['location_tags'] ?? ''))), 'channels' => $channels, 'premium' => 'off', 'created' => date(DATE_ATOM), 'updated' => date(DATE_ATOM) ]; /* write files & template ---------------- */ mkdir($dir, 0777, true); file_put_contents("$dir/new-business.json", json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); file_put_contents("$dir/profile.json", json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); if (is_readable($templateFile)) copy($templateFile, "$dir/index.php"); $msg = "✅ Business added! <a href='/ph/$phone10/'>View your business page</a>"; } } } /* ===== the rest of the file (HTML + JS) is exactly as before ===== */ ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Add Your Business | BestDealOn</title> <meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="robots" content="noindex,follow"> <link rel="icon" href="/bestdealon.svg" type="image/svg+xml"> <style> body {background:#f5f8fb;font-family:system-ui,Arial,sans-serif;margin:0;color:#234;} h1 {text-align:center;color:#2257a2;} .top-bar{background:#eee;padding:.8em 1.2em;font-weight:900;} .wrap {max-width:520px;margin:2.7em auto;background:#fff;padding:2em 2em 2em 2em;border-radius:17px;box-shadow:0 2px 18px #dde3fa33;} .cta {background:#ecfbf1;border-radius:10px;padding:1em 1.1em;margin-bottom:2em;font-size:1.08em;color:#175e35;text-align:center;} input[type=text],input[type=tel],input[type=email],textarea,select { width:100%;padding:.7em;border-radius:7px;border:1.4px solid #b7c2df;margin-bottom:1.13em;font-size:1.08em;} input[type=submit],button {padding:.7em 1.4em;font-size:1.1em;font-weight:700;background:#2357d7;color:#fff;border:none;border-radius:8px;cursor:pointer;transition:background .16s;} input[type=submit]:hover:enabled, button:hover:enabled {background:#0a3798;} input[type=submit]:disabled, button:disabled {background:#8ba0c5!important;color:#dbe7fa!important;cursor:not-allowed!important;opacity:.72;} .lab {font-weight:600;margin-bottom:.2em;display:block;} .hint {font-size:.96em;color:#666;margin:-1.07em 0 1em .2em;} .msg {text-align:center;margin-bottom:1.4em;font-size:1.07em;} .location-tag {display:inline-block;background:#e6f0fa;color:#17468d;border-radius:14px;padding:.22em .85em;margin:.08em .2em .18em 0;font-size:.99em;position:relative;} .location-tag .del {cursor:pointer;color:#c63c27;font-size:1.13em;margin-left:.22em;} .location-tag .del:hover {color:#d80d19;} #addTag {margin-bottom:1.1em;} @media (max-width:600px){.wrap{padding:1em .1em 1em .1em;max-width:99vw;}} /* ========== NEW: lock & style city/state once set ========== */ .locked-input{background:#e9ecef!important;color:#666!important;} /* ========== NEW: collapsible social links ========== */ .social-toggle{cursor:pointer;display:flex;align-items:center;gap:.35em;font-weight:600;margin:1.4em 0 .4em;} .social-toggle .arrow{transition:transform .2s;} #socialSection.collapsed{display:none;} /* ========== NEW: taller description box ========== */ textarea#description{min-height:6.5em;} /* (~2 extra lines) */ /* ---------- global field padding on narrow screens ---------- */ @media (max-width: 600px){ /* tweak breakpoint if desired */ #business-form{ /* use your existing id */ padding-left: 1rem; padding-right: 1rem; } } /* ---------- address layout (works for ALL widths) ---------- */ #business-form .address-row{ flex-wrap: wrap; /* allow children to wrap */ } /* ZIP (third child) always starts a new row */ #business-form .address-row > div:nth-child(3){ flex: 1 0 100% !important; /* full‑width row */ margin-top: 0.6rem; /* little vertical spacing */ } /* Stretch inputs so they fill their flex‑box neatly */ #business-form input, #business-form textarea, #business-form select{ width: 100%; box-sizing: border-box; /* includes padding in width */ }</style> <script> let userLat = '', userLon = '', userState = '', userCity = '', userCitiesList = []; let tags = []; let phoneAvailable = false; /* ---------- NEW: lock city & state ---------- */ function lockCityState(){ ['city','state'].forEach(id=>{ const el=document.getElementById(id); el.readOnly=true; el.classList.add('locked-input'); }); } /* ---------- NEW: social‑section toggle ---------- */ function initSocialToggle(){ const tog=document.getElementById('socialToggle'); if(!tog) return; const sect=document.getElementById('socialSection'); const arr=tog.querySelector('.arrow'); tog.addEventListener('click',()=>{ const shut=sect.classList.toggle('collapsed'); arr.style.transform=shut?'rotate(-90deg)':'rotate(0deg)'; }); } /* Prefill phone from ?ph= on load */ window.addEventListener('DOMContentLoaded', function() { let urlPh = (new URL(window.location)).searchParams.get('ph'); if (urlPh && urlPh.match(/^\d{10}$/)) { let nums = urlPh.replace(/\D/g,'').slice(-10); let formatted = nums; if(nums.length === 10) formatted = `(${nums.substr(0,3)})-${nums.substr(3,3)}-${nums.substr(6,4)}`; document.getElementById('phone').value = formatted; document.getElementById('business-id').textContent = nums; } document.getElementById('business-form').style.display = 'none'; document.getElementById('locateBtn').addEventListener('click', function() { locateMe(); }); document.getElementById('addTag').addEventListener('keydown', function(e){ if(e.key === "Enter" || e.key === "," ) { e.preventDefault(); addTagsFromInput(); } if(e.key==='Backspace' && this.value===''){ if(tags.length){ tags.pop(); renderChips(); checkFormReady(); } } }); document.getElementById('addTag').addEventListener('blur', function(){ addTagsFromInput(); }); document.getElementById('phone').addEventListener('input', function() { let nums = this.value.replace(/\D/g,'').slice(0,10); let out = nums; if(nums.length > 6) out = `(${nums.substr(0,3)})-${nums.substr(3,3)}-${nums.substr(6,4)}`; else if(nums.length > 3) out = `(${nums.substr(0,3)})-${nums.substr(3)}`; this.value = out; document.getElementById('business-id').textContent = nums; if(nums.length === 10) { fetch('/ph/check.php?phone='+nums).then(r=>r.json()).then(data=>{ if (data.exists) { document.getElementById('phone-status').innerHTML = '<span style="color:#d92c2c;font-weight:700;">❌ Already exists</span>'; phoneAvailable = false; document.getElementById('addBtn').disabled = true; } else { document.getElementById('phone-status').innerHTML = '<span style="color:#20b934;font-weight:700;">✅ Available to claim!</span>'; phoneAvailable = true; checkFormReady(); } }); } else { document.getElementById('phone-status').innerHTML = ''; phoneAvailable = false; document.getElementById('addBtn').disabled = true; } }); document.getElementById('city').addEventListener('input', updateLatLonFromCityState); document.getElementById('state').addEventListener('input', updateLatLonFromCityState); Array.from(document.querySelectorAll('#business-form input, #business-form textarea')).forEach(i=>{ i.addEventListener('input', checkFormReady); }); /* NEW: initialise collapsible socials */ initSocialToggle(); }); /* After location is found and the form is shown, trigger the phone availability check if prefilled */ function locateMe() { if (!navigator.geolocation) { alert("Geolocation not supported."); return; } document.getElementById('geo-status').textContent = "Locating..."; navigator.geolocation.getCurrentPosition(function(pos) { userLat = pos.coords.latitude; userLon = pos.coords.longitude; document.getElementById('lat').value = userLat; document.getElementById('lon').value = userLon; fetch('/geo/json-data/states-bounds.json').then(r=>r.json()).then(bounds=>{ let st = ''; bounds.forEach(function(b){ if (userLat >= b.minLat && userLat <= b.maxLat && userLon >= b.minLon && userLon <= b.maxLon) st=b.state; }); if(st) { userState = st; document.getElementById('state').value = st; fetch('/geo/json-data/'+st+'.json').then(r=>r.json()).then(cities=>{ let nearest = null, bestDist = 1e9; userCitiesList = cities; cities.forEach(c=>{ let d = Math.hypot(userLat-c.lat, userLon-c.lon); if (d < bestDist) { bestDist = d; nearest = c; } }); if (nearest) { userCity = nearest.city; document.getElementById('city').value = nearest.city; document.getElementById('lat').value = nearest.lat; document.getElementById('lon').value = nearest.lon; document.getElementById('geo-status').innerHTML = "Location found: <b>" + nearest.city + ", " + st + "</b>."; lockCityState(); /* NEW: lock fields now */ let dists = cities.map(c=>({city:c.city, d:Math.hypot(userLat-c.lat,userLon-c.lon)})); dists.sort((a,b)=>a.d-b.d); let preTags = [nearest.city, st]; for (let i=1; i<7 && i<dists.length; ++i) preTags.push(dists[i].city); setTags(preTags); document.getElementById('business-form').style.display = ''; checkFormReady(); let phoneInput = document.getElementById('phone'); if (phoneInput.value.replace(/\D/g,'').length === 10) { phoneInput.dispatchEvent(new Event('input', { bubbles: true })); } } }); } else { document.getElementById('geo-status').textContent = "Not in USA/States."; } }); }, function(){ document.getElementById('geo-status').textContent = "Failed."; }); } function setTags(arr) { tags = [...new Set(arr)]; renderChips(); } function renderChips() { const wrap = document.getElementById('location-tags'); wrap.innerHTML = ''; tags.forEach((tag, idx) => { let chip = document.createElement('span'); chip.className = 'location-tag'; chip.dataset.val = tag; chip.textContent = tag; let btn = document.createElement('span'); btn.className = 'del'; btn.innerHTML = '×'; btn.onclick = () => { tags.splice(idx, 1); renderChips(); checkFormReady(); }; chip.appendChild(btn); wrap.appendChild(chip); }); document.getElementById('location_tags').value = tags.join(', '); checkFormReady(); } function addTagsFromInput() { const input = document.getElementById('addTag'); let newTags = input.value.split(',').map(s=>s.trim()).filter(Boolean); let changed = false; newTags.forEach(tag=>{ if(tag && !tags.includes(tag)){ tags.push(tag); changed=true; } }); if (changed) renderChips(); input.value = ''; checkFormReady(); } function updateLatLonFromCityState() { const state = document.getElementById('state').value.trim().toUpperCase(); const city = document.getElementById('city').value.trim(); if (state && city) { fetch('/geo/json-data/' + state + '.json') .then(r => r.json()) .then(cities => { const match = cities.find(c => c.city.toLowerCase() === city.toLowerCase()); if (match) { document.getElementById('lat').value = match.lat; document.getElementById('lon').value = match.lon; } }); } } function checkFormReady() { let phoneOk = phoneAvailable; let ready = ( phoneOk && document.getElementById('phone').value.replace(/\D/g,'').length === 10 && document.getElementById('email').value.trim().length > 0 && document.getElementById('email').checkValidity() && document.getElementById('name').value.trim() && document.getElementById('description').value.trim() && document.getElementById('address').value.trim() && document.getElementById('city').value.trim() && document.getElementById('state').value.trim() && document.getElementById('zip').value.trim() && tags.length >= 2 && document.getElementById('phone-status').innerHTML.indexOf('Available')!==-1 ); document.getElementById('addBtn').disabled = !ready; } </script> </head> <body> <div class="top-bar"><a href="/" style="text-decoration:none;color:#2a3ca5">BestDealOn</a> » Add Your Business</div> <div class="wrap"> <h1>Add Your Business</h1> <div class="cta"> <div id="step1-text"> Step 1: Click “Locate Me” to begin </div> <div id="locate-container"> <button id="locateBtn" onclick="locateMe()">Locate Me</button> </div> <span id="geo-status" style="margin-left:.8em;color:#1067a8;"></span> </div> <?php if($msg): ?><div class="msg" style="color:<?=strpos($msg,'✅')!==false?'green':'red'?>"><?= $msg ?></div><?php endif; ?> <form id="business-form" method="post" autocomplete="off" style="display:none;"> <input type="hidden" id="lat" name="lat"> <input type="hidden" id="lon" name="lon"> <label class="lab" for="phone">Business Phone (10 digits)</label> <input type="text" id="phone" name="phone" maxlength="16" placeholder="(727)-610-1111" required autocomplete="off"> <div style="margin-bottom:.6em;"> <span>Business ID: <span id="business-id" style="font-family:monospace;font-size:1.04em;"></span></span> <span id="phone-status" style="margin-left:.8em;"></span> </div> <label class="lab" for="email">Business Email</label> <input type="email" id="email" name="email" maxlength="120" required placeholder="you@email.com" autocomplete="off"> <label class="lab" for="name">Business Name</label> <input type="text" id="name" name="name" maxlength="80" required> <label class="lab" for="slogan">Slogan <span class="hint">(max 160 characters, optional)</span></label> <input type="text" id="slogan" name="slogan" maxlength="160"> <!-- DESCRIPTION: taller but same width --> <label class="lab" for="description">Description <span class="hint">(max 240 characters)</span></label> <textarea id="description" name="description" maxlength="240" rows="5" required></textarea> <label class="lab" for="address">Street Address</label> <input type="text" id="address" name="address" maxlength="120" required> <!-- City / State / ZIP block --> <div class="address-row" style="display:flex; gap:1.1em;"> <div style="flex:2"> <label class="lab" for="city">City</label> <input type="text" id="city" name="city" maxlength="40" required> </div> <div style="flex:1.1"> <label class="lab" for="state">State (2‑letter)</label> <input type="text" id="state" name="state" maxlength="2" required> </div> <div style="flex:1.2"> <label class="lab" for="zip">ZIP Code</label> <input type="text" id="zip" name="zip" maxlength="12" required> </div> </div> <label class="lab" for="website">Website (optional)</label> <input type="text" id="website" name="website" maxlength="120"> <hr style="margin:1.7em 0 .9em 0;"> <!-- COLLAPSIBLE SOCIAL LINKS --> <div id="socialToggle" class="social-toggle"><span class="arrow">▸</span> Add social links</div> <div id="socialSection" class="collapsed"> <label class="lab" for="twitter">Twitter/X (URL or handle)</label> <input type="text" id="twitter" name="twitter" maxlength="120" placeholder="e.g. https://twitter.com/username or @username"> <label class="lab" for="facebook">Facebook (URL or handle)</label> <input type="text" id="facebook" name="facebook" maxlength="120" placeholder="e.g. https://facebook.com/username"> <label class="lab" for="instagram">Instagram (URL or handle)</label> <input type="text" id="instagram" name="instagram" maxlength="120" placeholder="e.g. https://instagram.com/username or @username"> <label class="lab" for="tiktok">TikTok (URL or handle)</label> <input type="text" id="tiktok" name="tiktok" maxlength="120" placeholder="e.g. https://tiktok.com/@username or @username"> <label class="lab" for="linkedin">LinkedIn (URL or handle)</label> <input type="text" id="linkedin" name="linkedin" maxlength="120" placeholder="e.g. https://linkedin.com/in/username"> <label class="lab" for="youtube">YouTube (URL or channel)</label> <input type="text" id="youtube" name="youtube" maxlength="120" placeholder="e.g. https://youtube.com/@username"> </div> <hr style="margin:1.7em 0 .9em 0;"> <label class="lab" for="tags">Business Tags (comma separated)</label> <input type="text" id="tags" name="tags" maxlength="120" placeholder="e.g. Restaurant, Pizza, Delivery"> <label class="lab" for="location-tags">Location Tags</label> <div id="location-tags" style="margin-bottom:.5em;"></div> <input type="hidden" id="location_tags" name="location_tags"> <input type="text" id="addTag" placeholder="Add more tags (comma separated)" autocomplete="off"> <input type="submit" id="addBtn" value="Add My Business 🚀" disabled> </form> </div> <script> (function(){ if (typeof window.locateMe !== 'function') return; let inProgress = false; const originalLocateMe = window.locateMe; window.locateMe = function() { if (inProgress) return; inProgress = true; const btn = document.getElementById('locateBtn'); if (btn) { btn.disabled = true; btn.textContent = '⌛ Locating…'; } // call your existing locateMe const ret = originalLocateMe.apply(this, arguments); // poll until lat shows up const poll = setInterval(() => { const latField = document.querySelector('input[name="lat"], #lat'); if (latField && latField.value) { // hide the button if (btn) btn.style.display = 'none'; // swap out the Step 1 copy for Step 2 const step1 = document.getElementById('step1-text'); if (step1) { step1.textContent = 'Step 2: Fill out the form below.'; } clearInterval(poll); } }, 300); return ret; }; })(); </script> </body> </html>
Save changes
Create folder
writable 0777
Create
Cancel