- Implemented the full Fat Bottom Grille storefront and staff dashboard.

Highlights include configurable menus, specials, cart/checkout, taxes, 
customer accounts with email 2FA, newsletters, analytics, refunds, 
PDF/CSV exports, Square/Mailgun adapters, and optional MySQL migration.

See README.md for setup and production configuration. Verified 
PHP/JavaScript syntax, responsive layouts, registration, 2FA, checkout, 
dashboard workflows, settings persistence, and PDF generation.
Real Square, Mailgun, and MySQL connections require credentials/server 
access. Square implementation follows the official Payments API and 
Refunds API.
This commit is contained in:
Ty Clifford
2026-06-10 13:27:42 -04:00
parent d46b0be488
commit 16235369cb
49 changed files with 7685 additions and 2 deletions
+28
View File
@@ -0,0 +1,28 @@
<aside class="admin-sidebar">
<a class="admin-brand" href="/admin">
<span class="brand-mark">FB</span>
<span>
<strong><?= e($config->get('business.name')) ?></strong>
<small>Operations</small>
</span>
</a>
<nav class="admin-nav" aria-label="Dashboard navigation">
<a class="<?= $adminPage === 'dashboard' ? 'is-active' : '' ?>" href="/admin">Overview</a>
<a class="<?= $adminPage === 'orders' ? 'is-active' : '' ?>" href="/admin/orders">Orders</a>
<a class="<?= $adminPage === 'menu' ? 'is-active' : '' ?>" href="/admin/menu">Menu</a>
<a class="<?= $adminPage === 'specials' ? 'is-active' : '' ?>" href="/admin/specials">Specials</a>
<a class="<?= $adminPage === 'newsletters' ? 'is-active' : '' ?>" href="/admin/newsletters">Newsletters</a>
<?php if (($currentUser['role'] ?? '') === 'admin'): ?>
<a class="<?= $adminPage === 'users' ? 'is-active' : '' ?>" href="/admin/users">Users & Staff</a>
<a class="<?= $adminPage === 'settings' ? 'is-active' : '' ?>" href="/admin/settings">Settings</a>
<?php endif; ?>
</nav>
<div class="admin-user">
<span class="avatar"><?= e(strtoupper(substr((string) ($currentUser['full_name'] ?? 'S'), 0, 1))) ?></span>
<span>
<strong><?= e($currentUser['full_name'] ?? '') ?></strong>
<small><?= e(ucfirst((string) ($currentUser['role'] ?? 'staff'))) ?></small>
</span>
</div>
</aside>
+11
View File
@@ -0,0 +1,11 @@
<?php if (!empty($flashMessages)): ?>
<div class="<?= isset($adminPage) ? 'admin-flash-wrap' : 'container flash-wrap' ?>" aria-live="polite">
<?php foreach ($flashMessages as $message): ?>
<div class="flash flash-<?= e($message['type']) ?>">
<span><?= e($message['message']) ?></span>
<button type="button" class="flash-close" aria-label="Dismiss message">&times;</button>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
+40
View File
@@ -0,0 +1,40 @@
<?php
$tags = json_decode((string) ($item['dietary_tags'] ?? '[]'), true);
$tags = is_array($tags) ? $tags : [];
?>
<article class="menu-card">
<div class="menu-card-art <?= !empty($item['image_url']) ? 'has-image' : '' ?>"
<?php if (!empty($item['image_url'])): ?>style="background-image:url('<?= e($item['image_url']) ?>')"<?php endif; ?>>
<?php if (empty($item['image_url'])): ?>
<span><?= e(strtoupper(substr((string) $item['name'], 0, 2))) ?></span>
<?php endif; ?>
<?php if (!empty($item['featured'])): ?><span class="card-badge">House Pick</span><?php endif; ?>
</div>
<div class="menu-card-body">
<div class="menu-card-heading">
<h3><?= e($item['name']) ?></h3>
<strong><?= money($item['price_cents']) ?></strong>
</div>
<p><?= e($item['description']) ?></p>
<?php if ($tags): ?>
<div class="tag-list">
<?php foreach ($tags as $tag): ?><span><?= e($tag) ?></span><?php endforeach; ?>
</div>
<?php endif; ?>
<form class="quick-add" action="/cart/add" method="post">
<?= csrf_field() ?>
<input type="hidden" name="menu_item_id" value="<?= (int) $item['id'] ?>">
<input type="hidden" name="return_to" value="<?= e($_SERVER['REQUEST_URI'] ?? '/menu') ?>">
<label>
<span class="sr-only">Quantity</span>
<select name="quantity" aria-label="Quantity for <?= e($item['name']) ?>">
<?php for ($quantity = 1; $quantity <= 8; $quantity++): ?>
<option value="<?= $quantity ?>"><?= $quantity ?></option>
<?php endfor; ?>
</select>
</label>
<button class="button button-small" type="submit">Add to Order</button>
</form>
</div>
</article>