The ride table holds two generations of status values: the legacy CamelCase
set ('Finished', 'CancelFromPassenger') and the lowercase set written by
backend/ride/rides/* today ('completed', 'cancelled_by_passenger'). Admin
queries only matched the legacy set, so on live data:
- get_rides_by_status.php returned nothing meaningful for every filter, and
the "in progress" default masked it.
- dashbord.php reported total_driver_earnings as NULL, completed_rides as a
fraction of the real count, and cancelled_rides as 0.
- driver_avg_duration averaged in negative durations, yielding "-00h 22m".
All three now match on LOWER(status) across both families.
Staff/pending.php ran with no authentication at all, exposing pending
admins' names and phone numbers to any caller; it now goes through
connect.php with a role check. It also returned HTTP 400 for everything when
the `users` table was absent — each source is queried independently and
reports its own availability.
Console:
- Render rides from either schema generation (price/date/time and
start_location coordinates, or the older address/created_at columns).
- Null aggregates render as "—" rather than a measured 0.00.
- Add tariff/promo, WhatsApp send and encryption modules, all super-admin
gated; pricing remains read-only.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
607 lines
24 KiB
HTML
607 lines
24 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Siro Admin | Operations Console</title>
|
|
|
|
<!-- Fonts & Icons -->
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Outfit:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
<script src="https://unpkg.com/@phosphor-icons/web"></script>
|
|
|
|
<!-- Styles -->
|
|
<link rel="stylesheet" href="css/main.css">
|
|
</head>
|
|
<body>
|
|
|
|
<!-- ================= AUTH OVERLAY ================= -->
|
|
<div class="auth-wrapper" id="authWrapper">
|
|
<div class="auth-card">
|
|
<div class="auth-header">
|
|
<div class="auth-logo">
|
|
<i class="ph-bold ph-car"></i>
|
|
<span>SIRO ADMIN</span>
|
|
</div>
|
|
<p class="auth-subtitle">Operations & Control Console</p>
|
|
</div>
|
|
|
|
<form id="loginForm">
|
|
<div class="secure-strip">
|
|
<i class="ph-fill ph-shield-check"></i>
|
|
<span>Secure session · device-bound token · WhatsApp OTP</span>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label" for="loginEmail">Admin Email / Phone</label>
|
|
<div class="form-input-group">
|
|
<input type="text" id="loginEmail" class="form-input" placeholder="you@siromove.com or +9627..." autocomplete="username" required>
|
|
<i class="ph ph-envelope"></i>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label" for="loginPass">Password</label>
|
|
<div class="form-input-group">
|
|
<input type="password" id="loginPass" class="form-input" placeholder="Enter your password" autocomplete="current-password" required>
|
|
<i class="ph ph-lock-key"></i>
|
|
</div>
|
|
</div>
|
|
|
|
<button type="submit" class="btn-primary" id="loginSubmitBtn" style="margin-top: 1.25rem;">
|
|
<i class="ph-bold ph-sign-in"></i> <span>Sign In</span>
|
|
</button>
|
|
|
|
<div class="device-note">
|
|
<i class="ph ph-fingerprint"></i>
|
|
<span>This browser is registered as a trusted device: <code id="fpPreview">…</code></span>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ================= APP CONTAINER ================= -->
|
|
<div class="app-container">
|
|
|
|
<!-- Sidebar Navigation -->
|
|
<aside class="sidebar" id="sidebar">
|
|
<div class="sidebar-header">
|
|
<a href="#" class="brand">
|
|
<div class="brand-icon">
|
|
<i class="ph-bold ph-lightning"></i>
|
|
</div>
|
|
<span>SIRO</span>
|
|
<span class="brand-badge">ADMIN</span>
|
|
</a>
|
|
</div>
|
|
|
|
<nav class="sidebar-menu">
|
|
<div class="menu-label">Operations</div>
|
|
<a class="nav-item active" data-view="dashboardView">
|
|
<i class="ph ph-squares-four"></i>
|
|
<span>Overview</span>
|
|
</a>
|
|
<a class="nav-item" data-view="ridesView">
|
|
<i class="ph ph-path"></i>
|
|
<span>Rides</span>
|
|
</a>
|
|
<a class="nav-item" data-view="driversView">
|
|
<i class="ph ph-steering-wheel"></i>
|
|
<span>Captains</span>
|
|
</a>
|
|
<a class="nav-item" data-view="passengersView">
|
|
<i class="ph ph-users"></i>
|
|
<span>Passengers</span>
|
|
</a>
|
|
|
|
<div class="menu-label">Business</div>
|
|
<a class="nav-item" data-view="financialsView">
|
|
<i class="ph ph-currency-circle-dollar"></i>
|
|
<span>Financials</span>
|
|
</a>
|
|
<a class="nav-item" data-view="approvalsView">
|
|
<i class="ph ph-user-check"></i>
|
|
<span>Approvals</span>
|
|
<span class="nav-count" id="approvalsCount" hidden>0</span>
|
|
</a>
|
|
<a class="nav-item" data-view="complaintsView">
|
|
<i class="ph ph-warning-circle"></i>
|
|
<span>Complaints</span>
|
|
</a>
|
|
<a class="nav-item" data-view="systemView">
|
|
<i class="ph ph-shield-check"></i>
|
|
<span>Session & Security</span>
|
|
</a>
|
|
</nav>
|
|
|
|
<div class="sidebar-footer">
|
|
<div class="user-profile-mini">
|
|
<div class="avatar" id="userAvatar">SA</div>
|
|
<div class="user-info">
|
|
<div class="name" id="userName">Admin</div>
|
|
<div class="role" id="userRole">—</div>
|
|
</div>
|
|
</div>
|
|
<button class="btn-icon" id="logoutBtn" title="Sign Out">
|
|
<i class="ph ph-sign-out"></i>
|
|
</button>
|
|
</div>
|
|
</aside>
|
|
|
|
<!-- Main Content Body -->
|
|
<div class="main-wrapper">
|
|
|
|
<!-- Top Sticky Header -->
|
|
<header class="top-header">
|
|
<div class="header-left">
|
|
<button class="btn-icon toggle-sidebar-btn" id="toggleSidebar">
|
|
<i class="ph ph-list"></i>
|
|
</button>
|
|
<div class="search-box">
|
|
<i class="ph ph-magnifying-glass"></i>
|
|
<input type="text" id="globalSearch" placeholder="Filter the current table…">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="header-right">
|
|
<div class="live-indicator" id="connectionPill">
|
|
<span class="pulse-dot"></span> <span id="connectionText">Connecting…</span>
|
|
</div>
|
|
<button class="btn-icon" id="refreshBtn" title="Refresh data">
|
|
<i class="ph ph-arrows-clockwise"></i>
|
|
</button>
|
|
</div>
|
|
</header>
|
|
|
|
<main class="content-body">
|
|
|
|
<!-- ================= 1. OVERVIEW ================= -->
|
|
<section class="page-view active" id="dashboardView">
|
|
<div class="page-header">
|
|
<div class="page-title">
|
|
<h1>Operations Overview</h1>
|
|
<p>Live figures read directly from the production database</p>
|
|
</div>
|
|
<div class="page-actions">
|
|
<span class="stamp" id="lastUpdated">—</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Primary KPIs -->
|
|
<div class="stats-grid">
|
|
<div class="stat-card">
|
|
<div class="stat-header">
|
|
<div>
|
|
<div class="stat-value" data-kpi="rides">—</div>
|
|
<div class="stat-title">Total Rides</div>
|
|
</div>
|
|
<div class="stat-icon primary"><i class="ph ph-path"></i></div>
|
|
</div>
|
|
<div class="stat-footer">
|
|
<span class="trend-badge up"><i class="ph ph-plus"></i> <span data-kpi="ridesMonth">—</span></span>
|
|
<span class="trend-text">this month</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stat-card">
|
|
<div class="stat-header">
|
|
<div>
|
|
<div class="stat-value" data-kpi="completed">—</div>
|
|
<div class="stat-title">Completed Rides</div>
|
|
</div>
|
|
<div class="stat-icon success"><i class="ph ph-check-circle"></i></div>
|
|
</div>
|
|
<div class="stat-footer">
|
|
<span class="trend-badge up"><i class="ph ph-percent"></i> <span data-kpi="completionRate">—</span></span>
|
|
<span class="trend-text">completion rate</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stat-card">
|
|
<div class="stat-header">
|
|
<div>
|
|
<div class="stat-value" data-kpi="drivers">—</div>
|
|
<div class="stat-title">Registered Captains</div>
|
|
</div>
|
|
<div class="stat-icon info"><i class="ph ph-steering-wheel"></i></div>
|
|
</div>
|
|
<div class="stat-footer">
|
|
<span class="trend-badge up"><i class="ph ph-plus"></i> <span data-kpi="driversMonth">—</span></span>
|
|
<span class="trend-text">joined this month</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stat-card">
|
|
<div class="stat-header">
|
|
<div>
|
|
<div class="stat-value" data-kpi="passengers">—</div>
|
|
<div class="stat-title">Registered Passengers</div>
|
|
</div>
|
|
<div class="stat-icon warning"><i class="ph ph-users"></i></div>
|
|
</div>
|
|
<div class="stat-footer">
|
|
<span class="trend-badge up"><i class="ph ph-plus"></i> <span data-kpi="passengersMonth">—</span></span>
|
|
<span class="trend-text">joined this month</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Charts -->
|
|
<div class="dashboard-grid">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3 class="card-title">Rides per Day <span class="card-sub">last 21 days</span></h3>
|
|
</div>
|
|
<div class="chart-container">
|
|
<canvas id="ridesTrendChart"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3 class="card-title">Ride Outcomes</h3>
|
|
</div>
|
|
<div class="chart-container donut-wrap">
|
|
<canvas id="statusDonut"></canvas>
|
|
</div>
|
|
<div class="legend" id="statusLegend"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="dashboard-grid">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3 class="card-title">Demand by Time of Day</h3>
|
|
</div>
|
|
<div class="chart-container" style="height:200px;">
|
|
<canvas id="timeOfDayChart"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3 class="card-title">Service Mix</h3>
|
|
</div>
|
|
<div class="mini-list" id="serviceMix"></div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ================= 2. RIDES ================= -->
|
|
<section class="page-view" id="ridesView">
|
|
<div class="page-header">
|
|
<div class="page-title">
|
|
<h1>Rides</h1>
|
|
<p>Trips, assigned captains, fares and live status</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="filter-bar">
|
|
<div class="filter-inputs">
|
|
<select class="select-input" id="rideStatusFilter">
|
|
<option value="All">All statuses</option>
|
|
<option value="Pending">Waiting for a captain</option>
|
|
<option value="Begin">In progress</option>
|
|
<option value="Completed">Completed</option>
|
|
<option value="Canceled">Cancelled</option>
|
|
</select>
|
|
<div class="search-inline">
|
|
<input type="text" class="form-input" id="ridesSearch" placeholder="Search rides by phone number">
|
|
<button class="btn btn-secondary btn-sm" id="ridesSearchBtn"><i class="ph ph-magnifying-glass"></i></button>
|
|
</div>
|
|
</div>
|
|
<span class="stamp" id="ridesMeta">—</span>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="table-responsive">
|
|
<table class="data-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Trip</th>
|
|
<th>Passenger</th>
|
|
<th>Captain</th>
|
|
<th>Route</th>
|
|
<th>Fare</th>
|
|
<th>Status</th>
|
|
<th>Created</th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="ridesTableBody"></tbody>
|
|
</table>
|
|
</div>
|
|
<div class="load-more">
|
|
<button class="btn btn-secondary btn-sm" id="ridesMore" hidden>
|
|
<i class="ph ph-plus"></i> Show more
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ================= 3. CAPTAINS ================= -->
|
|
<section class="page-view" id="driversView">
|
|
<div class="page-header">
|
|
<div class="page-title">
|
|
<h1>Captains</h1>
|
|
<p>Fleet roster, ratings and trip volume</p>
|
|
</div>
|
|
<div class="page-actions">
|
|
<button class="btn btn-secondary btn-sm" id="driversPrev"><i class="ph ph-caret-left"></i></button>
|
|
<span class="stamp" id="driversMeta">—</span>
|
|
<button class="btn btn-secondary btn-sm" id="driversNext"><i class="ph ph-caret-right"></i></button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="filter-bar">
|
|
<div class="search-inline">
|
|
<input type="text" class="form-input" id="driversSearch" placeholder="Search by captain ID, phone or email">
|
|
<button class="btn btn-secondary btn-sm" id="driversSearchBtn"><i class="ph ph-magnifying-glass"></i></button>
|
|
</div>
|
|
<span class="stamp">Click any row to open the full profile</span>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="table-responsive">
|
|
<table class="data-table">
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>Name</th>
|
|
<th>Phone</th>
|
|
<th>Email</th>
|
|
<th>Rating</th>
|
|
<th>Trips</th>
|
|
<th>Cancellations</th>
|
|
<th>Status</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="driversTableBody"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ================= 4. PASSENGERS ================= -->
|
|
<section class="page-view" id="passengersView">
|
|
<div class="page-header">
|
|
<div class="page-title">
|
|
<h1>Passengers</h1>
|
|
<p>Most active customer accounts by trip count</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="filter-bar">
|
|
<div class="search-inline">
|
|
<input type="text" class="form-input" id="passengersSearch" placeholder="Search by passenger ID, phone or email">
|
|
<button class="btn btn-secondary btn-sm" id="passengersSearchBtn"><i class="ph ph-magnifying-glass"></i></button>
|
|
</div>
|
|
<span class="stamp">Click any row to open the full profile</span>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="table-responsive">
|
|
<table class="data-table">
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>Name</th>
|
|
<th>Contact</th>
|
|
<th>Trips</th>
|
|
<th>Rating</th>
|
|
<th>Cancellations</th>
|
|
<th>Joined</th>
|
|
<th>Status</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="passengersTableBody"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ================= 5. FINANCIALS ================= -->
|
|
<section class="page-view" id="financialsView">
|
|
<div class="page-header">
|
|
<div class="page-title">
|
|
<h1>Financials</h1>
|
|
<p>Earnings, fares and distance aggregates across all recorded trips</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stats-grid">
|
|
<div class="stat-card">
|
|
<div class="stat-value" data-kpi="driverEarnings">—</div>
|
|
<div class="stat-title">Captain earnings (completed trips)</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value" data-kpi="avgFare">—</div>
|
|
<div class="stat-title">Average passenger fare</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value" data-kpi="totalDistance">—</div>
|
|
<div class="stat-title">Total distance covered</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-header"><h3 class="card-title">Trip Performance</h3></div>
|
|
<div class="mini-list" id="tripPerformance"></div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ================= 6. APPROVALS ================= -->
|
|
<section class="page-view" id="approvalsView">
|
|
<div class="page-header">
|
|
<div class="page-title">
|
|
<h1>Pending Approvals</h1>
|
|
<p>Admin and service accounts awaiting activation</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="table-responsive">
|
|
<table class="data-table">
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>Name</th>
|
|
<th>Phone</th>
|
|
<th>Type</th>
|
|
<th>Requested role</th>
|
|
<th>Requested at</th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="approvalsTableBody"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ================= 7. COMPLAINTS ================= -->
|
|
<section class="page-view" id="complaintsView">
|
|
<div class="page-header">
|
|
<div class="page-title">
|
|
<h1>Complaints</h1>
|
|
<p>Volume of customer complaints filed</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stats-grid">
|
|
<div class="stat-card">
|
|
<div class="stat-value" data-kpi="complaintsToday">—</div>
|
|
<div class="stat-title">Filed today</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value" data-kpi="complaintsWeek">—</div>
|
|
<div class="stat-title">This week</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value" data-kpi="complaintsMonth">—</div>
|
|
<div class="stat-title">This month</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ================= 8. SESSION & SECURITY ================= -->
|
|
<section class="page-view" id="systemView">
|
|
<div class="page-header">
|
|
<div class="page-title">
|
|
<h1>Session & Security</h1>
|
|
<p>Details of the credential currently authorising this console</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-header"><h3 class="card-title">Current session</h3></div>
|
|
<div class="mini-list" id="sessionInfo"></div>
|
|
</div>
|
|
|
|
<div class="card" data-requires-super hidden>
|
|
<div class="card-header">
|
|
<h3 class="card-title">Send WhatsApp message <span class="card-sub">super admin</span></h3>
|
|
</div>
|
|
<p class="card-note">
|
|
Sends a single message through the platform's WhatsApp provider. This leaves the system
|
|
and reaches a real person — you will be asked to confirm before it is sent.
|
|
</p>
|
|
<div class="form-group">
|
|
<input type="text" class="form-input" id="waReceiver" placeholder="Recipient phone, e.g. 962798583052" style="padding-left:1rem;">
|
|
</div>
|
|
<div class="form-group">
|
|
<textarea class="form-input decrypt-area" id="waMessage" rows="3" placeholder="Message text"></textarea>
|
|
</div>
|
|
<div class="api-base-row">
|
|
<button class="btn btn-secondary btn-sm" id="waSendBtn"><i class="ph ph-paper-plane-tilt"></i> <span>Send message</span></button>
|
|
<span class="stamp" id="waStatus"></span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card" data-requires-super hidden>
|
|
<div class="card-header">
|
|
<h3 class="card-title">Encryption tool <span class="card-sub">super admin</span></h3>
|
|
</div>
|
|
<p class="card-note">
|
|
Decrypts a value taken straight from the database (or encrypts one for a manual query).
|
|
The server authorises this by admin phone number against its own allow-list.
|
|
</p>
|
|
<div class="form-group">
|
|
<input type="text" class="form-input" id="decryptPhone" placeholder="Your admin phone (as registered on the server)" style="padding-left:1rem;">
|
|
</div>
|
|
<div class="form-group">
|
|
<textarea class="form-input decrypt-area" id="decryptInput" rows="3" placeholder="Paste the encrypted (or plain) value here"></textarea>
|
|
</div>
|
|
<div class="api-base-row">
|
|
<button class="btn btn-secondary btn-sm" id="decryptBtn" data-action="decrypt"><i class="ph ph-lock-open"></i> <span>Decrypt</span></button>
|
|
<button class="btn btn-secondary btn-sm" id="encryptBtn" data-action="encrypt"><i class="ph ph-lock"></i> Encrypt</button>
|
|
</div>
|
|
<pre class="diagnostics" id="decryptOutput" style="margin-top:1rem;">Result appears here.</pre>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3 class="card-title">API endpoint</h3>
|
|
</div>
|
|
<p class="card-note">
|
|
The mobile admin app points at a different host per country. If every panel is empty,
|
|
the console is most likely calling a backend that does not exist on this domain.
|
|
</p>
|
|
<div class="api-base-row">
|
|
<select class="select-input" id="apiBaseSelect"></select>
|
|
<input type="text" class="form-input" id="apiBaseCustom" placeholder="https://host/backend" hidden>
|
|
<button class="btn btn-primary btn-sm" id="saveApiBaseBtn"><i class="ph ph-check"></i> Apply</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3 class="card-title">Server diagnostics</h3>
|
|
<div style="display:flex; gap:0.5rem;">
|
|
<button class="btn btn-secondary btn-sm" id="copyDiagnosticsBtn"><i class="ph ph-copy"></i> Copy</button>
|
|
<button class="btn btn-secondary btn-sm" id="runDiagnosticsBtn"><i class="ph ph-play"></i> <span>Run diagnostics</span></button>
|
|
</div>
|
|
</div>
|
|
<pre class="diagnostics" id="diagnosticsOutput">Press “Run diagnostics” to call every endpoint and print the raw server reply.</pre>
|
|
</div>
|
|
</section>
|
|
|
|
</main>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Details Modal -->
|
|
<div class="modal-overlay" id="detailsModal">
|
|
<div class="modal-content">
|
|
<div id="modalBodyContent"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- OTP Verification Modal -->
|
|
<div class="modal-overlay" id="otpModal">
|
|
<div class="modal-content" style="max-width: 400px; text-align: center;">
|
|
<div class="otp-icon"><i class="ph ph-whatsapp-logo"></i></div>
|
|
<h3 style="color: #fff; margin-bottom: 0.5rem;">WhatsApp Verification</h3>
|
|
<p style="color: var(--text-muted); font-size: 0.88rem; margin-bottom: 1.5rem;" id="otpPhoneText">
|
|
A 3-digit verification code was sent to your WhatsApp.
|
|
</p>
|
|
|
|
<div style="display: flex; gap: 0.5rem; justify-content: center; margin-bottom: 1.5rem;">
|
|
<input type="text" maxlength="3" inputmode="numeric" id="otpInput" class="form-input otp-input" placeholder="123">
|
|
</div>
|
|
|
|
<button type="button" class="btn-primary" id="submitOtpBtn">
|
|
<i class="ph-bold ph-check"></i> <span>Verify & Sign In</span>
|
|
</button>
|
|
|
|
<button type="button" class="btn btn-secondary" onclick="closeOtpModal()" style="width: 100%; margin-top: 0.75rem; justify-content: center;">
|
|
Cancel
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="js/app.js"></script>
|
|
</body>
|
|
</html>
|