[object Object]

← back to Robet Site

auto-save: 2026-06-30T05:28:32 (5 files) — config.json public/app.js public/index.html server.js tests/api.test.mjs

0d21f3b31d1376d71b248866b9c55a028c3fda75 · 2026-06-30 05:28:36 -0700 · Steve Abrams

Files touched

Diff

commit 0d21f3b31d1376d71b248866b9c55a028c3fda75
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Jun 30 05:28:36 2026 -0700

    auto-save: 2026-06-30T05:28:32 (5 files) — config.json public/app.js public/index.html server.js tests/api.test.mjs
---
 config.json        |  5 ++---
 public/app.js      | 13 +++++++++++--
 public/index.html  |  5 ++---
 server.js          | 10 +++++-----
 tests/api.test.mjs |  7 ++++---
 5 files changed, 24 insertions(+), 16 deletions(-)

diff --git a/config.json b/config.json
index 4c97d1d..6567640 100644
--- a/config.json
+++ b/config.json
@@ -11,9 +11,8 @@
     "hours": "By appointment — pick a chair below"
   },
   "scheduling": {
-    "owner": "robert@agentabrams.com",
-    "embedLoader": "https://venturacorridor.com/embed/appointments.js",
-    "bookUrl": "https://venturacorridor.com/book?owner=robert@agentabrams.com"
+    "bookUrl": "",
+    "_note": "Robert's OWN booking link (Calendly / Cal.com / Square Appointments). Standalone — NOT wired to any DW/venturacorridor/dw_unified infra. Empty → the Book button routes to the contact form."
   },
   "social": []
 }
diff --git a/public/app.js b/public/app.js
index 7f591b3..9656c9a 100644
--- a/public/app.js
+++ b/public/app.js
@@ -51,10 +51,19 @@
     }
   });
 
-  // ── Smart Scheduling: link-first (reliable) — the live /book page does the real booking ──
+  // ── Booking: link to Robert's OWN scheduler (Calendly/Cal/Square). Until set → contact form. ──
   const sched = cfg.scheduling || {};
   const bookPrimary = $('#bookPrimary');
-  if (bookPrimary && sched.bookUrl) bookPrimary.href = sched.bookUrl;
+  if (bookPrimary) {
+    if (sched.bookUrl) {
+      bookPrimary.href = sched.bookUrl;
+    } else {
+      bookPrimary.href = '#contact';
+      bookPrimary.textContent = 'Get in touch to book →';
+      bookPrimary.removeAttribute('target');
+      bookPrimary.removeAttribute('rel');
+    }
+  }
 
   // ── gallery: render real photos from /api/photos, else tasteful placeholders ──
   (async () => {
diff --git a/public/index.html b/public/index.html
index 38647f9..d34c298 100644
--- a/public/index.html
+++ b/public/index.html
@@ -6,7 +6,6 @@
   <title>ROBERT · Barber</title>
   <meta name="description" content="Book a cut with Robert — barber. Pick a time, pick your cut, walk in sharp." />
   <link rel="canonical" href="https://robert.agentabrams.com/" />
-  <link rel="preconnect" href="https://venturacorridor.com" crossorigin />
   <meta name="theme-color" content="#0a0a0a" />
   <link rel="icon" href="/favicon.svg" type="image/svg+xml" />
   <link rel="apple-touch-icon" href="/apple-touch-icon.png" />
@@ -44,7 +43,7 @@
       "name": "Book a cut",
       "target": {
         "@type": "EntryPoint",
-        "urlTemplate": "https://venturacorridor.com/book?owner=robert@agentabrams.com",
+        "urlTemplate": "https://robert.agentabrams.com/#booking",
         "actionPlatform": [
           "https://schema.org/DesktopWebPlatform",
           "https://schema.org/MobileWebPlatform"
@@ -108,7 +107,7 @@ CUT.</h1>
       <span class="kicker">/ book a chair</span>
       <h2 class="sec-h">Pick a time.</h2>
       <p class="mono" style="font-size:18px; max-width:560px; margin-bottom:24px; font-weight:600;">
-        Live availability — pick a slot and it lands straight in the chair. Opens the booking page in a new tab.
+        Book your next cut — grab a slot online, or reach out and I'll fit you in.
       </p>
       <a class="cta" id="bookPrimary" href="#" target="_blank" rel="noopener noreferrer"
          style="font-size:28px; padding:20px 40px;">Book your cut →</a>
diff --git a/server.js b/server.js
index 12ec189..d64d190 100644
--- a/server.js
+++ b/server.js
@@ -2,7 +2,7 @@
 /*
  * robet-site — brutalist marketing site
  *   • config-driven brand/contact (config.json)
- *   • Smart Scheduling booking (embeds venturacorridor.com appointments loader)
+ *   • Booking via Robert's own scheduler link (standalone — no DW/venturacorridor infra)
  *   • /admin API-key entry page (basic-auth) → data/keys.json (gitignored)
  *
  * Zero external deps beyond express. A tiny inline .env loader avoids dotenv.
@@ -35,12 +35,12 @@ app.use(express.urlencoded({ extended: false }));
 // ── security headers (defense-in-depth; nginx also sets some on prod) ──
 const CSP = [
   "default-src 'self'",
-  "script-src 'self' https://venturacorridor.com",
+  "script-src 'self'",
   "style-src 'self' 'unsafe-inline'",            // inline style="" attrs on the hero/admin
-  "img-src 'self' data: https://venturacorridor.com",
+  "img-src 'self' data:",
   "font-src 'self'",
-  "connect-src 'self' https://venturacorridor.com",
-  "frame-src https://venturacorridor.com",        // Smart Scheduling booking iframe
+  "connect-src 'self'",
+  "frame-src 'none'",
   "object-src 'none'",
   "base-uri 'self'",
   "form-action 'self'",
diff --git a/tests/api.test.mjs b/tests/api.test.mjs
index d6d04d4..e10dec6 100644
--- a/tests/api.test.mjs
+++ b/tests/api.test.mjs
@@ -41,7 +41,7 @@ test('home page renders brand + OG meta', async () => {
 test('/api/config exposes brand + scheduling, no secrets', async () => {
   const j = await (await fetch(`${BASE}/api/config`)).json();
   assert.equal(j.brand, 'ROBERT');
-  assert.ok(j.scheduling?.owner, 'scheduling owner present');
+  assert.ok(j.scheduling && 'bookUrl' in j.scheduling, 'scheduling.bookUrl key present (standalone, no DW owner)');
   assert.equal(JSON.stringify(j).includes('ADMIN_PASS'), false);
 });
 
@@ -49,7 +49,8 @@ test('security headers present', async () => {
   const r = await fetch(`${BASE}/`);
   assert.ok(r.headers.get('content-security-policy'), 'CSP set');
   assert.equal(r.headers.get('x-content-type-options'), 'nosniff');
-  assert.match(r.headers.get('content-security-policy'), /venturacorridor\.com/);
+  assert.match(r.headers.get('content-security-policy'), /default-src 'self'/);
+  assert.match(r.headers.get('content-security-policy'), /frame-src 'none'/);   // de-entangled: no 3rd-party embeds
 });
 
 test('static SEO assets serve', async () => {
@@ -92,7 +93,7 @@ test('homepage embeds valid JSON-LD with a booking action', async () => {
   const j = JSON.parse(m[1]); // throws if invalid → test fails
   assert.equal(j['@type'], 'HairSalon');
   assert.equal(j.potentialAction['@type'], 'ScheduleAction');
-  assert.match(j.potentialAction.target.urlTemplate, /venturacorridor\.com\/book/);
+  assert.match(j.potentialAction.target.urlTemplate, /robert\.agentabrams\.com/);
 });
 
 test('malformed JSON body → clean JSON error, not HTML stack', async () => {

← a673808 robert barber v2: gallery (drop photos in public/photos, /ap  ·  back to Robet Site  ·  de-entangle friend build from DW infra: remove venturacorrid a7ab1ca →