- Implemented the full order lifecycle, secure customer tracker, automatic acceptance emails, configurable status templates, and guest/account CRM management.

Key areas: 
[OrderStatusService.php](/Users/tyemeclifford/Documents/GH/fatbottomgrille/app/Services/OrderStatusService.php), 
[AdminController.php](/Users/tyemeclifford/Documents/GH/fatbottomgrille/app/Controllers/AdminController.php), 
and 
[Database.php](/Users/tyemeclifford/Documents/GH/fatbottomgrille/app/Core/Database.php).
SQLite migration applied successfully. PHP/JS syntax, integration 
workflows, rendered pages, database integrity, and tracker authorization 
all passed. Invalid tracker tokens correctly return 403.
This commit is contained in:
Ty Clifford
2026-06-14 13:35:31 -04:00
parent 3417589a7c
commit 03348cad79
24 changed files with 1218 additions and 72 deletions
+351 -13
View File
@@ -1866,18 +1866,34 @@ textarea:focus {
white-space: nowrap;
}
.status-paid,
.status-completed,
.status-pending,
.status-paid {
color: #8a5618;
background: #f8e5c7;
}
.status-accepted {
color: #17633f;
background: #d7f4e5;
}
.status-ready {
color: #246449;
background: #ddefe6;
color: #195d83;
background: #d9effc;
}
.status-completed {
color: #65368c;
background: #ecdffc;
}
.status-processing,
.status-preparing {
color: #765417;
background: #f5e8c9;
color: #7d2d72;
background: #f7dcf2;
}
.status-declined,
.status-cancelled,
.status-refunded {
color: #7d3f3f;
@@ -2121,18 +2137,34 @@ textarea:focus {
background: #182a37;
}
.store-body .status-paid,
.store-body .status-completed,
.store-body .status-pending,
.store-body .status-paid {
color: #ffbd59;
background: #3d2a14;
}
.store-body .status-accepted {
color: #6cffad;
background: #143a29;
}
.store-body .status-ready {
color: #9de0c0;
background: #17382d;
color: #67d5ff;
background: #11334a;
}
.store-body .status-completed {
color: #cf8dff;
background: #321c49;
}
.store-body .status-processing,
.store-body .status-preparing {
color: #f0ce83;
background: #3b3019;
color: #ff82dc;
background: #46183c;
}
.store-body .status-declined,
.store-body .status-cancelled,
.store-body .status-refunded {
color: #efaaaa;
@@ -2698,6 +2730,213 @@ td small {
color: var(--gold-light);
}
.order-action-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px;
}
.order-action-grid .button:last-child {
grid-column: 1 / -1;
}
.email-template-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.email-template-list details {
background: #0a1722;
border: 1px solid #283b49;
border-radius: var(--radius);
}
.email-template-list summary {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20px;
padding: 14px 16px;
cursor: pointer;
}
.email-template-list summary span {
color: #758690;
font-size: 0.68rem;
text-transform: uppercase;
}
.email-template-list .inline-editor {
padding: 0 16px 16px;
}
.crm-stat-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 12px;
margin-bottom: 20px;
}
.crm-stat-grid > div {
display: flex;
min-height: 105px;
flex-direction: column;
justify-content: center;
padding: 18px;
background: #0e1c28;
border: 1px solid var(--line);
border-radius: var(--radius-large);
}
.crm-stat-grid span {
color: #7f909a;
font-size: 0.64rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.crm-stat-grid strong {
margin-top: 6px;
color: var(--cream);
font-family: var(--font-display);
font-size: 1.45rem;
}
.tracker-layout {
display: grid;
grid-template-columns: minmax(0, 1.35fr) minmax(300px, 0.65fr);
align-items: start;
gap: 28px;
}
.tracker-heading {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 20px;
}
.tracker-heading h2 {
margin: 8px 0 30px;
font-size: clamp(2rem, 5vw, 3.7rem);
text-transform: uppercase;
}
.order-progress {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
margin: 12px 0 28px;
}
.order-progress > div {
position: relative;
display: flex;
align-items: center;
flex-direction: column;
gap: 9px;
color: #71818c;
font-size: 0.66rem;
font-weight: 700;
text-align: center;
text-transform: uppercase;
}
.order-progress > div::before {
position: absolute;
z-index: 0;
top: 17px;
right: 50%;
width: 100%;
height: 2px;
background: #2d404e;
content: "";
}
.order-progress > div:first-child::before {
display: none;
}
.order-progress i {
position: relative;
z-index: 1;
display: grid;
width: 36px;
height: 36px;
place-items: center;
color: #81919a;
background: #142532;
border: 2px solid #314552;
border-radius: 50%;
font-style: normal;
}
.order-progress .is-complete::before,
.order-progress .is-complete i {
color: #07140f;
background: #54c996;
border-color: #54c996;
}
.order-progress .is-current i {
box-shadow: 0 0 0 6px rgba(84, 201, 150, 0.13);
}
.tracker-declined {
margin-bottom: 28px;
padding: 18px;
color: #f0b4b4;
background: #321b20;
border: 1px solid #69313a;
border-radius: var(--radius);
}
.tracker-declined p {
margin: 8px 0 0;
}
.tracker-updates {
margin-top: 28px;
padding-top: 24px;
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.tracker-updates h3 {
margin-bottom: 10px;
text-transform: uppercase;
}
.tracker-updates article {
padding: 14px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.09);
}
.tracker-updates article > div {
display: flex;
justify-content: space-between;
gap: 16px;
}
.tracker-updates time {
color: #748690;
font-size: 0.68rem;
}
.tracker-updates p {
margin: 6px 0 0;
color: #aebbc3;
}
.nav-theme-toggle {
padding: 5px 0;
color: var(--gold-light);
background: transparent;
border: 0;
font-size: 0.72rem;
font-weight: 700;
}
.refund-list {
margin-top: 18px;
border-top: 1px solid var(--line);
@@ -3062,7 +3301,8 @@ code {
.account-grid,
.checkout-layout,
.admin-detail-grid,
.newsletter-grid {
.newsletter-grid,
.tracker-layout {
grid-template-columns: 1fr;
}
@@ -3138,6 +3378,10 @@ code {
.special-admin-grid {
grid-template-columns: 1fr;
}
.crm-stat-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 620px) {
@@ -3149,6 +3393,30 @@ code {
padding-block: 60px;
}
.order-progress {
grid-template-columns: 1fr;
gap: 10px;
}
.order-progress > div {
align-items: center;
flex-direction: row;
text-align: left;
}
.order-progress > div::before {
display: none;
}
.crm-stat-grid,
.order-action-grid {
grid-template-columns: 1fr;
}
.order-action-grid .button:last-child {
grid-column: auto;
}
.brand-copy strong {
font-size: 1.1rem;
}
@@ -3305,6 +3573,10 @@ code {
display: none;
}
.admin-top-actions .theme-toggle {
display: inline-flex;
}
.admin-topbar {
flex-direction: row;
align-items: center;
@@ -3336,3 +3608,69 @@ code {
max-width: none;
}
}
/* User-selected light theme */
html[data-theme="light"] body,
html[data-theme="light"] .store-body main,
html[data-theme="light"] .admin-body {
color: #15232d;
background: #f5f2e9;
}
html[data-theme="light"] .site-header,
html[data-theme="light"] .admin-sidebar,
html[data-theme="light"] .admin-topbar {
color: #15232d;
background: #fffdf7;
border-color: #d8d3c7;
}
html[data-theme="light"] .store-body .panel,
html[data-theme="light"] .store-body .cart-list,
html[data-theme="light"] .store-body .empty-state,
html[data-theme="light"] .store-body .auth-card,
html[data-theme="light"] .admin-body .admin-panel,
html[data-theme="light"] .stat-card,
html[data-theme="light"] .crm-stat-grid > div {
color: #15232d;
background: #fffdf7;
border-color: #d8d3c7;
}
html[data-theme="light"] .admin-body input,
html[data-theme="light"] .admin-body select,
html[data-theme="light"] .admin-body textarea,
html[data-theme="light"] .store-body input,
html[data-theme="light"] .store-body select,
html[data-theme="light"] .store-body textarea {
color: #15232d;
background: #ffffff;
border-color: #b9b4a8;
}
html[data-theme="light"] .admin-body label,
html[data-theme="light"] .store-body label,
html[data-theme="light"] .tracker-updates p,
html[data-theme="light"] td,
html[data-theme="light"] .customer-card {
color: #4e5b63;
}
html[data-theme="light"] .admin-nav a:hover,
html[data-theme="light"] .admin-nav a.is-active,
html[data-theme="light"] .email-template-list details,
html[data-theme="light"] .admin-body .notice,
html[data-theme="light"] .admin-body .check-control,
html[data-theme="light"] .store-body .notice,
html[data-theme="light"] .store-body .check-control {
color: #263640;
background: #eee9dd;
border-color: #d2ccbf;
}
html[data-theme="light"] .detail-title-row h2,
html[data-theme="light"] .admin-panel-heading h2,
html[data-theme="light"] .tracker-heading h2,
html[data-theme="light"] .crm-stat-grid strong {
color: #15232d;
}
+19 -1
View File
@@ -1,4 +1,23 @@
(() => {
const savedTheme = window.localStorage.getItem("fatbottom-theme");
const preferredTheme = window.matchMedia?.("(prefers-color-scheme: light)").matches ? "light" : "dark";
document.documentElement.dataset.theme = savedTheme || preferredTheme;
const syncThemeButtons = () => {
document.querySelectorAll("[data-theme-toggle]").forEach((button) => {
button.textContent = document.documentElement.dataset.theme === "light" ? "Dark Mode" : "Light Mode";
});
};
syncThemeButtons();
document.querySelectorAll("[data-theme-toggle]").forEach((button) => {
button.addEventListener("click", () => {
const next = document.documentElement.dataset.theme === "light" ? "dark" : "light";
document.documentElement.dataset.theme = next;
window.localStorage.setItem("fatbottom-theme", next);
syncThemeButtons();
});
});
const navToggle = document.querySelector(".nav-toggle");
const nav = document.querySelector(".site-nav");
if (navToggle && nav) {
@@ -82,4 +101,3 @@
initializeSquare();
})();
+21 -4
View File
@@ -9,8 +9,10 @@ use FatBottom\Core\Auth;
use FatBottom\Core\Http;
use FatBottom\Core\Router;
use FatBottom\Services\CartService;
use FatBottom\Services\CustomerService;
use FatBottom\Services\Mailer;
use FatBottom\Services\OrderService;
use FatBottom\Services\OrderStatusService;
use FatBottom\Services\PaymentGateway;
use FatBottom\Services\StatsService;
use FatBottom\Services\TwoFactorService;
@@ -23,13 +25,24 @@ $auth = new Auth($db);
$cart = new CartService($db);
$mailer = new Mailer($config);
$payments = new PaymentGateway($config);
$orders = new OrderService($db, $payments);
$customers = new CustomerService($db);
$orderStatuses = new OrderStatusService($config, $db, $mailer);
$orders = new OrderService($db, $payments, $customers);
$stats = new StatsService($db);
$twoFactor = new TwoFactorService($db, $mailer, $config);
$store = new StoreController($config, $auth, $cart, $db, $orders, $mailer);
$store = new StoreController($config, $auth, $cart, $db, $orders, $orderStatuses);
$authentication = new AuthController($config, $auth, $cart, $db, $twoFactor);
$admin = new AdminController($config, $auth, $cart, $db, $stats, $payments, $mailer);
$admin = new AdminController(
$config,
$auth,
$cart,
$db,
$stats,
$payments,
$mailer,
$orderStatuses
);
$path = Http::path();
$user = $auth->user();
@@ -45,6 +58,7 @@ $router->post('/cart/remove', [$store, 'removeFromCart']);
$router->get('/checkout', [$store, 'checkout']);
$router->post('/checkout', [$store, 'placeOrder']);
$router->get('/order/complete', [$store, 'orderComplete']);
$router->get('/order/track', [$store, 'orderTracker']);
$router->get('/login', [$authentication, 'loginForm']);
$router->post('/login', [$authentication, 'login']);
@@ -62,6 +76,10 @@ $router->get('/admin/orders', [$admin, 'orders']);
$router->get('/admin/order', [$admin, 'orderDetail']);
$router->post('/admin/order', [$admin, 'updateOrder']);
$router->post('/admin/order/refund', [$admin, 'refundOrder']);
$router->get('/admin/customers', [$admin, 'customers']);
$router->get('/admin/customer', [$admin, 'customerDetail']);
$router->post('/admin/customer/save', [$admin, 'saveCustomer']);
$router->post('/admin/customer/delete', [$admin, 'deleteCustomer']);
$router->get('/admin/menu', [$admin, 'menu']);
$router->post('/admin/category/save', [$admin, 'saveCategory']);
$router->post('/admin/item/save', [$admin, 'saveItem']);
@@ -79,4 +97,3 @@ $router->get('/admin/export/orders.csv', [$admin, 'exportOrdersCsv']);
$router->post('/admin/migrate/mysql', [$admin, 'migrateMySql']);
$router->dispatch($_SERVER['REQUEST_METHOD'] ?? 'GET', $path);