Add removal/blacklist tools, Arabic UI, and a persistent sign-in error
Authorisation: driver/deleteCaptain.php permanently deletes a captain and had no role check at all — any valid token, including a driver's or passenger's, could delete captains. It now requires super_admin. The two unblacklist endpoints had no role check either and now require an admin role. Console: - Blacklist & removal screen (super admin only): current blocks, permanent removal, and lifting a block. Deletion additionally requires the phone number to be retyped, because the row is deleted outright. - Arabic interface with full RTL layout and a language toggle. Sidebar, content offset, input affordances, toasts and directional icons all mirror; numbers, identifiers and the diagnostics output stay LTR. - Sign-in failures now render in the form and stay there. They were only shown as a toast, which disappears before it can be read — a locked account or a rejected device looked like the page simply returning to the login screen with no explanation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
42f6d33efd
commit
81ee2acefd
@@ -15,7 +15,7 @@
|
||||
<!-- ?v= must be bumped whenever css/main.css or js/app.js changes: the files
|
||||
are served straight off a bind mount, so without it browsers keep
|
||||
running the previously cached build after a deploy. -->
|
||||
<link rel="stylesheet" href="css/main.css?v=2026-07-25-2">
|
||||
<link rel="stylesheet" href="css/main.css?v=2026-07-25-6">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -27,17 +27,17 @@
|
||||
<i class="ph-bold ph-car"></i>
|
||||
<span>SIRO ADMIN</span>
|
||||
</div>
|
||||
<p class="auth-subtitle">Operations & Control Console</p>
|
||||
<p class="auth-subtitle" data-i18n="Operations & Control Console">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>
|
||||
<span data-i18n="Secure session · device-bound token · WhatsApp OTP">Secure session · device-bound token · WhatsApp OTP</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="loginEmail">Admin Email / Phone</label>
|
||||
<label class="form-label" for="loginEmail" data-i18n="Admin Email / Phone">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>
|
||||
@@ -45,15 +45,19 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="loginPass">Password</label>
|
||||
<label class="form-label" for="loginPass" data-i18n="Password">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>
|
||||
|
||||
<!-- Stays on screen until the next attempt: a toast disappears before
|
||||
the operator can read why the sign-in failed. -->
|
||||
<div class="login-error" id="loginError" hidden></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>
|
||||
<i class="ph-bold ph-sign-in"></i> <span data-i18n="Sign In">Sign In</span>
|
||||
</button>
|
||||
|
||||
<div class="device-note">
|
||||
@@ -80,41 +84,41 @@
|
||||
</div>
|
||||
|
||||
<nav class="sidebar-menu">
|
||||
<div class="menu-label">Operations</div>
|
||||
<div class="menu-label" data-i18n="Operations">Operations</div>
|
||||
<a class="nav-item active" data-view="dashboardView">
|
||||
<i class="ph ph-squares-four"></i>
|
||||
<span>Overview</span>
|
||||
<span data-i18n="Overview">Overview</span>
|
||||
</a>
|
||||
<a class="nav-item" data-view="ridesView">
|
||||
<i class="ph ph-path"></i>
|
||||
<span>Rides</span>
|
||||
<span data-i18n="Rides">Rides</span>
|
||||
</a>
|
||||
<a class="nav-item" data-view="driversView">
|
||||
<i class="ph ph-steering-wheel"></i>
|
||||
<span>Captains</span>
|
||||
<span data-i18n="Captains">Captains</span>
|
||||
</a>
|
||||
<a class="nav-item" data-view="passengersView">
|
||||
<i class="ph ph-users"></i>
|
||||
<span>Passengers</span>
|
||||
<span data-i18n="Passengers">Passengers</span>
|
||||
</a>
|
||||
|
||||
<div class="menu-label">Business</div>
|
||||
<div class="menu-label" data-i18n="Business">Business</div>
|
||||
<a class="nav-item" data-view="financialsView">
|
||||
<i class="ph ph-currency-circle-dollar"></i>
|
||||
<span>Financials</span>
|
||||
<span data-i18n="Financials">Financials</span>
|
||||
</a>
|
||||
<a class="nav-item" data-view="approvalsView">
|
||||
<i class="ph ph-user-check"></i>
|
||||
<span>Approvals</span>
|
||||
<span data-i18n="Approvals">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>
|
||||
<span data-i18n="Complaints">Complaints</span>
|
||||
</a>
|
||||
<a class="nav-item" data-view="systemView">
|
||||
<i class="ph ph-shield-check"></i>
|
||||
<span>Session & Security</span>
|
||||
<span data-i18n="Session & Security">Session & Security</span>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
@@ -126,7 +130,7 @@
|
||||
<div class="role" id="userRole">—</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn-icon" id="logoutBtn" title="Sign Out">
|
||||
<button class="btn-icon" id="logoutBtn" title="Sign Out" data-i18n-title="Sign Out">
|
||||
<i class="ph ph-sign-out"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -143,15 +147,16 @@
|
||||
</button>
|
||||
<div class="search-box">
|
||||
<i class="ph ph-magnifying-glass"></i>
|
||||
<input type="text" id="globalSearch" placeholder="Filter the current table…">
|
||||
<input type="text" id="globalSearch" placeholder="Filter the current table…" data-i18n-ph="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>
|
||||
<span class="pulse-dot"></span> <span id="connectionText" data-i18n="Connecting…">Connecting…</span>
|
||||
</div>
|
||||
<button class="btn-icon" id="refreshBtn" title="Refresh data">
|
||||
<button class="btn-icon lang-toggle" id="langToggle" title="العربية / English">ع</button>
|
||||
<button class="btn-icon" id="refreshBtn" title="Refresh data" data-i18n-title="Refresh data">
|
||||
<i class="ph ph-arrows-clockwise"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -163,8 +168,8 @@
|
||||
<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>
|
||||
<h1 data-i18n="Operations Overview">Operations Overview</h1>
|
||||
<p data-i18n="Live figures read directly from the production database">Live figures read directly from the production database</p>
|
||||
</div>
|
||||
<div class="page-actions">
|
||||
<span class="stamp" id="lastUpdated">—</span>
|
||||
@@ -177,13 +182,13 @@
|
||||
<div class="stat-header">
|
||||
<div>
|
||||
<div class="stat-value" data-kpi="rides">—</div>
|
||||
<div class="stat-title">Total Rides</div>
|
||||
<div class="stat-title" data-i18n="Total Rides">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>
|
||||
<span class="trend-text" data-i18n="this month">this month</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -191,13 +196,13 @@
|
||||
<div class="stat-header">
|
||||
<div>
|
||||
<div class="stat-value" data-kpi="completed">—</div>
|
||||
<div class="stat-title">Completed Rides</div>
|
||||
<div class="stat-title" data-i18n="Completed Rides">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>
|
||||
<span class="trend-text" data-i18n="completion rate">completion rate</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -205,13 +210,13 @@
|
||||
<div class="stat-header">
|
||||
<div>
|
||||
<div class="stat-value" data-kpi="drivers">—</div>
|
||||
<div class="stat-title">Registered Captains</div>
|
||||
<div class="stat-title" data-i18n="Registered Captains">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>
|
||||
<span class="trend-text" data-i18n="joined this month">joined this month</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -219,13 +224,13 @@
|
||||
<div class="stat-header">
|
||||
<div>
|
||||
<div class="stat-value" data-kpi="passengers">—</div>
|
||||
<div class="stat-title">Registered Passengers</div>
|
||||
<div class="stat-title" data-i18n="Registered Passengers">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>
|
||||
<span class="trend-text" data-i18n="joined this month">joined this month</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -234,7 +239,7 @@
|
||||
<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>
|
||||
<h3 class="card-title"><span data-i18n="Rides per Day">Rides per Day</span> <span class="card-sub" data-i18n="last 21 days">last 21 days</span></h3>
|
||||
</div>
|
||||
<div class="chart-container">
|
||||
<canvas id="ridesTrendChart"></canvas>
|
||||
@@ -243,7 +248,7 @@
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Ride Outcomes</h3>
|
||||
<h3 class="card-title" data-i18n="Ride Outcomes">Ride Outcomes</h3>
|
||||
</div>
|
||||
<div class="chart-container donut-wrap">
|
||||
<canvas id="statusDonut"></canvas>
|
||||
@@ -255,7 +260,7 @@
|
||||
<div class="dashboard-grid">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Demand by Time of Day</h3>
|
||||
<h3 class="card-title" data-i18n="Demand by Time of Day">Demand by Time of Day</h3>
|
||||
</div>
|
||||
<div class="chart-container" style="height:200px;">
|
||||
<canvas id="timeOfDayChart"></canvas>
|
||||
@@ -264,7 +269,7 @@
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Service Mix</h3>
|
||||
<h3 class="card-title" data-i18n="Service Mix">Service Mix</h3>
|
||||
</div>
|
||||
<div class="mini-list" id="serviceMix"></div>
|
||||
</div>
|
||||
@@ -276,7 +281,7 @@
|
||||
<div class="page-header">
|
||||
<div class="page-title">
|
||||
<h1>Rides</h1>
|
||||
<p>Trips, assigned captains, fares and live status</p>
|
||||
<p data-i18n="Trips, assigned captains, fares and live status">Trips, assigned captains, fares and live status</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -302,13 +307,13 @@
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Trip</th>
|
||||
<th>Passenger</th>
|
||||
<th>Captain</th>
|
||||
<th>Route</th>
|
||||
<th>Fare</th>
|
||||
<th data-i18n="Trip">Trip</th>
|
||||
<th data-i18n="Passenger">Passenger</th>
|
||||
<th data-i18n="Captain">Captain</th>
|
||||
<th data-i18n="Route">Route</th>
|
||||
<th data-i18n="Fare">Fare</th>
|
||||
<th>Status</th>
|
||||
<th>Created</th>
|
||||
<th data-i18n="Created">Created</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -328,7 +333,7 @@
|
||||
<div class="page-header">
|
||||
<div class="page-title">
|
||||
<h1>Captains</h1>
|
||||
<p>Fleet roster, ratings and trip volume</p>
|
||||
<p data-i18n="Fleet roster, ratings and trip volume">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>
|
||||
@@ -350,7 +355,7 @@
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th data-i18n="ID">ID</th>
|
||||
<th>Name</th>
|
||||
<th>Phone</th>
|
||||
<th>Email</th>
|
||||
@@ -371,7 +376,7 @@
|
||||
<div class="page-header">
|
||||
<div class="page-title">
|
||||
<h1>Passengers</h1>
|
||||
<p>Most active customer accounts by trip count</p>
|
||||
<p data-i18n="Most active customer accounts by trip count">Most active customer accounts by trip count</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -388,7 +393,7 @@
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th data-i18n="ID">ID</th>
|
||||
<th>Name</th>
|
||||
<th>Contact</th>
|
||||
<th>Trips</th>
|
||||
@@ -409,27 +414,27 @@
|
||||
<div class="page-header">
|
||||
<div class="page-title">
|
||||
<h1>Financials</h1>
|
||||
<p>Earnings, fares and distance aggregates across all recorded trips</p>
|
||||
<p data-i18n="Earnings, fares and distance aggregates across all recorded trips">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 class="stat-title" data-i18n="Captain earnings (completed trips)">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 class="stat-title" data-i18n="Average passenger fare">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 class="stat-title" data-i18n="Total distance covered">Total distance covered</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header"><h3 class="card-title">Trip Performance</h3></div>
|
||||
<div class="card-header"><h3 class="card-title" data-i18n="Trip Performance">Trip Performance</h3></div>
|
||||
<div class="mini-list" id="tripPerformance"></div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -438,8 +443,8 @@
|
||||
<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>
|
||||
<h1 data-i18n="Pending Approvals">Pending Approvals</h1>
|
||||
<p data-i18n="Admin and service accounts awaiting activation">Admin and service accounts awaiting activation</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -448,12 +453,12 @@
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th data-i18n="ID">ID</th>
|
||||
<th>Name</th>
|
||||
<th>Phone</th>
|
||||
<th>Type</th>
|
||||
<th>Requested role</th>
|
||||
<th>Requested at</th>
|
||||
<th data-i18n="Type">Type</th>
|
||||
<th data-i18n="Requested role">Requested role</th>
|
||||
<th data-i18n="Requested at">Requested at</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -468,22 +473,22 @@
|
||||
<div class="page-header">
|
||||
<div class="page-title">
|
||||
<h1>Complaints</h1>
|
||||
<p>Volume of customer complaints filed</p>
|
||||
<p data-i18n="Volume of customer complaints filed">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 class="stat-title" data-i18n="Filed today">Filed today</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" data-kpi="complaintsWeek">—</div>
|
||||
<div class="stat-title">This week</div>
|
||||
<div class="stat-title" data-i18n="This week">This week</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" data-kpi="complaintsMonth">—</div>
|
||||
<div class="stat-title">This month</div>
|
||||
<div class="stat-title" data-i18n="This month">This month</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -493,12 +498,12 @@
|
||||
<div class="page-header">
|
||||
<div class="page-title">
|
||||
<h1>Session & Security</h1>
|
||||
<p>Details of the credential currently authorising this console</p>
|
||||
<p data-i18n="Details of the credential currently authorising this console">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="card-header"><h3 class="card-title" data-i18n="Current session">Current session</h3></div>
|
||||
<div class="mini-list" id="sessionInfo"></div>
|
||||
</div>
|
||||
|
||||
@@ -545,7 +550,7 @@
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">API endpoint</h3>
|
||||
<h3 class="card-title" data-i18n="API endpoint">API endpoint</h3>
|
||||
</div>
|
||||
<p class="card-note">
|
||||
The mobile admin app points at a different host per country. If every panel is empty,
|
||||
@@ -560,7 +565,7 @@
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Server diagnostics</h3>
|
||||
<h3 class="card-title" data-i18n="Server diagnostics">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>
|
||||
@@ -585,7 +590,7 @@
|
||||
<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>
|
||||
<h3 style="color: #fff; margin-bottom: 0.5rem;" data-i18n="WhatsApp Verification">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>
|
||||
@@ -595,7 +600,7 @@
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn-primary" id="submitOtpBtn">
|
||||
<i class="ph-bold ph-check"></i> <span>Verify & Sign In</span>
|
||||
<i class="ph-bold ph-check"></i> <span data-i18n="Verify & Sign In">Verify & Sign In</span>
|
||||
</button>
|
||||
|
||||
<button type="button" class="btn btn-secondary" onclick="closeOtpModal()" style="width: 100%; margin-top: 0.75rem; justify-content: center;">
|
||||
@@ -604,6 +609,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="js/app.js?v=2026-07-25-2"></script>
|
||||
<script src="js/app.js?v=2026-07-25-6"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user