- 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:
+351
-13
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user