- 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
+54
View File
@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace FatBottom\Services;
use FatBottom\Core\Config;
use FatBottom\Core\Database;
final class TwoFactorService
{
public function __construct(
private readonly Database $db,
private readonly Mailer $mailer,
private readonly Config $config
) {
}
public function issue(array $user, string $purpose = 'login'): ?string
{
$code = (string) random_int(100000, 999999);
$expiresAt = gmdate('Y-m-d H:i:s', time() + 600);
$this->db->execute(
'INSERT INTO login_codes (user_id, code_hash, purpose, expires_at) VALUES (?, ?, ?, ?)',
[(int) $user['id'], password_hash($code, PASSWORD_DEFAULT), $purpose, $expiresAt]
);
$name = htmlspecialchars((string) $user['full_name'], ENT_QUOTES, 'UTF-8');
$this->mailer->send(
(string) $user['email'],
'Your Fat Bottom Grille verification code',
"<p>Hi {$name},</p><p>Your verification code is <strong>{$code}</strong>.</p><p>It expires in 10 minutes.</p>"
);
return (bool) $this->config->get('mailgun.enabled', false) ? null : $code;
}
public function verify(int $userId, string $code, string $purpose = 'login'): bool
{
$record = $this->db->fetch(
'SELECT * FROM login_codes
WHERE user_id = ? AND purpose = ? AND used_at IS NULL AND expires_at > CURRENT_TIMESTAMP
ORDER BY id DESC LIMIT 1',
[$userId, $purpose]
);
if ($record === null || !password_verify($code, (string) $record['code_hash'])) {
return false;
}
$this->db->execute('UPDATE login_codes SET used_at = CURRENT_TIMESTAMP WHERE id = ?', [(int) $record['id']]);
return true;
}
}