- Fixed 30 EJS views: changed ../partials/ to ../../partials/ for views in subdirectories
(pages/loads/, pages/shippers/, pages/portal/, pages/marketplace/, pages/payments/, etc.)
- Fixed layouts/main.ejs: corrected malformed EJS tags on lines 11 and 66
(<% ... { <% → <% ... { %>)
143 lines
5.5 KiB
Text
143 lines
5.5 KiB
Text
<%- include('../../partials/header', { activeMenu: 'portal-users' }) %>
|
|
|
|
<div class="page-header">
|
|
<div>
|
|
<h1 class="page-title">👥 Portal Users</h1>
|
|
<p class="page-subtitle">Manage shipper and driver portal access</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Create New Portal User -->
|
|
<% if (typeof availableShippers !== 'undefined') { %>
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h3 class="card-title">Create Portal Account</h3>
|
|
</div>
|
|
<div class="card-body">
|
|
<form method="POST" action="/portal-users" class="row" style="gap:16px;align-items:flex-end;">
|
|
<div class="form-group" style="flex:2;">
|
|
<label class="form-label">Role</label>
|
|
<select name="role" class="form-input" id="roleSelect" onchange="toggleRoleSelects()" required>
|
|
<option value="">Select role...</option>
|
|
<option value="shipper">Shipper</option>
|
|
<option value="driver">Driver</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group" style="flex:2;">
|
|
<label class="form-label">Username / Phone</label>
|
|
<input type="text" name="username" class="form-input" required placeholder="Phone number or email">
|
|
</div>
|
|
<div class="form-group" style="flex:1;">
|
|
<label class="form-label">Password</label>
|
|
<input type="text" name="password" class="form-input" required placeholder="Min 6 chars" minlength="6">
|
|
</div>
|
|
<div class="form-group" style="flex:2;">
|
|
<label class="form-label">Shipper</label>
|
|
<select name="shipper_id" class="form-input" id="shipperSelect" disabled>
|
|
<option value="">Select shipper...</option>
|
|
<% for (const s of availableShippers) { %>
|
|
<option value="<%= s.id %>"><%= s.name %></option>
|
|
<% } %>
|
|
</select>
|
|
</div>
|
|
<div class="form-group" style="flex:2;">
|
|
<label class="form-label">Driver / Vehicle</label>
|
|
<select name="driver_id" class="form-input" id="driverSelect" disabled>
|
|
<option value="">Select driver...</option>
|
|
<% for (const d of availableDrivers) { %>
|
|
<option value="<%= d.id %>"><%= d.number %> <%= d.driver_name ? '(' + d.driver_name + ')' : '' %></option>
|
|
<% } %>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<button type="submit" class="btn btn-primary">Create</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<% } %>
|
|
|
|
<!-- Existing Portal Users -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3 class="card-title">Existing Portal Accounts (<%= users.length %>)</h3>
|
|
</div>
|
|
<div class="card-body">
|
|
<% if (users.length === 0) { %>
|
|
<p class="empty-state">No portal accounts created yet.</p>
|
|
<% } else { %>
|
|
<div class="table-responsive">
|
|
<table class="table">
|
|
<thead>
|
|
<tr>
|
|
<th>Username</th>
|
|
<th>Role</th>
|
|
<th>Linked To</th>
|
|
<th>Status</th>
|
|
<th>Created</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<% for (const user of users) { %>
|
|
<tr>
|
|
<td><strong><%= user.username %></strong></td>
|
|
<td><span class="badge badge-<%= user.role === 'shipper' ? 'blue' : 'green' %>"><%= user.role %></span></td>
|
|
<td>
|
|
<% if (user.role === 'shipper' && user.shipper) { %>
|
|
<%= user.shipper.name %>
|
|
<% } else if (user.role === 'driver' && user.driver) { %>
|
|
<%= user.driver.number %>
|
|
<% } else { %>
|
|
<span class="text-muted">—</span>
|
|
<% } %>
|
|
</td>
|
|
<td>
|
|
<span class="badge badge-<%= user.is_active ? 'success' : 'danger' %>">
|
|
<%= user.is_active ? 'Active' : 'Disabled' %>
|
|
</span>
|
|
</td>
|
|
<td><%= user.created_at ? new Date(user.created_at).toLocaleDateString('en-IN') : '—' %></td>
|
|
<td>
|
|
<button class="btn btn-sm btn-outline" onclick="toggleUser('<%= user.id %>', <%= user.is_active %>)">
|
|
<%= user.is_active ? 'Disable' : 'Enable' %>
|
|
</button>
|
|
<button class="btn btn-sm btn-outline" onclick="resetPassword('<%= user.id %>')">Reset PW</button>
|
|
</td>
|
|
</tr>
|
|
<% } %>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<% } %>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function toggleRoleSelects() {
|
|
const role = document.getElementById('roleSelect').value;
|
|
document.getElementById('shipperSelect').disabled = role !== 'shipper';
|
|
document.getElementById('driverSelect').disabled = role !== 'driver';
|
|
if (role !== 'shipper') document.getElementById('shipperSelect').value = '';
|
|
if (role !== 'driver') document.getElementById('driverSelect').value = '';
|
|
}
|
|
|
|
async function toggleUser(id, currentStatus) {
|
|
if (!confirm(currentStatus ? 'Disable this portal account?' : 'Enable this portal account?')) return;
|
|
const res = await fetch(`/portal-users/${id}/toggle`, { method: 'PUT' });
|
|
if (res.ok) location.reload();
|
|
}
|
|
|
|
async function resetPassword(id) {
|
|
const pw = prompt('Enter new password (min 6 chars):');
|
|
if (!pw || pw.length < 6) return alert('Password must be at least 6 characters');
|
|
const res = await fetch(`/portal-users/${id}/reset-password`, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ password: pw }),
|
|
});
|
|
if (res.ok) alert('Password reset successfully');
|
|
}
|
|
</script>
|
|
|
|
<%- include('../../partials/footer') %>
|