Portal: - New business portal UI (portal/index.html, portal.css, portal.js) - Dashboard with real-time stats (orders today, revenue, pending, menu items) - Business info endpoint (api/businesses/get.cfm) - Portal stats endpoint (api/portal/stats.cfm) - Menu page links to existing full-featured menu editor Stripe Connect: - Onboarding endpoint (api/stripe/onboard.cfm) - Status check endpoint (api/stripe/status.cfm) - Payment intent creation (api/stripe/createPaymentIntent.cfm) - Webhook handler (api/stripe/webhook.cfm) Beacon APIs: - List all beacons (api/beacons/list_all.cfm) - Get business from beacon (api/beacons/getBusinessFromBeacon.cfm) Task System: - List pending tasks (api/tasks/listPending.cfm) - Accept task (api/tasks/accept.cfm) Other: - HUD interface for quick order status display - KDS debug/test pages - Updated Application.cfm with public endpoint allowlist - Order status check improvements 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
429 lines
17 KiB
HTML
429 lines
17 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Payfrit Business Portal</title>
|
|
<link rel="stylesheet" href="portal.css">
|
|
</head>
|
|
<body>
|
|
<div class="app">
|
|
<!-- Sidebar -->
|
|
<aside class="sidebar" id="sidebar">
|
|
<div class="sidebar-header">
|
|
<div class="logo">
|
|
<span class="logo-icon">P</span>
|
|
<span class="logo-text">Payfrit</span>
|
|
</div>
|
|
<button class="sidebar-toggle" id="sidebarToggle">
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M3 12h18M3 6h18M3 18h18"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<nav class="sidebar-nav">
|
|
<a href="#dashboard" class="nav-item active" data-page="dashboard">
|
|
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/>
|
|
<rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/>
|
|
</svg>
|
|
<span>Dashboard</span>
|
|
</a>
|
|
<a href="#orders" class="nav-item" data-page="orders">
|
|
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2"/>
|
|
<rect x="9" y="3" width="6" height="4" rx="1"/>
|
|
<path d="M9 12h6M9 16h6"/>
|
|
</svg>
|
|
<span>Orders</span>
|
|
</a>
|
|
<a href="#menu" class="nav-item" data-page="menu">
|
|
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M3 6h18M3 12h18M3 18h18"/>
|
|
</svg>
|
|
<span>Menu</span>
|
|
</a>
|
|
<a href="#reports" class="nav-item" data-page="reports">
|
|
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M18 20V10M12 20V4M6 20v-6"/>
|
|
</svg>
|
|
<span>Reports</span>
|
|
</a>
|
|
<a href="#team" class="nav-item" data-page="team">
|
|
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/>
|
|
<circle cx="9" cy="7" r="4"/>
|
|
<path d="M23 21v-2a4 4 0 00-3-3.87M16 3.13a4 4 0 010 7.75"/>
|
|
</svg>
|
|
<span>Team</span>
|
|
</a>
|
|
<a href="#settings" class="nav-item" data-page="settings">
|
|
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<circle cx="12" cy="12" r="3"/>
|
|
<path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-2 2 2 2 0 01-2-2v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 01-2-2 2 2 0 012-2h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 010-2.83 2 2 0 012.83 0l.06.06a1.65 1.65 0 001.82.33H9a1.65 1.65 0 001-1.51V3a2 2 0 012-2 2 2 0 012 2v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 0 2 2 0 010 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 012 2 2 2 0 01-2 2h-.09a1.65 1.65 0 00-1.51 1z"/>
|
|
</svg>
|
|
<span>Settings</span>
|
|
</a>
|
|
</nav>
|
|
|
|
<div class="sidebar-footer">
|
|
<div class="business-info">
|
|
<div class="business-avatar" id="businessAvatar">B</div>
|
|
<div class="business-details">
|
|
<div class="business-name" id="businessName">Loading...</div>
|
|
<div class="business-status online">Online</div>
|
|
</div>
|
|
</div>
|
|
<a href="#logout" class="nav-item logout" data-page="logout">
|
|
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h4M16 17l5-5-5-5M21 12H9"/>
|
|
</svg>
|
|
<span>Logout</span>
|
|
</a>
|
|
</div>
|
|
</aside>
|
|
|
|
<!-- Main Content -->
|
|
<main class="main-content">
|
|
<header class="top-bar">
|
|
<div class="page-title">
|
|
<h1 id="pageTitle">Dashboard</h1>
|
|
</div>
|
|
<div class="top-bar-actions">
|
|
<button class="btn btn-icon" id="refreshBtn" title="Refresh">
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M23 4v6h-6M1 20v-6h6"/>
|
|
<path d="M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15"/>
|
|
</svg>
|
|
</button>
|
|
<div class="user-menu">
|
|
<button class="user-btn" id="userBtn">
|
|
<span class="user-avatar" id="userAvatar">U</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Page Container -->
|
|
<div class="page-container" id="pageContainer">
|
|
<!-- Dashboard Page -->
|
|
<section class="page active" id="page-dashboard">
|
|
<div class="stats-grid">
|
|
<div class="stat-card">
|
|
<div class="stat-icon orders">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2"/>
|
|
</svg>
|
|
</div>
|
|
<div class="stat-content">
|
|
<div class="stat-value" id="statOrdersToday">0</div>
|
|
<div class="stat-label">Orders Today</div>
|
|
</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-icon revenue">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M12 1v22M17 5H9.5a3.5 3.5 0 000 7h5a3.5 3.5 0 010 7H6"/>
|
|
</svg>
|
|
</div>
|
|
<div class="stat-content">
|
|
<div class="stat-value" id="statRevenueToday">$0</div>
|
|
<div class="stat-label">Revenue Today</div>
|
|
</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-icon pending">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/>
|
|
</svg>
|
|
</div>
|
|
<div class="stat-content">
|
|
<div class="stat-value" id="statPendingOrders">0</div>
|
|
<div class="stat-label">Pending Orders</div>
|
|
</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-icon items">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M3 6h18M3 12h18M3 18h18"/>
|
|
</svg>
|
|
</div>
|
|
<div class="stat-content">
|
|
<div class="stat-value" id="statMenuItems">0</div>
|
|
<div class="stat-label">Menu Items</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="dashboard-grid">
|
|
<div class="card recent-orders">
|
|
<div class="card-header">
|
|
<h3>Recent Orders</h3>
|
|
<a href="#orders" class="link">View All</a>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="orders-list" id="recentOrdersList">
|
|
<div class="empty-state">No recent orders</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card quick-actions">
|
|
<div class="card-header">
|
|
<h3>Quick Actions</h3>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="action-grid">
|
|
<button class="action-btn" onclick="Portal.navigate('menu')">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M12 5v14M5 12h14"/>
|
|
</svg>
|
|
Add Menu Item
|
|
</button>
|
|
<button class="action-btn" onclick="window.open('/kds/', '_blank')">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<rect x="2" y="3" width="20" height="14" rx="2"/>
|
|
<path d="M8 21h8M12 17v4"/>
|
|
</svg>
|
|
Open KDS
|
|
</button>
|
|
<button class="action-btn" onclick="window.open('/hud/', '_blank')">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M18 20V10M12 20V4M6 20v-6"/>
|
|
</svg>
|
|
Task HUD
|
|
</button>
|
|
<button class="action-btn" onclick="Portal.navigate('settings')">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<circle cx="12" cy="12" r="3"/>
|
|
</svg>
|
|
Settings
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Orders Page -->
|
|
<section class="page" id="page-orders">
|
|
<div class="page-header">
|
|
<div class="filters">
|
|
<select id="orderStatusFilter" class="form-select">
|
|
<option value="">All Statuses</option>
|
|
<option value="1">Submitted</option>
|
|
<option value="2">Preparing</option>
|
|
<option value="3">Ready</option>
|
|
<option value="4">Completed</option>
|
|
</select>
|
|
<input type="date" id="orderDateFilter" class="form-input">
|
|
</div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="table-container">
|
|
<table class="data-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Order #</th>
|
|
<th>Customer</th>
|
|
<th>Items</th>
|
|
<th>Total</th>
|
|
<th>Status</th>
|
|
<th>Time</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="ordersTableBody">
|
|
<tr><td colspan="7" class="empty-state">Loading orders...</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Menu Page -->
|
|
<section class="page" id="page-menu">
|
|
<div class="page-header">
|
|
<button class="btn btn-primary" onclick="Portal.showAddCategoryModal()">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M12 5v14M5 12h14"/>
|
|
</svg>
|
|
Add Category
|
|
</button>
|
|
<button class="btn btn-primary" onclick="Portal.showAddItemModal()">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M12 5v14M5 12h14"/>
|
|
</svg>
|
|
Add Item
|
|
</button>
|
|
</div>
|
|
<div class="menu-grid" id="menuGrid">
|
|
<div class="empty-state">Loading menu...</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Reports Page -->
|
|
<section class="page" id="page-reports">
|
|
<div class="reports-grid">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3>Sales Overview</h3>
|
|
<select id="reportPeriod" class="form-select">
|
|
<option value="7">Last 7 Days</option>
|
|
<option value="30">Last 30 Days</option>
|
|
<option value="90">Last 90 Days</option>
|
|
</select>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="chart-placeholder" id="salesChart">
|
|
<p>Sales chart will appear here</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3>Top Selling Items</h3>
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="topItemsList" class="top-items-list">
|
|
<div class="empty-state">No data available</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Team Page -->
|
|
<section class="page" id="page-team">
|
|
<div class="page-header">
|
|
<button class="btn btn-primary" onclick="Portal.showInviteModal()">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M16 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/>
|
|
<circle cx="8.5" cy="7" r="4"/>
|
|
<path d="M20 8v6M23 11h-6"/>
|
|
</svg>
|
|
Invite Team Member
|
|
</button>
|
|
<div class="hiring-toggle">
|
|
<label class="toggle">
|
|
<input type="checkbox" id="hiringToggle">
|
|
<span class="toggle-slider"></span>
|
|
</label>
|
|
<span>Accepting Applications</span>
|
|
</div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="table-container">
|
|
<table class="data-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Name</th>
|
|
<th>Email</th>
|
|
<th>Role</th>
|
|
<th>Status</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="teamTableBody">
|
|
<tr><td colspan="5" class="empty-state">Loading team...</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Settings Page -->
|
|
<section class="page" id="page-settings">
|
|
<div class="settings-grid">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3>Business Information</h3>
|
|
</div>
|
|
<div class="card-body">
|
|
<form id="businessInfoForm" class="form">
|
|
<div class="form-group">
|
|
<label>Business Name</label>
|
|
<input type="text" id="settingBusinessName" class="form-input">
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Description</label>
|
|
<textarea id="settingDescription" class="form-textarea" rows="3"></textarea>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Address</label>
|
|
<input type="text" id="settingAddress" class="form-input">
|
|
</div>
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label>Phone</label>
|
|
<input type="tel" id="settingPhone" class="form-input">
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Email</label>
|
|
<input type="email" id="settingEmail" class="form-input">
|
|
</div>
|
|
</div>
|
|
<button type="submit" class="btn btn-primary">Save Changes</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3>Business Hours</h3>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="hours-list" id="hoursEditor">
|
|
<!-- Hours will be loaded here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3>Payment Settings</h3>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="stripe-status" id="stripeStatus">
|
|
<div class="status-icon disconnected">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<circle cx="12" cy="12" r="10"/>
|
|
<path d="M15 9l-6 6M9 9l6 6"/>
|
|
</svg>
|
|
</div>
|
|
<div class="status-text">
|
|
<strong>Stripe Not Connected</strong>
|
|
<p>Connect your Stripe account to accept payments</p>
|
|
</div>
|
|
<button class="btn btn-primary" onclick="Portal.connectStripe()">
|
|
Connect Stripe
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
|
|
<!-- Modal Container -->
|
|
<div class="modal-overlay" id="modalOverlay">
|
|
<div class="modal" id="modal">
|
|
<div class="modal-header">
|
|
<h3 id="modalTitle">Modal</h3>
|
|
<button class="modal-close" onclick="Portal.closeModal()">×</button>
|
|
</div>
|
|
<div class="modal-body" id="modalBody">
|
|
<!-- Modal content loaded dynamically -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Toast Container -->
|
|
<div class="toast-container" id="toastContainer"></div>
|
|
|
|
<script src="portal.js?v=3"></script>
|
|
</body>
|
|
</html>
|