<title>Rider Management Dashboard</title>
<style>
/* Reset & Base Styles */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
body {
background-color: #f4f6f9;
color: #333;
padding: 24px;
line-height: 1.5;
}
.dashboard-container {
max-width: 1100px;
margin: 0 auto;
}
/* Header Info Bar */
.info-bar {
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
color: #ffffff;
padding: 16px 20px;
border-radius: 8px;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
gap: 12px;
margin-bottom: 24px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
/* Cards & Layout */
.card {
background: #ffffff;
border-radius: 8px;
padding: 24px;
margin-bottom: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.card h2 {
font-size: 1.25rem;
margin-bottom: 16px;
color: #1a202c;
}
/* Form Styles */
.form-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 16px;
margin-bottom: 20px;
}
.form-group {
display: flex;
flex-direction: column;
}
.form-group label {
font-size: 0.875rem;
font-weight: 600;
margin-bottom: 6px;
color: #4a5568;
}
.form-group input,
.form-group select,
.filter-group input,
.filter-group select {
padding: 10px 12px;
border: 1px solid #cbd5e0;
border-radius: 6px;
font-size: 0.95rem;
outline: none;
transition: border-color 0.2s;
}
.form-group input:focus,
.form-group select:focus,
.filter-group input:focus,
.filter-group select:focus {
border-color: #3182ce;
box-shadow: 0 0 0 3px rgba(49, 130, 206, 0.15);
}
/* Buttons */
.btn {
padding: 10px 20px;
border: none;
border-radius: 6px;
font-weight: 600;
cursor: pointer;
transition: background-color 0.2s, transform 0.1s;
}
.btn:active {
transform: scale(0.98);
}
.btn-primary {
background-color: #2b6cb0;
color: white;
}
.btn-primary:hover {
background-color: #2c5282;
}
.btn-action {
padding: 6px 12px;
font-size: 0.825rem;
margin-right: 4px;
}
.btn-toggle {
background-color: #edf2f7;
color: #2d3748;
}
.btn-toggle:hover {
background-color: #e2e8f0;
}
.btn-delete {
background-color: #fed7d7;
color: #9b2c2c;
}
.btn-delete:hover {
background-color: #feb2b2;
}
.btn-logout {
background-color: #e53e3e;
color: white;
padding: 6px 12px;
font-size: 0.8rem;
border-radius: 4px;
}
.btn-logout:hover {
background-color: #c53030;
}
/* Table Controls & Filtering */
.table-header-controls {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 16px;
margin-bottom: 16px;
}
.filter-group {
display: flex;
gap: 10px;
}
/* Table Design */
.table-wrapper {
overflow-x: auto;
}
table {
width: 100%;
border-collapse: collapse;
text-align: left;
}
th {
background-color: #f7fafc;
color: #4a5568;
font-weight: 600;
font-size: 0.85rem;
text-transform: uppercase;
letter-spacing: 0.05em;
padding: 12px 16px;
border-bottom: 2px solid #e2e8f0;
}
td {
padding: 14px 16px;
border-bottom: 1px solid #e2e8f0;
font-size: 0.95rem;
}
tr:hover {
background-color: #f8fafc;
}
/* Status Badges */
.badge {
display: inline-block;
padding: 4px 10px;
border-radius: 12px;
font-size: 0.825rem;
font-weight: 600;
}
.badge-paid {
background-color: #c6f6d5;
color: #22543d;
}
.badge-pending {
background-color: #feebc8;
color: #744210;
}
.empty-msg {
text-align: center;
color: #a0aec0;
padding: 24px;
}
/* Login Overlay Modal */
.auth-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(15, 23, 42, 0.85);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.auth-card {
background: #ffffff;
padding: 32px;
border-radius: 10px;
width: 100%;
max-width: 400px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
}
.auth-card h2 {
margin-bottom: 8px;
color: #1e3c72;
}
.auth-card p {
color: #64748b;
font-size: 0.9rem;
margin-bottom: 20px;
}
.error-msg {
color: #e53e3e;
font-size: 0.85rem;
margin-top: 10px;
text-align: center;
}
</style>
<!-- Login Modal / Overlay -->
<div id="authOverlay" class="auth-overlay">
<div class="auth-card">
<h2>Admin Login</h2>
<p>Sign in to manage rider tax collections.</p>
<form id="loginForm">
<div class="form-group">
<label for="loginEmail">Email Address</label>
<input type="email" id="loginEmail" value="timothybala8@gmail.com" required>
</div>
<div class="form-group" style="margin-top: 15px;">
<label for="loginPassword">Password</label>
<input type="password" id="loginPassword" placeholder="Enter password" required>
</div>
<button type="submit" class="btn btn-primary" style="width: 100%; margin-top: 20px;">Login</button>
<p id="authError" class="error-msg"></p>
</form>
</div>
</div>
<div class="dashboard-container">
<!-- Info Header -->
<header class="info-bar">
<div class="info-item"><strong>Admin:</strong> <span id="adminEmailDisplay">Not Signed In</span></div>
<div class="info-item"><strong>Tax Collection:</strong> Opay 6103229012 (Timothy Bala Williams)</div>
<div class="info-item">
<strong style="margin-right: 10px;">Fee: ₦5,000/Month</strong>
<button id="logoutBtn" class="btn btn-logout" style="display: none;">Logout</button>
</div>
</header>
<!-- Main Dashboard Content -->
<div id="mainDashboard" style="display: none;">
<!-- Admin Form Card -->
<section class="card">
<h2>Add New Rider</h2>
<form id="addRiderForm">
<div class="form-grid">
<div class="form-group">
<label for="riderName">Rider Name</label>
<input type="text" id="riderName" placeholder="e.g. Usman Ibrahim" required>
</div>
<div class="form-group">
<label for="riderPhone">Phone / WhatsApp</label>
<input type="tel" id="riderPhone" placeholder="e.g. 08012345678" required>
</div>
<div class="form-group">
<label for="membershipId">Membership ID</label>
<input type="text" id="membershipId" placeholder="e.g. NPG-2026-001" required>
</div>
<div class="form-group">
<label for="taxStatus">Tax Status</label>
<select id="taxStatus">
<option value="Paid">Paid</option>
<option value="Pending">Pending</option>
</select>
</div>
</div>
<button type="submit" class="btn btn-primary">Register Rider</button>
</form>
</section>
<!-- Table & Filter Controls -->
<section class="card">
<div class="table-header-controls">
<h2>Registered Riders</h2>
<div class="filter-group">
<input type="text" id="searchInput" placeholder="Search name or ID...">
<select id="statusFilter">
<option value="All">All Statuses</option>
<option value="Paid">Paid</option>
<option value="Pending">Pending</option>
</select>
</div>
</div>
<div class="table-wrapper">
<table id="ridersTable">
<thead>
<tr>
<th>Rider Name</th>
<th>Phone/WhatsApp</th>
<th>Membership ID</th>
<th>Tax Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="tableBody">
<!-- Dynamic JavaScript Rows -->
</tbody>
</table>
</div>
</section>
</div>
</div>
<!-- Firebase SDKs and Application Logic -->
<script type="module">
import { initializeApp } from "https://www.gstatic.com/firebasejs/10.8.0/firebase-app.js";
import {
getDatabase,
ref,
onValue,
push,
update,
remove
} from "https://www.gstatic.com/firebasejs/10.8.0/firebase-database.js";
import {
getAuth,
signInWithEmailAndPassword,
signOut,
onAuthStateChanged
} from "https://www.gstatic.com/firebasejs/10.8.0/firebase-auth.js";
// Your Firebase Configuration (Replace placeholders with your real Firebase keys)
const firebaseConfig = {
apiKey: "YOUR_API_KEY",
authDomain: "napep-go.firebaseapp.com",
databaseURL: "https://napep-go-default-rtdb.firebaseio.com",
projectId: "napep-go",
storageBucket: "napep-go.appspot.com",
messagingSenderId: "YOUR_SENDER_ID",
appId: "YOUR_APP_ID"
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
const db = getDatabase(app);
const auth = getAuth(app);
const ridersRef = ref(db, 'riders');
// Local Data State & Listener Handle
let riders = [];
let unsubscribeRiders = null;
// DOM Elements
const authOverlay = document.getElementById('authOverlay');
const loginForm = document.getElementById('loginForm');
const loginEmail = document.getElementById('loginEmail');
const loginPassword = document.getElementById('loginPassword');
const authError = document.getElementById('authError');
const mainDashboard = document.getElementById('mainDashboard');
const adminEmailDisplay = document.getElementById('adminEmailDisplay');
const logoutBtn = document.getElementById('logoutBtn');
const form = document.getElementById('addRiderForm');
const tableBody = document.getElementById('tableBody');
const searchInput = document.getElementById('searchInput');
const statusFilter = document.getElementById('statusFilter');
// Event Listeners
form.addEventListener('submit', handleAddRider);
searchInput.addEventListener('input', renderTable);
statusFilter.addEventListener('change', renderTable);
logoutBtn.addEventListener('click', handleLogout);
loginForm.addEventListener('submit', handleLogin);
// Authentication State Observer
onAuthStateChanged(auth, (user) => {
if (user) {
authOverlay.style.display = 'none';
mainDashboard.style.display = 'block';
logoutBtn.style.display = 'inline-block';
adminEmailDisplay.textContent = user.email;
startRidersListener();
} else {
authOverlay.style.display = 'flex';
mainDashboard.style.display = 'none';
logoutBtn.style.display = 'none';
adminEmailDisplay.textContent = 'Not Signed In';
if (unsubscribeRiders) unsubscribeRiders();
}
});
// Login Handler
function handleLogin(e) {
e.preventDefault();
authError.textContent = '';
signInWithEmailAndPassword(auth, loginEmail.value.trim(), loginPassword.value)
.then(() => {
loginPassword.value = '';
})
.catch((error) => {
authError.textContent = error.message;
});
}
// Logout Handler
function handleLogout() {
signOut(auth).catch((error) => {
console.error("Sign out error:", error);
});
}
// Real-Time Database Sync
function startRidersListener() {
unsubscribeRiders = onValue(ridersRef, (snapshot) => {
const data = snapshot.val();
riders = [];
if (data) {
Object.keys(data).forEach(key => {
riders.push({
id: key,
...data[key]
});
});
}
renderTable();
});
}
// Render Table
function renderTable() {
const searchTerm = searchInput.value.toLowerCase();
const filterValue = statusFilter.value;
tableBody.innerHTML = '';
const filteredRiders = riders.filter(rider => {
const matchesSearch = (rider.name && rider.name.toLowerCase().includes(searchTerm)) ||
(rider.memberId && rider.memberId.toLowerCase().includes(searchTerm));
const matchesStatus = filterValue === 'All' || rider.status === filterValue;
return matchesSearch && matchesStatus;
});
if (filteredRiders.length === 0) {
tableBody.innerHTML = `<tr><td colspan="5" class="empty-msg">No riders found in database.</td></tr>`;
return;
}
filteredRiders.forEach(rider => {
const tr = document.createElement('tr');
const badgeClass = rider.status === 'Paid' ? 'badge-paid' : 'badge-pending';
tr.innerHTML = `
<td><strong>${escapeHtml(rider.name)}</strong></td>
<td>${escapeHtml(rider.phone)}</td>
<td><code>${escapeHtml(rider.memberId)}</code></td>
<td><span class="badge ${badgeClass}">${rider.status}</span></td>
<td>
<button class="btn btn-action btn-toggle" data-id="${rider.id}" data-status="${rider.status}">Toggle Status</button>
<button class="btn btn-action btn-delete" data-id="${rider.id}">Delete</button>
</td>
`;
tableBody.appendChild(tr);
});
document.querySelectorAll('.btn-toggle').forEach(btn => {
btn.addEventListener('click', (e) => {
toggleTaxStatus(e.target.getAttribute('data-id'), e.target.getAttribute('data-status'));
});
});
document.querySelectorAll('.btn-delete').forEach(btn => {
btn.addEventListener('click', (e) => {
deleteRider(e.target.getAttribute('data-id'));
});
});
}
// Database Operations
function handleAddRider(e) {
e.preventDefault();
const newRider = {
name: document.getElementById('riderName').value.trim(),
phone: document.getElementById('riderPhone').value.trim(),
memberId: document.getElementById('membershipId').value.trim(),
status: document.getElementById('taxStatus').value,
createdAt: Date.now()
};
push(ridersRef, newRider)
.then(() => form.reset())
.catch(error => alert("Failed to save rider: " + error.message));
}
function toggleTaxStatus(id, currentStatus) {
const newStatus = currentStatus === 'Paid' ? 'Pending' : 'Paid';
update(ref(db, `riders/${id}`), { status: newStatus });
}
function deleteRider(id) {
if (confirm('Are you sure you want to remove this rider from the database?')) {
remove(ref(db, `riders/${id}`));
}
}
// Helper to prevent HTML Injection
function escapeHtml(str) {
return (str || '').replace(/[&<>"']/g, match => ({
'&': '&', '<': '<', '>': '>', '"': '"', "'": '''
}[match]));
}
</script>
Run these code