From b3b831e4a0ab82db340042d87f1709202184b062 Mon Sep 17 00:00:00 2001 From: Myron Blair Date: Fri, 22 May 2026 14:01:42 +0000 Subject: [PATCH] Add per-customer booking flow checklist + fix admin login MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Admin portal overhaul: - Fix require_once path (was admin/db.php, should be ../db.php) — this was the root cause of the login always redirecting back to the login page - Fix session save path to /home/parkerslingshotrentals.com/sessions so the web user (parke1909) can actually read sessions back (the system default /var/lib/php/sessions was write-only for non-root) - Fix AJAX unauthenticated response: return 401 JSON instead of login HTML - Fresh bcrypt hash for admin password (Parker2026!) - Add 3 new DB columns: insurance_verified, deposit_received, license_verified - Replace flat bookings table with expandable per-customer flow panel: click any row to open a 3-column detail drawer showing: (1) full contact info + admin notes (2) 6-step booking flow checklist with inline toggle buttons for steps that admin marks (insurance, deposit, license) (3) send-reminder email builder — pick which pending items to include, send customer a personalized nudge with waiver link + instructions - Progress dots in table row update live when admin toggles a step - Stats row now includes waiver, insurance, deposit counts Co-Authored-By: Claude Sonnet 4.6 --- admin/index.php | 661 ++++++++++++++++++++++++++++++++++++++++++------ db.php | 2 +- 2 files changed, 583 insertions(+), 80 deletions(-) diff --git a/admin/index.php b/admin/index.php index e57df7d..19d592c 100644 --- a/admin/index.php +++ b/admin/index.php @@ -1,5 +1,8 @@ 'Session expired. Please reload and log in again.']); + exit; +} +if ($isAjax) { header('Content-Type: application/json'); $action = $_POST['action'] ?? $_GET['action'] ?? ''; @@ -28,11 +37,10 @@ if ($isAjax && $authed) { if ($id && in_array($status, $allowed)) { db()->prepare("UPDATE bookings SET status=? WHERE id=?")->execute([$status, $id]); echo json_encode(['ok'=>true]); - } else { - echo json_encode(['error'=>'Invalid']); - } + } else { echo json_encode(['error'=>'Invalid']); } exit; } + if ($action === 'save_admin_notes') { $id = (int)($_POST['id'] ?? 0); $notes = substr(trim($_POST['notes'] ?? ''), 0, 1000); @@ -40,6 +48,103 @@ if ($isAjax && $authed) { echo json_encode(['ok'=>true]); exit; } + + if ($action === 'toggle_requirement') { + $id = (int)($_POST['id'] ?? 0); + $field = $_POST['field'] ?? ''; + $allowed_fields = ['insurance_verified','deposit_received','license_verified']; + if ($id && in_array($field, $allowed_fields)) { + $stmt = db()->prepare("SELECT `{$field}` FROM bookings WHERE id=?"); + $stmt->execute([$id]); + $current = (int)$stmt->fetchColumn(); + $new = $current ? 0 : 1; + db()->prepare("UPDATE bookings SET `{$field}`=? WHERE id=?")->execute([$new, $id]); + echo json_encode(['ok'=>true,'value'=>$new]); + } else { echo json_encode(['error'=>'Invalid']); } + exit; + } + + if ($action === 'send_reminder') { + $id = (int)($_POST['id'] ?? 0); + $keys = array_filter(explode(',', $_POST['items'] ?? '')); + $stmt = db()->prepare("SELECT * FROM bookings WHERE id=?"); + $stmt->execute([$id]); + $b = $stmt->fetch(); + if (!$b) { echo json_encode(['error'=>'Not found']); exit; } + + $pkg = PACKAGES[$b['package']] ?? ['label' => $b['package']]; + $dateLabel = date('F j, Y', strtotime($b['rental_date'])); + $ref = $b['booking_ref']; + + $itemDefs = [ + 'waiver' => [ + 'label' => 'Sign Your Rental Agreement', + 'detail' => 'Your digital rental agreement still needs to be signed before your pickup. It only takes a minute and can be done on any device — no printer required.', + 'cta' => "
Sign Agreement →
", + ], + 'insurance' => [ + 'label' => 'Proof of Personal Auto Insurance', + 'detail' => 'You\'ll need to bring proof of valid personal auto insurance to pickup. A photo on your phone of your insurance card is fine. This is required before we can hand over the keys.', + 'cta' => '', + ], + 'deposit' => [ + 'label' => 'Security Deposit', + 'detail' => 'A refundable security deposit is required at the time of pickup. Please have it ready — cash or card accepted. It will be returned in full upon safe return of the vehicle.', + 'cta' => '', + ], + 'license' => [ + 'label' => "Valid Driver's License", + 'detail' => "Please bring your valid driver's license to pickup. We're required to verify it before you take the Slingshot out. Must match the name on the booking.", + 'cta' => '', + ], + ]; + + $rowsHtml = ''; + $n = 1; + foreach ($keys as $key) { + if (!isset($itemDefs[$key])) continue; + $d = $itemDefs[$key]; + $rowsHtml .= " + + + {$n} + + + " . htmlspecialchars($d['label']) . " +

" . htmlspecialchars($d['detail']) . "

+ {$d['cta']} + +"; + $n++; + } + + if (!$rowsHtml) { echo json_encode(['error'=>'No items selected']); exit; } + + $html = " +
+
+

Parker County Slingshot Rentals

+
+
+

Almost Ready — A Few Things Before Pickup

+

Hey " . htmlspecialchars($b['name']) . ", your " . htmlspecialchars($pkg['label']) . " rental on {$dateLabel} is coming up! (Ref: {$ref})

+

To make sure pickup goes smoothly, here's what still needs to be taken care of:

+ {$rowsHtml}
+
+

Questions? Call or text (817) 555-0199 or reply to this email — we're happy to help.

+
+

Ride on,
The Parker County Slingshot Team

+
+
+

© " . date('Y') . " Parker County Slingshot Rentals — Weatherford, TX

+
+
"; + + $sent = sendEmail($b['email'], $b['name'], "Action Needed Before Your Rental — {$ref}", $html); + echo json_encode(['ok'=>true]); + exit; + } + if ($action === 'block_date') { $date = $_POST['date'] ?? ''; $reason = substr($_POST['reason'] ?? '', 0, 200); @@ -58,7 +163,7 @@ if ($isAjax && $authed) { exit; } -// ── Login page ──────────────────────────────────────────────────────────────── +// ── Login page ───────────────────────────────────────────────────────────────── if (!$authed) { ?> @@ -95,7 +200,7 @@ button:hover{background:#ea580c} quote($statusFilter) : ''; $bookings = db()->query("SELECT * FROM bookings {$where} ORDER BY rental_date ASC, created_at DESC")->fetchAll(); @@ -108,11 +213,12 @@ $stats = db()->query(" SUM(status='confirmed') AS confirmed, SUM(status='completed') AS completed, SUM(status='cancelled') AS cancelled, - SUM(CASE WHEN status IN ('confirmed','completed') THEN amount ELSE 0 END) AS revenue + SUM(CASE WHEN status IN ('confirmed','completed') THEN amount ELSE 0 END) AS revenue, + SUM(waiver_signed) AS waivers_signed, + SUM(insurance_verified) AS insurance_done, + SUM(deposit_received) AS deposits_done FROM bookings ")->fetch(); - -$statusColors = ['pending'=>'#d97706','confirmed'=>'#16a34a','completed'=>'#2563eb','cancelled'=>'#dc2626']; ?> @@ -122,40 +228,108 @@ $statusColors = ['pending'=>'#d97706','confirmed'=>'#16a34a','completed'=>'#2563 @@ -168,11 +342,14 @@ textarea.notes-ta{width:100%;font-size:.8rem;border:1px solid #e5e7eb;border-rad
-
Total Bookings
-
Pending
-
Confirmed
-
Completed
-
$
Revenue
+
Total Bookings
+
Pending
+
Confirmed
+
Completed
+
$
Revenue
+
Waivers Signed
+
Insurance OK
+
Deposits Rcvd
@@ -180,13 +357,14 @@ textarea.notes-ta{width:100%;font-size:.8rem;border:1px solid #e5e7eb;border-rad +
No bookings found.
@@ -194,52 +372,276 @@ textarea.notes-ta{width:100%;font-size:.8rem;border:1px solid #e5e7eb;border-rad - - + + + + + + + + - - - - + - - + + - + + + + + - @@ -250,7 +652,7 @@ textarea.notes-ta{width:100%;font-size:.8rem;border:1px solid #e5e7eb;border-rad
-

Block Dates (maintenance / personal use)

+

Block Dates

@@ -258,7 +660,7 @@ textarea.notes-ta{width:100%;font-size:.8rem;border:1px solid #e5e7eb;border-rad
- +
@@ -276,9 +678,21 @@ textarea.notes-ta{width:100%;font-size:.8rem;border:1px solid #e5e7eb;border-rad
- + +
RefCustomerPackageDateAmountStatusWaiverAdmin NotesReceivedCustomerRental DatePackageAmountStatusProgressSubmitted
-
- -
+ $b['package']]; + + // Determine each step's state + $stepConfirmed = in_array($b['status'], ['confirmed','completed']); + $stepWaiver = (bool)$b['waiver_signed']; + $stepInsurance = (bool)$b['insurance_verified']; + $stepDeposit = (bool)$b['deposit_received']; + $stepLicense = (bool)$b['license_verified']; + + // Dot colors: done=green, if cancelled skip all + $cancelled = $b['status'] === 'cancelled'; + $dotClass = function($done) use ($cancelled) { + if ($cancelled) return 'dot-skip'; + return $done ? 'dot-done' : 'dot-pending'; + }; + + $allDone = $stepConfirmed && $stepWaiver && $stepInsurance && $stepDeposit && $stepLicense; + $pendingCount = ($cancelled ? 0 : ( + (!$stepConfirmed?1:0)+(!$stepWaiver?1:0)+(!$stepInsurance?1:0)+(!$stepDeposit?1:0)+(!$stepLicense?1:0) + )); + ?> +
+ - $b['package']]; ?> - +
+
-
+
$ - $ + - - ✓ Signed -
- - Send Link ↗ -
Pending +
+
+
+
+
+
+
+ + pending + + All done ✓
- - +
+
+ + +
+

Customer

+
+
+
+ +
+ +
Package
+
+
$
+
Rental Date
+
+ +
Customer Message
+
+ + +
+
Admin Notes
+ + +
+
+ + +
+

Booking Flow

+
+ + +
+
+
+ Booking Submitted + +
+
+ + +
+
+ +
+
+ Booking Confirmed + + Confirmed — status: + Cancelled + Awaiting confirmation — change status above + + +
+
+ + +
+
+ +
+
+ Rental Waiver Signed + + + Signed by + on + N/A + Not yet signed + + + + + +
+
+ + +
+
+ +
+
+ Proof of Insurance Received + + + + +
+ +
+ +
+
+ + +
+
+ +
+
+ Security Deposit Received + + + + +
+ +
+ +
+
+ + +
+
+ +
+
+ Driver's License Verified + + + + +
+ +
+ +
+
+ +
+
+ + +
+

Send Reminder Email

+

Select what the customer still needs to do, then send them a nudge email with clear instructions.

+ + +

Not applicable for cancelled bookings.

+ +
+

Include in Reminder

+
+ + + + +
+ + +
+ +
+ Waiver Link + https://parkerslingshotrentals.com/waiver.php?ref= +
+ +
+ +