Autosave: 20260307-172929

This commit is contained in:
Flatlogic Bot 2026-03-07 17:29:29 +00:00
parent 41b182bfba
commit 8bc413c26a
16 changed files with 2035 additions and 425 deletions

226
admin_cities.php Normal file
View File

@ -0,0 +1,226 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/includes/layout.php';
$errors = [];
$flash = null;
$editCityId = isset($_GET['edit_city']) ? (int)$_GET['edit_city'] : 0;
db()->exec("
CREATE TABLE IF NOT EXISTS countries (
id INT AUTO_INCREMENT PRIMARY KEY,
name_en VARCHAR(255) NOT NULL,
name_ar VARCHAR(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
");
db()->exec("
CREATE TABLE IF NOT EXISTS cities (
id INT AUTO_INCREMENT PRIMARY KEY,
country_id INT NOT NULL,
name_en VARCHAR(255) NOT NULL,
name_ar VARCHAR(255) DEFAULT NULL,
UNIQUE KEY uniq_city_country (country_id, name_en),
CONSTRAINT fk_cities_country FOREIGN KEY (country_id) REFERENCES countries(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
");
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['add_city'])) {
$countryId = (int)($_POST['country_id'] ?? 0);
$cityNameEn = trim($_POST['city_name_en'] ?? '');
$cityNameAr = trim($_POST['city_name_ar'] ?? '');
if ($countryId <= 0 || $cityNameEn === '') {
$errors[] = 'Please select a country and provide city name (English).';
} else {
try {
$stmt = db()->prepare("INSERT INTO cities (country_id, name_en, name_ar) VALUES (?, ?, ?)");
$stmt->execute([$countryId, $cityNameEn, $cityNameAr !== '' ? $cityNameAr : null]);
$flash = 'City added.';
} catch (Throwable $e) {
$errors[] = 'City already exists or could not be saved.';
}
}
} elseif (isset($_POST['update_city'])) {
$cityId = (int)($_POST['city_id'] ?? 0);
$countryId = (int)($_POST['country_id'] ?? 0);
$cityNameEn = trim($_POST['city_name_en'] ?? '');
$cityNameAr = trim($_POST['city_name_ar'] ?? '');
if ($cityId <= 0 || $countryId <= 0 || $cityNameEn === '') {
$errors[] = 'City ID, country and English city name are required.';
} else {
try {
$stmt = db()->prepare("UPDATE cities SET country_id = ?, name_en = ?, name_ar = ? WHERE id = ?");
$stmt->execute([$countryId, $cityNameEn, $cityNameAr !== '' ? $cityNameAr : null, $cityId]);
$flash = 'City updated.';
$editCityId = 0;
} catch (Throwable $e) {
$errors[] = 'City could not be updated.';
}
}
} elseif (isset($_POST['delete_city'])) {
$cityId = (int)($_POST['city_id'] ?? 0);
if ($cityId <= 0) {
$errors[] = 'Invalid city selected.';
} else {
$stmt = db()->prepare("DELETE FROM cities WHERE id = ?");
$stmt->execute([$cityId]);
$flash = 'City deleted.';
$editCityId = 0;
}
}
}
$countryNameExpr = $lang === 'ar'
? "COALESCE(NULLIF(co.name_ar, ''), co.name_en)"
: "COALESCE(NULLIF(co.name_en, ''), co.name_ar)";
$countryNameExprNoAlias = $lang === 'ar'
? "COALESCE(NULLIF(name_ar, ''), name_en)"
: "COALESCE(NULLIF(name_en, ''), name_ar)";
$cityNameExpr = $lang === 'ar'
? "COALESCE(NULLIF(c.name_ar, ''), c.name_en)"
: "COALESCE(NULLIF(c.name_en, ''), c.name_ar)";
$countries = db()->query("SELECT id, name_en, name_ar, {$countryNameExprNoAlias} AS display_name FROM countries ORDER BY display_name ASC")->fetchAll();
$cities = db()->query(
"SELECT
c.id,
c.country_id,
c.name_en,
c.name_ar,
{$countryNameExpr} AS country_name,
{$cityNameExpr} AS city_name
FROM cities c
JOIN countries co ON co.id = c.country_id
ORDER BY country_name ASC, city_name ASC
LIMIT 200"
)->fetchAll();
$editingCity = null;
if ($editCityId > 0) {
foreach ($cities as $city) {
if ((int)$city['id'] === $editCityId) {
$editingCity = $city;
break;
}
}
}
render_header('Manage Cities', 'admin');
?>
<div class="row g-4">
<div class="col-lg-3">
<?php render_admin_sidebar('cities'); ?>
</div>
<div class="col-lg-9">
<div class="page-intro">
<h1 class="section-title mb-1">Cities</h1>
<p class="muted mb-0">Manage cities and map each city to its country.</p>
</div>
<?php if ($flash): ?>
<div class="alert alert-success" data-auto-dismiss="true"><?= e($flash) ?></div>
<?php endif; ?>
<?php if ($errors): ?>
<div class="alert alert-warning"><?= e(implode(' ', $errors)) ?></div>
<?php endif; ?>
<div class="panel p-4">
<h2 class="h5 mb-3">Add city</h2>
<form method="post" class="row g-3">
<div class="col-md-4">
<label class="form-label" for="country_id">Country</label>
<select id="country_id" name="country_id" class="form-select" required>
<option value="">Select country</option>
<?php foreach ($countries as $country): ?>
<option value="<?= e($country['id']) ?>"><?= e($country['display_name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-4">
<label class="form-label" for="city_name_en">City name (EN)</label>
<input id="city_name_en" type="text" name="city_name_en" class="form-control" required>
</div>
<div class="col-md-4">
<label class="form-label" for="city_name_ar">City name (AR)</label>
<input id="city_name_ar" type="text" name="city_name_ar" class="form-control">
</div>
<div class="col-12">
<button type="submit" name="add_city" class="btn btn-primary">Add city</button>
<a class="btn btn-outline-dark" href="<?= e(url_with_lang('admin_countries.php')) ?>">Go to countries</a>
</div>
</form>
</div>
<div class="panel p-4 mt-4">
<h2 class="h5 mb-2">Cities list</h2>
<?php if ($editingCity): ?>
<form method="post" class="row g-2 align-items-end mb-3">
<input type="hidden" name="city_id" value="<?= e((string)$editingCity['id']) ?>">
<div class="col-md-3">
<label class="form-label mb-1">Country</label>
<select class="form-select form-select-sm" name="country_id" required>
<?php foreach ($countries as $country): ?>
<option value="<?= e((string)$country['id']) ?>" <?= (int)$country['id'] === (int)$editingCity['country_id'] ? 'selected' : '' ?>>
<?= e($country['display_name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-3">
<label class="form-label mb-1">City (EN)</label>
<input class="form-control form-control-sm" type="text" name="city_name_en" value="<?= e($editingCity['name_en']) ?>" required>
</div>
<div class="col-md-3">
<label class="form-label mb-1">City (AR)</label>
<input class="form-control form-control-sm" type="text" name="city_name_ar" value="<?= e((string)($editingCity['name_ar'] ?? '')) ?>">
</div>
<div class="col-md-3 d-flex gap-2">
<button type="submit" name="update_city" class="btn btn-sm btn-primary">Save</button>
<a href="<?= e(url_with_lang('admin_cities.php')) ?>" class="btn btn-sm btn-outline-secondary">Cancel</a>
</div>
</form>
<?php endif; ?>
<?php if (!$cities): ?>
<p class="muted mb-0">No cities added yet.</p>
<?php else: ?>
<div class="table-responsive">
<table class="table mb-0">
<thead>
<tr>
<th>ID</th>
<th>Country</th>
<th>City (EN)</th>
<th>City (AR)</th>
<th class="text-end">Action</th>
</tr>
</thead>
<tbody>
<?php foreach ($cities as $city): ?>
<tr>
<td><?= e((string)$city['id']) ?></td>
<td><?= e($city['country_name']) ?></td>
<td><?= e($city['name_en']) ?></td>
<td><?= e((string)($city['name_ar'] ?? '-')) ?></td>
<td class="text-end">
<a class="btn btn-sm btn-outline-primary" href="<?= e(url_with_lang('admin_cities.php', ['edit_city' => (int)$city['id']])) ?>">Edit</a>
<form method="post" class="d-inline" onsubmit="return confirm('Delete this city?');">
<input type="hidden" name="city_id" value="<?= e((string)$city['id']) ?>">
<button type="submit" name="delete_city" class="btn btn-sm btn-outline-danger">Del</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
</div>
<?php render_footer(); ?>

183
admin_countries.php Normal file
View File

@ -0,0 +1,183 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/includes/layout.php';
$errors = [];
$flash = null;
$editCountryId = isset($_GET['edit_country']) ? (int)$_GET['edit_country'] : 0;
db()->exec("
CREATE TABLE IF NOT EXISTS countries (
id INT AUTO_INCREMENT PRIMARY KEY,
name_en VARCHAR(255) NOT NULL,
name_ar VARCHAR(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
");
db()->exec("
CREATE TABLE IF NOT EXISTS cities (
id INT AUTO_INCREMENT PRIMARY KEY,
country_id INT NOT NULL,
name_en VARCHAR(255) NOT NULL,
name_ar VARCHAR(255) DEFAULT NULL,
UNIQUE KEY uniq_city_country (country_id, name_en),
CONSTRAINT fk_cities_country FOREIGN KEY (country_id) REFERENCES countries(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
");
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['add_country'])) {
$countryNameEn = trim($_POST['country_name_en'] ?? '');
$countryNameAr = trim($_POST['country_name_ar'] ?? '');
if ($countryNameEn === '') {
$errors[] = 'Country name (English) is required.';
} else {
try {
$stmt = db()->prepare("INSERT INTO countries (name_en, name_ar) VALUES (?, ?)");
$stmt->execute([$countryNameEn, $countryNameAr !== '' ? $countryNameAr : null]);
$flash = 'Country added.';
} catch (Throwable $e) {
$errors[] = 'Country already exists or could not be saved.';
}
}
} elseif (isset($_POST['update_country'])) {
$countryId = (int)($_POST['country_id'] ?? 0);
$countryNameEn = trim($_POST['country_name_en'] ?? '');
$countryNameAr = trim($_POST['country_name_ar'] ?? '');
if ($countryId <= 0 || $countryNameEn === '') {
$errors[] = 'Country ID and English name are required.';
} else {
try {
$stmt = db()->prepare("UPDATE countries SET name_en = ?, name_ar = ? WHERE id = ?");
$stmt->execute([$countryNameEn, $countryNameAr !== '' ? $countryNameAr : null, $countryId]);
$flash = 'Country updated.';
$editCountryId = 0;
} catch (Throwable $e) {
$errors[] = 'Country could not be updated.';
}
}
} elseif (isset($_POST['delete_country'])) {
$countryId = (int)($_POST['country_id'] ?? 0);
if ($countryId <= 0) {
$errors[] = 'Invalid country selected.';
} else {
$stmt = db()->prepare("DELETE FROM countries WHERE id = ?");
$stmt->execute([$countryId]);
$flash = 'Country deleted.';
$editCountryId = 0;
}
}
}
$countryNameExprNoAlias = $lang === 'ar'
? "COALESCE(NULLIF(name_ar, ''), name_en)"
: "COALESCE(NULLIF(name_en, ''), name_ar)";
$countries = db()->query("SELECT id, name_en, name_ar, {$countryNameExprNoAlias} AS display_name FROM countries ORDER BY display_name ASC")->fetchAll();
$editingCountry = null;
if ($editCountryId > 0) {
foreach ($countries as $country) {
if ((int)$country['id'] === $editCountryId) {
$editingCountry = $country;
break;
}
}
}
render_header('Manage Countries', 'admin');
?>
<div class="row g-4">
<div class="col-lg-3">
<?php render_admin_sidebar('countries'); ?>
</div>
<div class="col-lg-9">
<div class="page-intro">
<h1 class="section-title mb-1">Countries</h1>
<p class="muted mb-0">Manage the list of allowed countries for shipment routes.</p>
</div>
<?php if ($flash): ?>
<div class="alert alert-success" data-auto-dismiss="true"><?= e($flash) ?></div>
<?php endif; ?>
<?php if ($errors): ?>
<div class="alert alert-warning"><?= e(implode(' ', $errors)) ?></div>
<?php endif; ?>
<div class="panel p-4">
<h2 class="h5 mb-3">Add country</h2>
<form method="post" class="row g-3">
<div class="col-md-6">
<label class="form-label" for="country_name_en">Country name (EN)</label>
<input id="country_name_en" type="text" name="country_name_en" class="form-control" required>
</div>
<div class="col-md-6">
<label class="form-label" for="country_name_ar">Country name (AR)</label>
<input id="country_name_ar" type="text" name="country_name_ar" class="form-control">
</div>
<div class="col-12">
<button type="submit" name="add_country" class="btn btn-primary">Add country</button>
<a class="btn btn-outline-dark" href="<?= e(url_with_lang('admin_cities.php')) ?>">Go to cities</a>
</div>
</form>
</div>
<div class="panel p-4 mt-4">
<h2 class="h5 mb-2">Countries list</h2>
<?php if ($editingCountry): ?>
<form method="post" class="row g-2 align-items-end mb-3">
<input type="hidden" name="country_id" value="<?= e((string)$editingCountry['id']) ?>">
<div class="col-md-4">
<label class="form-label mb-1">Country (EN)</label>
<input class="form-control form-control-sm" type="text" name="country_name_en" value="<?= e($editingCountry['name_en']) ?>" required>
</div>
<div class="col-md-4">
<label class="form-label mb-1">Country (AR)</label>
<input class="form-control form-control-sm" type="text" name="country_name_ar" value="<?= e((string)($editingCountry['name_ar'] ?? '')) ?>">
</div>
<div class="col-md-4 d-flex gap-2">
<button type="submit" name="update_country" class="btn btn-sm btn-primary">Save</button>
<a href="<?= e(url_with_lang('admin_countries.php')) ?>" class="btn btn-sm btn-outline-secondary">Cancel</a>
</div>
</form>
<?php endif; ?>
<?php if (!$countries): ?>
<p class="muted mb-0">No countries added yet.</p>
<?php else: ?>
<div class="table-responsive">
<table class="table mb-0">
<thead>
<tr>
<th>ID</th>
<th>Country (EN)</th>
<th>Country (AR)</th>
<th class="text-end">Action</th>
</tr>
</thead>
<tbody>
<?php foreach ($countries as $country): ?>
<tr>
<td><?= e((string)$country['id']) ?></td>
<td><?= e($country['name_en']) ?></td>
<td><?= e((string)($country['name_ar'] ?? '-')) ?></td>
<td class="text-end">
<a class="btn btn-sm btn-outline-primary" href="<?= e(url_with_lang('admin_countries.php', ['edit_country' => (int)$country['id']])) ?>">Edit</a>
<form method="post" class="d-inline" onsubmit="return confirm('Delete this country and its cities?');">
<input type="hidden" name="country_id" value="<?= e((string)$country['id']) ?>">
<button type="submit" name="delete_country" class="btn btn-sm btn-outline-danger">Del</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
</div>
<?php render_footer(); ?>

View File

@ -48,7 +48,8 @@ render_header(t('admin_dashboard'), 'admin');
<div class="panel p-4 mb-4">
<h2 class="h5 mb-3">Quick actions</h2>
<div class="d-flex flex-wrap gap-2">
<a href="<?= e(url_with_lang('admin_manage_locations.php')) ?>" class="btn btn-outline-dark">Manage Locations</a>
<a href="<?= e(url_with_lang('admin_countries.php')) ?>" class="btn btn-outline-dark">Manage Countries</a>
<a href="<?= e(url_with_lang('admin_cities.php')) ?>" class="btn btn-outline-dark">Manage Cities</a>
<a href="<?= e(url_with_lang('register.php')) ?>" class="btn btn-primary">Register New User</a>
</div>
</div>

View File

@ -1,177 +1,7 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/includes/layout.php';
require_once __DIR__ . '/includes/app.php';
$errors = [];
$flash = null;
db()->exec("
CREATE TABLE IF NOT EXISTS countries (
id INT AUTO_INCREMENT PRIMARY KEY,
name_en VARCHAR(255) NOT NULL,
name_ar VARCHAR(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
");
db()->exec("
CREATE TABLE IF NOT EXISTS cities (
id INT AUTO_INCREMENT PRIMARY KEY,
country_id INT NOT NULL,
name_en VARCHAR(255) NOT NULL,
name_ar VARCHAR(255) DEFAULT NULL,
UNIQUE KEY uniq_city_country (country_id, name_en),
CONSTRAINT fk_cities_country FOREIGN KEY (country_id) REFERENCES countries(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
");
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['add_country'])) {
$countryNameEn = trim($_POST['country_name_en'] ?? '');
$countryNameAr = trim($_POST['country_name_ar'] ?? '');
if ($countryNameEn === '') {
$errors[] = 'Country name (English) is required.';
} else {
try {
$stmt = db()->prepare("INSERT INTO countries (name_en, name_ar) VALUES (?, ?)");
$stmt->execute([$countryNameEn, $countryNameAr !== '' ? $countryNameAr : null]);
$flash = 'Country added.';
} catch (Throwable $e) {
$errors[] = 'Country already exists or could not be saved.';
}
}
} elseif (isset($_POST['add_city'])) {
$countryId = (int)($_POST['country_id'] ?? 0);
$cityNameEn = trim($_POST['city_name_en'] ?? '');
$cityNameAr = trim($_POST['city_name_ar'] ?? '');
if ($countryId <= 0 || $cityNameEn === '') {
$errors[] = 'Please select a country and provide city name (English).';
} else {
try {
$stmt = db()->prepare("INSERT INTO cities (country_id, name_en, name_ar) VALUES (?, ?, ?)");
$stmt->execute([$countryId, $cityNameEn, $cityNameAr !== '' ? $cityNameAr : null]);
$flash = 'City added.';
} catch (Throwable $e) {
$errors[] = 'City already exists or could not be saved.';
}
}
}
}
$countryNameExpr = $lang === 'ar'
? "COALESCE(NULLIF(co.name_ar, ''), co.name_en)"
: "COALESCE(NULLIF(co.name_en, ''), co.name_ar)";
$countryNameExprNoAlias = $lang === 'ar'
? "COALESCE(NULLIF(name_ar, ''), name_en)"
: "COALESCE(NULLIF(name_en, ''), name_ar)";
$cityNameExpr = $lang === 'ar'
? "COALESCE(NULLIF(c.name_ar, ''), c.name_en)"
: "COALESCE(NULLIF(c.name_en, ''), c.name_ar)";
$countries = db()->query("SELECT id, {$countryNameExprNoAlias} AS display_name FROM countries ORDER BY display_name ASC")->fetchAll();
$cities = db()->query(
"SELECT
{$countryNameExpr} AS country_name,
{$cityNameExpr} AS city_name
FROM cities c
JOIN countries co ON co.id = c.country_id
ORDER BY country_name ASC, city_name ASC
LIMIT 30"
)->fetchAll();
render_header('Manage Locations', 'admin');
?>
<div class="row g-4">
<div class="col-lg-3">
<?php render_admin_sidebar('locations'); ?>
</div>
<div class="col-lg-9">
<div class="page-intro">
<h1 class="section-title mb-1">Country & city setup</h1>
<p class="muted mb-0">Define allowed origin and destination options for shipments.</p>
</div>
<?php if ($flash): ?>
<div class="alert alert-success" data-auto-dismiss="true"><?= e($flash) ?></div>
<?php endif; ?>
<?php if ($errors): ?>
<div class="alert alert-warning"><?= e(implode(' ', $errors)) ?></div>
<?php endif; ?>
<div class="row g-4">
<div class="col-lg-6">
<div class="panel p-4 h-100">
<h2 class="h5 mb-3">Add country</h2>
<form method="post">
<div class="mb-3">
<label class="form-label" for="country_name_en">Country name (EN)</label>
<input id="country_name_en" type="text" name="country_name_en" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label" for="country_name_ar">Country name (AR)</label>
<input id="country_name_ar" type="text" name="country_name_ar" class="form-control">
</div>
<button type="submit" name="add_country" class="btn btn-primary">Add country</button>
</form>
</div>
</div>
<div class="col-lg-6">
<div class="panel p-4 h-100">
<h2 class="h5 mb-3">Add city</h2>
<form method="post">
<div class="mb-3">
<label class="form-label" for="country_id">Country</label>
<select id="country_id" name="country_id" class="form-select" required>
<option value="">Select country</option>
<?php foreach ($countries as $country): ?>
<option value="<?= e($country['id']) ?>"><?= e($country['display_name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label class="form-label" for="city_name_en">City name (EN)</label>
<input id="city_name_en" type="text" name="city_name_en" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label" for="city_name_ar">City name (AR)</label>
<input id="city_name_ar" type="text" name="city_name_ar" class="form-control">
</div>
<button type="submit" name="add_city" class="btn btn-primary">Add city</button>
</form>
</div>
</div>
</div>
<div class="panel p-4 mt-4">
<div class="d-flex justify-content-between align-items-center mb-2">
<h2 class="h5 mb-0">Recently added cities</h2>
<a class="btn btn-sm btn-outline-dark" href="<?= e(url_with_lang('admin_dashboard.php')) ?>">Back to admin</a>
</div>
<?php if (!$cities): ?>
<p class="muted mb-0">No cities added yet.</p>
<?php else: ?>
<div class="table-responsive">
<table class="table mb-0">
<thead>
<tr>
<th>Country</th>
<th>City</th>
</tr>
</thead>
<tbody>
<?php foreach ($cities as $city): ?>
<tr>
<td><?= e($city['country_name']) ?></td>
<td><?= e($city['city_name']) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
</div>
<?php render_footer(); ?>
header('Location: ' . url_with_lang('admin_countries.php'), true, 302);
exit;

210
admin_shipper_edit.php Normal file
View File

@ -0,0 +1,210 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/includes/layout.php';
$userId = (int)($_GET['id'] ?? 0);
if ($userId <= 0) {
header('Location: admin_shippers.php');
exit;
}
$errors = [];
$flash = null;
// Fetch Shipper Profile
$stmt = db()->prepare("
SELECT u.id, u.email, u.full_name, u.status, u.role,
p.company_name, p.phone, p.address_line, p.country_id, p.city_id
FROM users u
LEFT JOIN shipper_profiles p ON u.id = p.user_id
WHERE u.id = ? AND u.role = 'shipper'
");
$stmt->execute([$userId]);
$shipper = $stmt->fetch();
if (!$shipper) {
header('Location: admin_shippers.php');
exit;
}
$countries = db()->query("SELECT id, name_en, name_ar FROM countries ORDER BY name_en ASC")->fetchAll();
$cities = db()->query("SELECT id, country_id, name_en, name_ar FROM cities ORDER BY name_en ASC")->fetchAll();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$fullName = trim($_POST['full_name'] ?? '');
$email = trim($_POST['email'] ?? '');
$phone = trim($_POST['phone'] ?? '');
$countryId = (int)($_POST['country_id'] ?? 0);
$cityId = (int)($_POST['city_id'] ?? 0);
$addressLine = trim($_POST['address_line'] ?? '');
$companyName = trim($_POST['company_name'] ?? '');
$status = trim($_POST['status'] ?? '');
if ($fullName === '') $errors[] = 'Full name is required.';
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) $errors[] = 'Valid email is required.';
if ($phone === '') $errors[] = 'Phone number is required.';
if ($companyName === '') $errors[] = 'Company name is required.';
if (!in_array($status, ['pending', 'active', 'rejected'], true)) $errors[] = 'Invalid status.';
if ($countryId <= 0 || $cityId <= 0) {
$errors[] = 'Please select country and city.';
} else {
$cityCheck = db()->prepare("SELECT COUNT(*) FROM cities WHERE id = ? AND country_id = ?");
$cityCheck->execute([$cityId, $countryId]);
if ((int)$cityCheck->fetchColumn() === 0) {
$errors[] = 'Selected city does not belong to selected country.';
}
}
if (!$errors) {
try {
db()->beginTransaction();
$stmtUser = db()->prepare("UPDATE users SET full_name = ?, email = ?, status = ? WHERE id = ? AND role = 'shipper'");
$stmtUser->execute([$fullName, $email, $status, $userId]);
$stmtProfile = db()->prepare("
UPDATE shipper_profiles
SET company_name = ?, phone = ?, address_line = ?, country_id = ?, city_id = ?
WHERE user_id = ?
");
$stmtProfile->execute([$companyName, $phone, $addressLine, $countryId, $cityId, $userId]);
db()->commit();
$flash = 'Shipper profile updated successfully.';
// Refresh data
$shipper['full_name'] = $fullName;
$shipper['email'] = $email;
$shipper['status'] = $status;
$shipper['company_name'] = $companyName;
$shipper['phone'] = $phone;
$shipper['address_line'] = $addressLine;
$shipper['country_id'] = $countryId;
$shipper['city_id'] = $cityId;
} catch (Throwable $e) {
db()->rollBack();
if (stripos($e->getMessage(), 'Duplicate entry') !== false) {
$errors[] = 'This email is already in use by another account.';
} else {
$errors[] = 'Failed to update shipper profile. Please try again.';
}
}
}
}
render_header('Edit Shipper', 'admin');
?>
<div class="row g-4">
<div class="col-lg-3">
<?php render_admin_sidebar('shippers'); ?>
</div>
<div class="col-lg-9">
<div class="page-intro d-flex flex-column flex-md-row justify-content-between align-items-md-center mb-4">
<div>
<a href="admin_shippers.php" class="text-decoration-none small text-muted mb-2 d-inline-block">&larr; Back to Shippers</a>
<h1 class="section-title mb-1">Edit Shipper</h1>
<p class="muted mb-0">Update profile information for <?= e($shipper['full_name']) ?>.</p>
</div>
</div>
<?php if ($flash): ?>
<div class="alert alert-success" data-auto-dismiss="true"><?= e($flash) ?></div>
<?php endif; ?>
<?php if ($errors): ?>
<div class="alert alert-warning"><?= e(implode('<br>', $errors)) ?></div>
<?php endif; ?>
<div class="panel p-4">
<form method="post">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label" for="full_name">Full Name</label>
<input type="text" name="full_name" id="full_name" class="form-control" value="<?= e((string)$shipper['full_name']) ?>" required>
</div>
<div class="col-md-6">
<label class="form-label" for="email">Email</label>
<input type="email" name="email" id="email" class="form-control" value="<?= e((string)$shipper['email']) ?>" required>
</div>
<div class="col-md-6">
<label class="form-label" for="company_name">Company Name</label>
<input type="text" name="company_name" id="company_name" class="form-control" value="<?= e((string)$shipper['company_name']) ?>" required>
</div>
<div class="col-md-6">
<label class="form-label" for="phone">Phone</label>
<input type="text" name="phone" id="phone" class="form-control" value="<?= e((string)$shipper['phone']) ?>" required>
</div>
<div class="col-md-6">
<label class="form-label" for="country_id">Country</label>
<select name="country_id" id="country_id" class="form-select" onchange="syncCities()" required>
<option value="">Select country</option>
<?php foreach ($countries as $country): ?>
<option value="<?= e((string)$country['id']) ?>" <?= (string)$shipper['country_id'] === (string)$country['id'] ? 'selected' : '' ?>>
<?= e($lang === 'ar' && !empty($country['name_ar']) ? $country['name_ar'] : $country['name_en']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-6">
<label class="form-label" for="city_id">City</label>
<select name="city_id" id="city_id" class="form-select" required data-selected="<?= e((string)$shipper['city_id']) ?>">
<option value="">Select city</option>
</select>
</div>
<div class="col-md-8">
<label class="form-label" for="address_line">Address Line</label>
<input type="text" name="address_line" id="address_line" class="form-control" value="<?= e((string)$shipper['address_line']) ?>" required>
</div>
<div class="col-md-4">
<label class="form-label" for="status">Account Status</label>
<select name="status" id="status" class="form-select" required>
<option value="pending" <?= $shipper['status'] === 'pending' ? 'selected' : '' ?>>Pending</option>
<option value="active" <?= $shipper['status'] === 'active' ? 'selected' : '' ?>>Active</option>
<option value="rejected" <?= $shipper['status'] === 'rejected' ? 'selected' : '' ?>>Rejected</option>
</select>
</div>
<div class="col-12 mt-4">
<button type="submit" class="btn btn-primary">Save Changes</button>
<a href="admin_shippers.php" class="btn btn-outline-dark ms-2">Cancel</a>
</div>
</div>
</form>
</div>
</div>
</div>
<script>
const allCities = <?= json_encode($cities, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
function syncCities() {
const countryId = document.getElementById('country_id').value;
const citySelect = document.getElementById('city_id');
const selectedValue = citySelect.dataset.selected || '';
citySelect.innerHTML = '<option value="">Select city</option>';
allCities.forEach((city) => {
if (String(city.country_id) !== String(countryId)) {
return;
}
const option = document.createElement('option');
option.value = city.id;
option.textContent = <?= $lang === 'ar' ? '(city.name_ar || city.name_en)' : '(city.name_en || city.name_ar)' ?>;
if (String(city.id) === String(selectedValue)) {
option.selected = true;
}
citySelect.appendChild(option);
});
// clear selected after first render
citySelect.dataset.selected = '';
}
syncCities();
</script>
<?php render_footer(); ?>

202
admin_shippers.php Normal file
View File

@ -0,0 +1,202 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/includes/layout.php';
$errors = [];
$flash = null;
// Handle action (Approve / Reject / Delete if necessary)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'], $_POST['user_id'])) {
$userId = (int)$_POST['user_id'];
$action = $_POST['action'];
if ($action === 'approve') {
db()->prepare("UPDATE users SET status = 'active' WHERE id = ? AND role = 'shipper'")->execute([$userId]);
$flash = 'Shipper approved successfully.';
} elseif ($action === 'reject') {
db()->prepare("UPDATE users SET status = 'rejected' WHERE id = ? AND role = 'shipper'")->execute([$userId]);
$flash = 'Shipper rejected.';
} elseif ($action === 'delete') {
db()->prepare("DELETE FROM shipper_profiles WHERE user_id = ?")->execute([$userId]);
db()->prepare("DELETE FROM users WHERE id = ? AND role = 'shipper'")->execute([$userId]);
$flash = 'Shipper deleted.';
}
}
// Search and Pagination parameters
$q = trim($_GET['q'] ?? '');
$page = max(1, (int)($_GET['page'] ?? 1));
$limit = 10;
$offset = ($page - 1) * $limit;
$whereClause = "u.role = 'shipper'";
$params = [];
if ($q !== '') {
$whereClause .= " AND (u.full_name LIKE ? OR u.email LIKE ? OR p.company_name LIKE ?)";
$likeQ = "%$q%";
$params = [$likeQ, $likeQ, $likeQ];
}
// Total count
$countSql = "
SELECT COUNT(*)
FROM users u
LEFT JOIN shipper_profiles p ON u.id = p.user_id
WHERE $whereClause
";
$stmt = db()->prepare($countSql);
$stmt->execute($params);
$total = (int)$stmt->fetchColumn();
$totalPages = (int)ceil($total / $limit);
// Fetch shippers
$sql = "
SELECT u.id, u.email, u.full_name, u.status, u.created_at,
p.company_name, p.phone, p.address_line,
c.name_en AS country_name,
ci.name_en AS city_name
FROM users u
LEFT JOIN shipper_profiles p ON u.id = p.user_id
LEFT JOIN countries c ON p.country_id = c.id
LEFT JOIN cities ci ON p.city_id = ci.id
WHERE $whereClause
ORDER BY u.created_at DESC
LIMIT $limit OFFSET $offset
";
$stmt = db()->prepare($sql);
$stmt->execute($params);
$shippers = $stmt->fetchAll();
render_header('Manage Shippers', 'admin');
?>
<div class="row g-4">
<div class="col-lg-3">
<?php render_admin_sidebar('shippers'); ?>
</div>
<div class="col-lg-9">
<div class="page-intro d-flex flex-column flex-md-row justify-content-between align-items-md-center mb-4">
<div>
<h1 class="section-title mb-1">Shippers</h1>
<p class="muted mb-0">Manage registered shippers.</p>
</div>
<form method="get" class="d-flex mt-3 mt-md-0 gap-2">
<input type="text" name="q" class="form-control form-control-sm" placeholder="Search name, email, company..." value="<?= e($q) ?>">
<button type="submit" class="btn btn-sm btn-primary">Search</button>
<?php if ($q): ?>
<a href="?q=" class="btn btn-sm btn-outline-secondary">Clear</a>
<?php endif; ?>
</form>
</div>
<?php if ($flash): ?>
<div class="alert alert-success" data-auto-dismiss="true"><?= e($flash) ?></div>
<?php endif; ?>
<div class="panel p-0">
<?php if (!$shippers && $q): ?>
<div class="p-4"><p class="muted mb-0">No shippers found matching your search.</p></div>
<?php elseif (!$shippers): ?>
<div class="p-4"><p class="muted mb-0">No shippers registered yet.</p></div>
<?php else: ?>
<div class="table-responsive">
<table class="table mb-0 align-middle table-hover">
<thead class="table-light">
<tr>
<th class="ps-4">ID</th>
<th>Name / Company</th>
<th>Contact</th>
<th>Location</th>
<th>Status</th>
<th class="text-end pe-4">Action</th>
</tr>
</thead>
<tbody>
<?php foreach ($shippers as $shipper): ?>
<tr>
<td class="ps-4"><?= e((string)$shipper['id']) ?></td>
<td>
<div class="fw-bold text-dark"><?= e($shipper['full_name']) ?></div>
<div class="text-muted small"><?= e((string)$shipper['company_name']) ?></div>
</td>
<td>
<div><a href="mailto:<?= e($shipper['email']) ?>" class="text-decoration-none"><?= e($shipper['email']) ?></a></div>
<div class="text-muted small"><?= e((string)$shipper['phone']) ?></div>
</td>
<td>
<?= e((string)$shipper['city_name']) ?>, <?= e((string)$shipper['country_name']) ?>
</td>
<td>
<?php if ($shipper['status'] === 'active'): ?>
<span class="badge bg-success-subtle text-success">Active</span>
<?php elseif ($shipper['status'] === 'pending'): ?>
<span class="badge bg-warning-subtle text-warning">Pending</span>
<?php else: ?>
<span class="badge bg-danger-subtle text-danger"><?= e(ucfirst($shipper['status'] ?? 'unknown')) ?></span>
<?php endif; ?>
</td>
<td class="text-end pe-4">
<div class="d-inline-flex gap-1 align-items-center">
<a href="admin_shipper_edit.php?id=<?= e((string)$shipper[id]) ?>" class="btn btn-sm btn-light border text-primary" title="Edit Shipper">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="bi bi-pencil" viewBox="0 0 16 16">
<path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325"/>
</svg>
</a>
<form method="post" class="d-inline m-0 p-0">
<input type="hidden" name="user_id" value="<?= e((string)$shipper['id']) ?>">
<?php if ($shipper['status'] !== 'active'): ?>
<button type="submit" name="action" value="approve" class="btn btn-sm btn-success" title="Approve">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="bi bi-check-lg" viewBox="0 0 16 16">
<path d="M12.736 3.97a.733.733 0 0 1 1.047 0c.286.289.29.756.01 1.05L7.88 12.01a.733.733 0 0 1-1.065.02L3.217 8.384a.757.757 0 0 1 0-1.06.733.733 0 0 1 1.047 0l3.052 3.093 5.4-6.425z"/>
</svg>
</button>
<?php endif; ?>
<?php if ($shipper['status'] !== 'rejected'): ?>
<button type="submit" name="action" value="reject" class="btn btn-sm btn-warning" title="Reject">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="bi bi-x-lg" viewBox="0 0 16 16">
<path d="M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8z"/>
</svg>
</button>
<?php endif; ?>
<button type="submit" name="action" value="delete" class="btn btn-sm btn-danger" onclick="return confirm('Delete this shipper forever?');" title="Delete">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0z"/>
<path d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4zM2.5 3h11V2h-11z"/>
</svg>
</button>
</form>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php if ($totalPages > 1): ?>
<div class="px-4 py-3 border-top d-flex justify-content-between align-items-center">
<span class="text-muted small">Showing <?= count($shippers) ?> of <?= $total ?> shippers</span>
<ul class="pagination pagination-sm mb-0">
<li class="page-item <?= $page <= 1 ? 'disabled' : '' ?>">
<a class="page-link" href="?q=<?= urlencode($q) ?>&page=<?= $page - 1 ?>">Previous</a>
</li>
<?php for ($i = 1; $i <= $totalPages; $i++): ?>
<li class="page-item <?= $i === $page ? 'active' : '' ?>">
<a class="page-link" href="?q=<?= urlencode($q) ?>&page=<?= $i ?>"><?= $i ?></a>
</li>
<?php endfor; ?>
<li class="page-item <?= $page >= $totalPages ? 'disabled' : '' ?>">
<a class="page-link" href="?q=<?= urlencode($q) ?>&page=<?= $page + 1 ?>">Next</a>
</li>
</ul>
</div>
<?php endif; ?>
<?php endif; ?>
</div>
</div>
</div>
<?php render_footer(); ?>

236
admin_truck_owner_edit.php Normal file
View File

@ -0,0 +1,236 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/includes/layout.php';
$userId = (int)($_GET['id'] ?? 0);
if ($userId <= 0) {
header('Location: admin_truck_owners.php');
exit;
}
$errors = [];
$flash = null;
// Fetch Truck Owner Profile
$stmt = db()->prepare("
SELECT u.id, u.email, u.full_name, u.status, u.role,
p.phone, p.address_line, p.country_id, p.city_id,
p.truck_type, p.load_capacity, p.plate_no
FROM users u
LEFT JOIN truck_owner_profiles p ON u.id = p.user_id
WHERE u.id = ? AND u.role = 'truck_owner'
");
$stmt->execute([$userId]);
$owner = $stmt->fetch();
if (!$owner) {
header('Location: admin_truck_owners.php');
exit;
}
$countries = db()->query("SELECT id, name_en, name_ar FROM countries ORDER BY name_en ASC")->fetchAll();
$cities = db()->query("SELECT id, country_id, name_en, name_ar FROM cities ORDER BY name_en ASC")->fetchAll();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$fullName = trim($_POST['full_name'] ?? '');
$email = trim($_POST['email'] ?? '');
$phone = trim($_POST['phone'] ?? '');
$countryId = (int)($_POST['country_id'] ?? 0);
$cityId = (int)($_POST['city_id'] ?? 0);
$addressLine = trim($_POST['address_line'] ?? '');
$truckType = trim($_POST['truck_type'] ?? '');
$loadCapacity = trim($_POST['load_capacity'] ?? '');
$plateNo = trim($_POST['plate_no'] ?? '');
$status = trim($_POST['status'] ?? '');
if ($fullName === '') $errors[] = 'Full name is required.';
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) $errors[] = 'Valid email is required.';
if ($phone === '') $errors[] = 'Phone number is required.';
if (!in_array($status, ['pending', 'active', 'rejected'], true)) $errors[] = 'Invalid status.';
if ($truckType === '' || $loadCapacity === '' || $plateNo === '') {
$errors[] = 'Truck type, load capacity, and plate number are required.';
} elseif (!is_numeric($loadCapacity) || (float)$loadCapacity <= 0) {
$errors[] = 'Load capacity must be a positive number.';
}
if ($countryId <= 0 || $cityId <= 0) {
$errors[] = 'Please select country and city.';
} else {
$cityCheck = db()->prepare("SELECT COUNT(*) FROM cities WHERE id = ? AND country_id = ?");
$cityCheck->execute([$cityId, $countryId]);
if ((int)$cityCheck->fetchColumn() === 0) {
$errors[] = 'Selected city does not belong to selected country.';
}
}
if (!$errors) {
try {
db()->beginTransaction();
$stmtUser = db()->prepare("UPDATE users SET full_name = ?, email = ?, status = ? WHERE id = ? AND role = 'truck_owner'");
$stmtUser->execute([$fullName, $email, $status, $userId]);
$stmtProfile = db()->prepare("
UPDATE truck_owner_profiles
SET phone = ?, address_line = ?, country_id = ?, city_id = ?,
truck_type = ?, load_capacity = ?, plate_no = ?
WHERE user_id = ?
");
$stmtProfile->execute([$phone, $addressLine, $countryId, $cityId, $truckType, $loadCapacity, $plateNo, $userId]);
db()->commit();
$flash = 'Truck Owner profile updated successfully.';
// Refresh data
$owner['full_name'] = $fullName;
$owner['email'] = $email;
$owner['status'] = $status;
$owner['phone'] = $phone;
$owner['address_line'] = $addressLine;
$owner['country_id'] = $countryId;
$owner['city_id'] = $cityId;
$owner['truck_type'] = $truckType;
$owner['load_capacity'] = $loadCapacity;
$owner['plate_no'] = $plateNo;
} catch (Throwable $e) {
db()->rollBack();
if (stripos($e->getMessage(), 'Duplicate entry') !== false) {
$errors[] = 'This email is already in use by another account.';
} else {
$errors[] = 'Failed to update truck owner profile. Please try again.';
}
}
}
}
render_header('Edit Truck Owner', 'admin');
?>
<div class="row g-4">
<div class="col-lg-3">
<?php render_admin_sidebar('truck_owners'); ?>
</div>
<div class="col-lg-9">
<div class="page-intro d-flex flex-column flex-md-row justify-content-between align-items-md-center mb-4">
<div>
<a href="admin_truck_owners.php" class="text-decoration-none small text-muted mb-2 d-inline-block">&larr; Back to Truck Owners</a>
<h1 class="section-title mb-1">Edit Truck Owner</h1>
<p class="muted mb-0">Update profile information for <?= e($owner['full_name']) ?>.</p>
</div>
</div>
<?php if ($flash): ?>
<div class="alert alert-success" data-auto-dismiss="true"><?= e($flash) ?></div>
<?php endif; ?>
<?php if ($errors): ?>
<div class="alert alert-warning"><?= e(implode('<br>', $errors)) ?></div>
<?php endif; ?>
<div class="panel p-4">
<form method="post">
<h5 class="mb-3">Personal Details</h5>
<div class="row g-3 mb-4">
<div class="col-md-6">
<label class="form-label" for="full_name">Full Name</label>
<input type="text" name="full_name" id="full_name" class="form-control" value="<?= e((string)$owner['full_name']) ?>" required>
</div>
<div class="col-md-6">
<label class="form-label" for="email">Email</label>
<input type="email" name="email" id="email" class="form-control" value="<?= e((string)$owner['email']) ?>" required>
</div>
<div class="col-md-6">
<label class="form-label" for="phone">Phone</label>
<input type="text" name="phone" id="phone" class="form-control" value="<?= e((string)$owner['phone']) ?>" required>
</div>
<div class="col-md-6">
<label class="form-label" for="status">Account Status</label>
<select name="status" id="status" class="form-select" required>
<option value="pending" <?= $owner['status'] === 'pending' ? 'selected' : '' ?>>Pending</option>
<option value="active" <?= $owner['status'] === 'active' ? 'selected' : '' ?>>Active</option>
<option value="rejected" <?= $owner['status'] === 'rejected' ? 'selected' : '' ?>>Rejected</option>
</select>
</div>
</div>
<h5 class="mb-3">Location</h5>
<div class="row g-3 mb-4">
<div class="col-md-4">
<label class="form-label" for="country_id">Country</label>
<select name="country_id" id="country_id" class="form-select" onchange="syncCities()" required>
<option value="">Select country</option>
<?php foreach ($countries as $country): ?>
<option value="<?= e((string)$country['id']) ?>" <?= (string)$owner['country_id'] === (string)$country['id'] ? 'selected' : '' ?>>
<?= e($lang === 'ar' && !empty($country['name_ar']) ? $country['name_ar'] : $country['name_en']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-4">
<label class="form-label" for="city_id">City</label>
<select name="city_id" id="city_id" class="form-select" required data-selected="<?= e((string)$owner['city_id']) ?>">
<option value="">Select city</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label" for="address_line">Address Line</label>
<input type="text" name="address_line" id="address_line" class="form-control" value="<?= e((string)$owner['address_line']) ?>" required>
</div>
</div>
<h5 class="mb-3">Truck Details</h5>
<div class="row g-3 mb-4">
<div class="col-md-4">
<label class="form-label" for="truck_type">Truck Type</label>
<input type="text" name="truck_type" id="truck_type" class="form-control" value="<?= e((string)$owner['truck_type']) ?>" required>
</div>
<div class="col-md-4">
<label class="form-label" for="load_capacity">Load Capacity (tons)</label>
<input type="number" step="0.01" min="0.1" name="load_capacity" id="load_capacity" class="form-control" value="<?= e((string)$owner['load_capacity']) ?>" required>
</div>
<div class="col-md-4">
<label class="form-label" for="plate_no">Plate Number</label>
<input type="text" name="plate_no" id="plate_no" class="form-control" value="<?= e((string)$owner['plate_no']) ?>" required>
</div>
</div>
<div class="mt-4">
<button type="submit" class="btn btn-primary">Save Changes</button>
<a href="admin_truck_owners.php" class="btn btn-outline-dark ms-2">Cancel</a>
</div>
</form>
</div>
</div>
</div>
<script>
const allCities = <?= json_encode($cities, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
function syncCities() {
const countryId = document.getElementById('country_id').value;
const citySelect = document.getElementById('city_id');
const selectedValue = citySelect.dataset.selected || '';
citySelect.innerHTML = '<option value="">Select city</option>';
allCities.forEach((city) => {
if (String(city.country_id) !== String(countryId)) {
return;
}
const option = document.createElement('option');
option.value = city.id;
option.textContent = <?= $lang === 'ar' ? '(city.name_ar || city.name_en)' : '(city.name_en || city.name_ar)' ?>;
if (String(city.id) === String(selectedValue)) {
option.selected = true;
}
citySelect.appendChild(option);
});
// clear selected after first render
citySelect.dataset.selected = '';
}
syncCities();
</script>
<?php render_footer(); ?>

261
admin_truck_owners.php Normal file
View File

@ -0,0 +1,261 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/includes/layout.php';
$errors = [];
$flash = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'], $_POST['user_id'])) {
$userId = (int)$_POST['user_id'];
$action = $_POST['action'];
if ($action === 'approve') {
db()->prepare("UPDATE users SET status = 'active' WHERE id = ? AND role = 'truck_owner'")->execute([$userId]);
$flash = 'Truck Owner approved successfully.';
} elseif ($action === 'reject') {
db()->prepare("UPDATE users SET status = 'rejected' WHERE id = ? AND role = 'truck_owner'")->execute([$userId]);
$flash = 'Truck Owner rejected.';
} elseif ($action === 'delete') {
db()->prepare("DELETE FROM truck_owner_profiles WHERE user_id = ?")->execute([$userId]);
db()->prepare("DELETE FROM users WHERE id = ? AND role = 'truck_owner'")->execute([$userId]);
$flash = 'Truck Owner deleted.';
}
}
// Search and Pagination parameters
$q = trim($_GET['q'] ?? '');
$page = max(1, (int)($_GET['page'] ?? 1));
$limit = 10;
$offset = ($page - 1) * $limit;
$whereClause = "u.role = 'truck_owner'";
$params = [];
if ($q !== '') {
$whereClause .= " AND (u.full_name LIKE ? OR u.email LIKE ? OR p.plate_no LIKE ? OR p.truck_type LIKE ?)";
$likeQ = "%$q%";
$params = [$likeQ, $likeQ, $likeQ, $likeQ];
}
// Total count
$countSql = "
SELECT COUNT(*)
FROM users u
LEFT JOIN truck_owner_profiles p ON u.id = p.user_id
WHERE $whereClause
";
$stmt = db()->prepare($countSql);
$stmt->execute($params);
$total = (int)$stmt->fetchColumn();
$totalPages = (int)ceil($total / $limit);
// Fetch truck owners
$sql = "
SELECT u.id, u.email, u.full_name, u.status, u.created_at,
p.phone, p.truck_type, p.load_capacity, p.plate_no,
p.id_card_path, p.truck_pic_path, p.registration_path,
c.name_en AS country_name,
ci.name_en AS city_name
FROM users u
LEFT JOIN truck_owner_profiles p ON u.id = p.user_id
LEFT JOIN countries c ON p.country_id = c.id
LEFT JOIN cities ci ON p.city_id = ci.id
WHERE $whereClause
ORDER BY u.created_at DESC
LIMIT $limit OFFSET $offset
";
$stmt = db()->prepare($sql);
$stmt->execute($params);
$owners = $stmt->fetchAll();
render_header('Manage Truck Owners', 'admin');
?>
<div class="row g-4">
<div class="col-lg-3">
<?php render_admin_sidebar('truck_owners'); ?>
</div>
<div class="col-lg-9">
<div class="page-intro d-flex flex-column flex-md-row justify-content-between align-items-md-center mb-4">
<div>
<h1 class="section-title mb-1">Truck Owners</h1>
<p class="muted mb-0">Review registrations and approve truck owners.</p>
</div>
<form method="get" class="d-flex mt-3 mt-md-0 gap-2">
<input type="text" name="q" class="form-control form-control-sm" placeholder="Search name, email, plate..." value="<?= e($q) ?>">
<button type="submit" class="btn btn-sm btn-primary">Search</button>
<?php if ($q): ?>
<a href="?q=" class="btn btn-sm btn-outline-secondary">Clear</a>
<?php endif; ?>
</form>
</div>
<?php if ($flash): ?>
<div class="alert alert-success" data-auto-dismiss="true"><?= e($flash) ?></div>
<?php endif; ?>
<div class="panel p-0">
<?php if (!$owners && $q): ?>
<div class="p-4"><p class="muted mb-0">No truck owners found matching your search.</p></div>
<?php elseif (!$owners): ?>
<div class="p-4"><p class="muted mb-0">No truck owners registered yet.</p></div>
<?php else: ?>
<div class="table-responsive">
<table class="table mb-0 align-middle table-hover">
<thead class="table-light">
<tr>
<th class="ps-4">ID</th>
<th>Name / Email</th>
<th>Truck Info</th>
<th>Documents</th>
<th>Status</th>
<th class="text-end pe-4">Action</th>
</tr>
</thead>
<tbody>
<?php foreach ($owners as $owner): ?>
<tr>
<td class="ps-4"><?= e((string)$owner['id']) ?></td>
<td>
<div class="fw-bold text-dark"><?= e($owner['full_name']) ?></div>
<div class="text-muted small"><a href="mailto:<?= e($owner['email']) ?>" class="text-decoration-none"><?= e($owner['email']) ?></a></div>
<div class="text-muted small"><?= e((string)$owner['phone']) ?></div>
</td>
<td>
<div><strong>Type:</strong> <?= e((string)$owner['truck_type']) ?></div>
<div><strong>Cap:</strong> <?= e((string)$owner['load_capacity']) ?>t</div>
<div><strong>Plate:</strong> <?= e((string)$owner['plate_no']) ?></div>
</td>
<td>
<button type="button" class="btn btn-sm btn-outline-secondary d-flex align-items-center gap-1" data-bs-toggle="modal" data-bs-target="#docsModal<?= $owner['id'] ?>">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="bi bi-file-earmark-text" viewBox="0 0 16 16">
<path d="M5.5 7a.5.5 0 0 0 0 1h5a.5.5 0 0 0 0-1zM5 9.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5m0 2a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5"/>
<path d="M9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V4.5zm0 1v2A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1z"/>
</svg>
View Docs
</button>
</td>
<td>
<?php if ($owner['status'] === 'active'): ?>
<span class="badge bg-success-subtle text-success">Active</span>
<?php elseif ($owner['status'] === 'pending'): ?>
<span class="badge bg-warning-subtle text-warning">Pending</span>
<?php else: ?>
<span class="badge bg-danger-subtle text-danger"><?= e(ucfirst($owner['status'] ?? 'unknown')) ?></span>
<?php endif; ?>
</td>
<td class="text-end pe-4">
<div class="d-inline-flex gap-1 align-items-center">
<a href="admin_truck_owner_edit.php?id=<?= e((string)$owner[id]) ?>" class="btn btn-sm btn-light border text-primary" title="Edit Owner">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="bi bi-pencil" viewBox="0 0 16 16">
<path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325"/>
</svg>
</a>
<form method="post" class="d-inline m-0 p-0">
<input type="hidden" name="user_id" value="<?= e((string)$owner['id']) ?>">
<?php if ($owner['status'] !== 'active'): ?>
<button type="submit" name="action" value="approve" class="btn btn-sm btn-success" title="Approve">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="bi bi-check-lg" viewBox="0 0 16 16">
<path d="M12.736 3.97a.733.733 0 0 1 1.047 0c.286.289.29.756.01 1.05L7.88 12.01a.733.733 0 0 1-1.065.02L3.217 8.384a.757.757 0 0 1 0-1.06.733.733 0 0 1 1.047 0l3.052 3.093 5.4-6.425z"/>
</svg>
</button>
<?php endif; ?>
<?php if ($owner['status'] !== 'rejected'): ?>
<button type="submit" name="action" value="reject" class="btn btn-sm btn-warning" title="Reject">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="bi bi-x-lg" viewBox="0 0 16 16">
<path d="M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8z"/>
</svg>
</button>
<?php endif; ?>
<button type="submit" name="action" value="delete" class="btn btn-sm btn-danger" onclick="return confirm('Delete this truck owner forever?');" title="Delete">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0z"/>
<path d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4zM2.5 3h11V2h-11z"/>
</svg>
</button>
</form>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php if ($totalPages > 1): ?>
<div class="px-4 py-3 border-top d-flex justify-content-between align-items-center">
<span class="text-muted small">Showing <?= count($owners) ?> of <?= $total ?> truck owners</span>
<ul class="pagination pagination-sm mb-0">
<li class="page-item <?= $page <= 1 ? 'disabled' : '' ?>">
<a class="page-link" href="?q=<?= urlencode($q) ?>&page=<?= $page - 1 ?>">Previous</a>
</li>
<?php for ($i = 1; $i <= $totalPages; $i++): ?>
<li class="page-item <?= $i === $page ? 'active' : '' ?>">
<a class="page-link" href="?q=<?= urlencode($q) ?>&page=<?= $i ?>"><?= $i ?></a>
</li>
<?php endfor; ?>
<li class="page-item <?= $page >= $totalPages ? 'disabled' : '' ?>">
<a class="page-link" href="?q=<?= urlencode($q) ?>&page=<?= $page + 1 ?>">Next</a>
</li>
</ul>
</div>
<?php endif; ?>
<?php endif; ?>
</div>
</div>
</div>
<?php foreach ($owners as $owner): ?>
<?php
$idCards = json_decode($owner['id_card_path'] ?? '[]', true) ?: [];
$regs = json_decode($owner['registration_path'] ?? '[]', true) ?: [];
$pic = $owner['truck_pic_path'];
?>
<div class="modal fade" id="docsModal<?= $owner['id'] ?>" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Documents for <?= e($owner['full_name']) ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<h6>ID Card</h6>
<div class="d-flex gap-2 mb-3 overflow-auto">
<?php foreach ($idCards as $path): ?>
<a href="<?= e('/' . $path) ?>" target="_blank">
<img src="<?= e('/' . $path) ?>" alt="ID Card" class="img-thumbnail" style="max-height: 150px;">
</a>
<?php endforeach; ?>
</div>
<h6>Truck Registration</h6>
<div class="d-flex gap-2 mb-3 overflow-auto">
<?php foreach ($regs as $path): ?>
<a href="<?= e('/' . $path) ?>" target="_blank">
<img src="<?= e('/' . $path) ?>" alt="Registration" class="img-thumbnail" style="max-height: 150px;">
</a>
<?php endforeach; ?>
</div>
<h6>Truck Picture</h6>
<?php if ($pic): ?>
<div>
<a href="<?= e('/' . $pic) ?>" target="_blank">
<img src="<?= e('/' . $pic) ?>" alt="Truck Pic" class="img-thumbnail" style="max-height: 250px;">
</a>
</div>
<?php else: ?>
<span class="text-muted">No picture uploaded.</span>
<?php endif; ?>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
<?php render_footer(); ?>

View File

@ -1,57 +1,110 @@
:root {
--bg: #f8fafc;
--bg: #f4f7f6;
--surface: #ffffff;
--text: #0f172a;
--text: #1e293b;
--muted: #64748b;
--border: #e2e8f0;
--primary: #0f172a;
--accent: #2563eb;
--success: #16a34a;
--primary: #3b82f6;
--primary-hover: #2563eb;
--accent: #0ea5e9;
--success: #10b981;
--warning: #f59e0b;
--shadow: 0 10px 30px rgba(15, 23, 42, 0.06);
--shadow: 0 10px 30px rgba(15, 23, 42, 0.05);
}
body.app-body {
background:
radial-gradient(circle at 5% 5%, rgba(37, 99, 235, 0.08), transparent 28%),
radial-gradient(circle at 95% 10%, rgba(14, 165, 233, 0.08), transparent 25%),
background:
radial-gradient(circle at top left, rgba(59, 130, 246, 0.05), transparent 40%),
radial-gradient(circle at bottom right, rgba(14, 165, 233, 0.05), transparent 40%),
var(--bg);
color: var(--text);
font-family: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
font-size: 14px;
font-size: 15px;
line-height: 1.6;
}
.navbar {
backdrop-filter: blur(6px);
background: rgba(255, 255, 255, 0.92) !important;
backdrop-filter: blur(10px);
background: rgba(255, 255, 255, 0.85) !important;
border-bottom: 1px solid rgba(0, 0, 0, 0.05) !important;
}
.navbar-brand {
letter-spacing: 0.02em;
color: var(--primary) !important;
font-weight: 700;
}
.card,
.panel {
border: 1px solid var(--border);
border-radius: 14px;
border-radius: 16px;
background: var(--surface);
box-shadow: var(--shadow);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.hero-section {
position: relative;
border-radius: 24px;
overflow: hidden;
background: linear-gradient(135deg, #eff6ff 0%, #ffffff 100%);
border: 1px solid var(--border);
box-shadow: var(--shadow);
}
.hero-card {
border-radius: 18px;
padding: 32px;
background: linear-gradient(135deg, #ffffff 0%, #f8fbff 65%, #eef4ff 100%);
border: 1px solid var(--border);
box-shadow: var(--shadow);
.hero-img-container {
height: 100%;
min-height: 400px;
background-size: cover;
background-position: center;
border-radius: 20px;
}
.hero-content {
padding: 60px 40px;
z-index: 2;
}
.motivation-box {
background: linear-gradient(135deg, var(--primary) 0%, var(--accent) 100%);
color: white;
padding: 40px;
border-radius: 20px;
text-align: center;
box-shadow: 0 15px 35px rgba(59, 130, 246, 0.2);
}
.motivation-box h3 {
font-weight: 700;
margin-bottom: 0;
}
.feature-icon {
width: 60px;
height: 60px;
background: #eff6ff;
color: var(--primary);
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
margin-bottom: 20px;
}
.stat-card {
padding: 20px;
padding: 24px;
border: 1px solid var(--border);
border-radius: 12px;
border-radius: 16px;
background: var(--surface);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.5);
box-shadow: var(--shadow);
text-align: center;
}
.stat-card .fs-4 {
color: var(--primary);
font-weight: 700;
}
.badge-status,
@ -59,8 +112,8 @@ body.app-body {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 12px;
padding: 6px 11px;
font-size: 13px;
padding: 8px 14px;
border-radius: 999px;
font-weight: 600;
border: 1px solid transparent;
@ -68,49 +121,47 @@ body.app-body {
.badge-status.posted,
.badge.posted {
background: #e2e8f0;
color: #334155;
border-color: #cbd5e1;
background: #f1f5f9;
color: #475569;
}
.badge-status.offered,
.badge.offered {
background: #dbeafe;
color: #1d4ed8;
border-color: #bfdbfe;
background: #e0f2fe;
color: #0284c7;
}
.badge-status.confirmed,
.badge.confirmed {
background: #dcfce7;
color: #15803d;
border-color: #bbf7d0;
color: #16a34a;
}
.badge-status.in_transit,
.badge.in_transit {
background: #fef3c7;
color: #b45309;
border-color: #fde68a;
color: #d97706;
}
.badge-status.delivered,
.badge.delivered {
background: #ede9fe;
color: #6d28d9;
border-color: #ddd6fe;
background: #f3e8ff;
color: #9333ea;
}
.table thead th {
font-size: 12px;
font-size: 13px;
text-transform: uppercase;
letter-spacing: 0.04em;
letter-spacing: 0.05em;
color: var(--muted);
border-bottom: 1px solid var(--border);
border-bottom: 2px solid #f1f5f9;
padding-bottom: 12px;
}
.table tbody td {
vertical-align: middle;
padding: 16px 8px;
border-bottom: 1px solid #f8fafc;
}
.table tbody tr:hover {
@ -119,48 +170,62 @@ body.app-body {
.form-control,
.form-select {
border-radius: 8px;
border-radius: 12px;
border: 1px solid var(--border);
padding: 10px 12px;
padding: 12px 16px;
background: #f8fafc;
}
.form-control:focus,
.form-select:focus {
border-color: #93c5fd;
box-shadow: 0 0 0 0.2rem rgba(37, 99, 235, 0.12);
background: white;
border-color: var(--primary);
box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.1);
}
.form-label {
font-weight: 600;
color: #334155;
margin-bottom: 6px;
color: #475569;
margin-bottom: 8px;
}
.btn-primary {
background: var(--primary);
border-color: var(--primary);
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
}
.btn-primary:hover,
.btn-primary:focus {
background: #111827;
border-color: #111827;
background: var(--primary-hover);
border-color: var(--primary-hover);
transform: translateY(-1px);
box-shadow: 0 6px 16px rgba(59, 130, 246, 0.4);
}
.btn {
border-radius: 10px;
border-radius: 12px;
font-weight: 600;
padding: 9px 14px;
padding: 10px 20px;
transition: all 0.2s ease;
}
.btn-outline-dark {
border-color: var(--border);
color: #334155;
}
.btn-outline-dark:hover {
background: #f1f5f9;
color: #0f172a;
border-color: #cbd5e1;
}
.section-title {
font-size: 18px;
font-weight: 600;
margin-bottom: 12px;
font-size: 22px;
font-weight: 700;
margin-bottom: 20px;
color: var(--text);
}
.muted {
@ -168,42 +233,35 @@ body.app-body {
}
.alert {
border-radius: 8px;
}
.page-intro {
margin-bottom: 18px;
}
.table-responsive {
border-radius: 12px;
}
.admin-sidebar {
position: sticky;
top: 88px;
border-radius: 16px;
}
.admin-nav-link {
display: block;
padding: 10px 12px;
border-radius: 10px;
color: #334155;
padding: 12px 16px;
border-radius: 12px;
color: #475569;
text-decoration: none;
font-weight: 600;
border: 1px solid transparent;
transition: all 0.2s ease;
}
.admin-nav-link:hover {
background: #eff6ff;
border-color: #dbeafe;
color: #1d4ed8;
background: #f1f5f9;
color: #0f172a;
}
.admin-nav-link.active {
background: #dbeafe;
background: #eff6ff;
color: var(--primary);
border-color: #bfdbfe;
color: #1d4ed8;
}
[dir="rtl"] .navbar .ms-auto {
@ -220,11 +278,16 @@ body.app-body {
}
@media (max-width: 991px) {
.hero-card {
padding: 24px;
.hero-content {
padding: 30px 20px;
}
.hero-img-container {
min-height: 250px;
margin-top: 20px;
}
.admin-sidebar {
position: static;
}
}
}

View File

@ -0,0 +1 @@
<?xml version='1.0' encoding='UTF-8'?><svg width='400' height='300' xmlns='http://www.w3.org/2000/svg'><rect width='100%' height='100%' fill='#ddd'/><text x='50%' y='50%' dominant-baseline='middle' text-anchor='middle' font-family='sans-serif' font-size='24' fill='#555'>Sample Document</text></svg>

After

Width:  |  Height:  |  Size: 300 B

View File

@ -0,0 +1 @@
<?xml version='1.0' encoding='UTF-8'?><svg width='400' height='300' xmlns='http://www.w3.org/2000/svg'><rect width='100%' height='100%' fill='#ddd'/><text x='50%' y='50%' dominant-baseline='middle' text-anchor='middle' font-family='sans-serif' font-size='24' fill='#555'>Sample Document</text></svg>

After

Width:  |  Height:  |  Size: 300 B

View File

@ -0,0 +1 @@
<?xml version='1.0' encoding='UTF-8'?><svg width='400' height='300' xmlns='http://www.w3.org/2000/svg'><rect width='100%' height='100%' fill='#ddd'/><text x='50%' y='50%' dominant-baseline='middle' text-anchor='middle' font-family='sans-serif' font-size='24' fill='#555'>Sample Document</text></svg>

After

Width:  |  Height:  |  Size: 300 B

View File

@ -22,6 +22,8 @@ $translations = [
'hero_title' => 'Move cargo faster with verified trucks.',
'hero_subtitle' => 'Post shipments, collect offers, and pay via Thawani or bank transfer. Built for local and nearby cross-border moves.',
'hero_tagline' => 'Multilingual Logistics Marketplace',
'register_shipper' => 'Register as Shipper',
'register_owner' => 'Register as Truck Owner',
'cta_shipper' => 'Post a shipment',
'cta_owner' => 'Find loads',
'cta_admin' => 'Open admin',
@ -77,6 +79,7 @@ $translations = [
'status_in_transit' => 'In transit',
'status_delivered' => 'Delivered',
'footer_note' => 'This is the initial MVP slice. Payments are not yet connected.',
'marketing_title_1' => 'For Shippers', 'marketing_desc_1' => 'Find the right truck for your cargo quickly and securely. Post your load and get offers instantly.', 'marketing_title_2' => 'For Truck Owners', 'marketing_desc_2' => 'Maximize your earnings and eliminate empty miles. Browse available shipments and offer your rate.', 'motivation_phrase' => 'Empowering the logistics of tomorrow.', 'why_choose_us' => 'Why Choose CargoLink?', 'feature_1_title' => 'Fast Matching', 'feature_1_desc' => 'Connect with available trucks or shipments in minutes.', 'feature_2_title' => 'Secure Payments', 'feature_2_desc' => 'Your transactions are protected with security.', 'feature_3_title' => 'Verified Users', 'feature_3_desc' => 'We verify all truck owners to ensure peace of mind.',
],
'ar' => [
'app_name' => 'CargoLink',
@ -87,6 +90,8 @@ $translations = [
'hero_title' => 'انقل شحنتك بسرعة مع شاحنات موثوقة.',
'hero_subtitle' => 'أنشئ شحنة، استلم عروضاً، وادفع عبر ثواني أو التحويل البنكي.',
'hero_tagline' => 'منصة لوجستية متعددة اللغات',
'register_shipper' => 'التسجيل كشاحن',
'register_owner' => 'التسجيل كمالك شاحنة',
'cta_shipper' => 'إنشاء شحنة',
'cta_owner' => 'البحث عن الشحنات',
'cta_admin' => 'الدخول للإدارة',
@ -142,6 +147,7 @@ $translations = [
'status_in_transit' => 'قيد النقل',
'status_delivered' => 'تم التسليم',
'footer_note' => 'هذه هي النسخة الأولية. الدفع غير متصل بعد.',
'marketing_title_1' => 'للشاحنين', 'marketing_desc_1' => 'ابحث عن الشاحنة المناسبة لحمولتك بسرعة وأمان.', 'marketing_title_2' => 'لأصحاب الشاحنات', 'marketing_desc_2' => 'عظّم أرباحك وتجنب العودة فارغاً.', 'motivation_phrase' => 'تمكين الخدمات اللوجستية للمستقبل.', 'why_choose_us' => 'لماذا تختار كارجو لينك؟', 'feature_1_title' => 'مطابقة سريعة', 'feature_1_desc' => 'تواصل مع الشاحنات المتاحة في دقائق.', 'feature_2_title' => 'مدفوعات آمنة', 'feature_2_desc' => 'معاملاتك محمية بأعلى معايير الأمان.', 'feature_3_title' => 'مستخدمون موثوقون', 'feature_3_desc' => 'نقوم بالتحقق من جميع أصحاب الشاحنات لضمان راحتك.',
],
];
@ -177,6 +183,53 @@ CREATE TABLE IF NOT EXISTS shipments (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
SQL;
db()->exec($sql);
try { db()->exec("ALTER TABLE users ADD COLUMN status ENUM('pending','active','rejected') NOT NULL DEFAULT 'active'"); } catch (Exception $e) {}
db()->exec(
"CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
full_name VARCHAR(255) NOT NULL,
role ENUM('admin','shipper','truck_owner') NOT NULL,
status ENUM('pending','active','rejected') NOT NULL DEFAULT 'active',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
);
db()->exec(
"CREATE TABLE IF NOT EXISTS shipper_profiles (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL UNIQUE,
company_name VARCHAR(255) NOT NULL,
phone VARCHAR(40) NOT NULL,
country_id INT NULL,
city_id INT NULL,
address_line VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_shipper_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
);
db()->exec(
"CREATE TABLE IF NOT EXISTS truck_owner_profiles (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL UNIQUE,
phone VARCHAR(40) NOT NULL,
country_id INT NULL,
city_id INT NULL,
address_line VARCHAR(255) NOT NULL,
truck_type VARCHAR(120) NOT NULL,
load_capacity DECIMAL(10,2) NOT NULL,
plate_no VARCHAR(80) NOT NULL,
id_card_path TEXT NOT NULL,
truck_pic_path VARCHAR(255) NOT NULL,
registration_path TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_owner_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
);
}
function set_flash(string $type, string $message): void

View File

@ -28,55 +28,114 @@ function render_header(string $title, string $active = ''): void
<link rel="stylesheet" href="/assets/css/custom.css?v=<?= time() ?>">
</head>
<body class="app-body">
<nav class="navbar navbar-expand-lg navbar-light bg-white border-bottom sticky-top">
<nav class="navbar navbar-expand-lg navbar-light bg-white border-bottom sticky-top shadow-sm py-3">
<div class="container">
<a class="navbar-brand fw-semibold" href="<?= e(url_with_lang('index.php')) ?>">
<a class="navbar-brand fs-4" href="<?= e(url_with_lang('index.php')) ?>">
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" fill="currentColor" class="bi bi-box-seam me-1 text-primary" viewBox="0 0 16 16">
<path d="M8.186 1.113a.5.5 0 0 0-.372 0L1.846 3.5l2.404.961L10.404 2zm3.564 1.426L5.596 5 8 5.961 14.154 3.5zm3.25 1.7-6.5 2.6v7.922l6.5-2.6V4.24zM7.5 14.762V6.84L1 4.239v7.923zM7.443.184a1.5 1.5 0 0 1 1.114 0l7.129 2.852A.5.5 0 0 1 16 3.5v8.662a1 1 0 0 1-.629.928l-7.185 2.874a.5.5 0 0 1-.372 0L.63 13.09a1 1 0 0 1-.63-.928V3.5a.5.5 0 0 1 .314-.464z"/>
</svg>
<?= e(t('app_name')) ?>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#mainNav">
<button class="navbar-toggler border-0" type="button" data-bs-toggle="collapse" data-bs-target="#mainNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="mainNav">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<ul class="navbar-nav me-auto mb-2 mb-lg-0 fw-semibold">
<li class="nav-item">
<a class="nav-link <?= $active === 'home' ? 'active' : '' ?>" href="<?= e(url_with_lang('index.php')) ?>">
<a class="nav-link <?= $active === 'home' ? 'active text-primary' : '' ?>" href="<?= e(url_with_lang('index.php')) ?>">
<?= e(t('nav_home')) ?>
</a>
</li>
<li class="nav-item">
<a class="nav-link <?= $active === 'shipper' ? 'active' : '' ?>" href="<?= e(url_with_lang('shipper_dashboard.php')) ?>">
<?= e(t('nav_shipper')) ?>
</a>
</li>
<li class="nav-item">
<a class="nav-link <?= $active === 'owner' ? 'active' : '' ?>" href="<?= e(url_with_lang('truck_owner_dashboard.php')) ?>">
<?= e(t('nav_owner')) ?>
</a>
</li>
<li class="nav-item">
<a class="nav-link <?= $active === 'admin' ? 'active' : '' ?>" href="<?= e(url_with_lang('admin_dashboard.php')) ?>">
<?= e(t('nav_admin')) ?>
</a>
</li>
</ul>
<a class="btn btn-sm btn-outline-dark" href="<?= e(current_url_with_lang($toggleLang)) ?>">
<?= e($toggleLabel) ?>
</a>
<div class="d-flex align-items-center gap-3">
<div class="dropdown">
<a class="text-decoration-none text-muted fw-semibold dropdown-toggle" href="#" id="loginDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Demo Login
</a>
<ul class="dropdown-menu dropdown-menu-end shadow-sm border-0 mt-3" aria-labelledby="loginDropdown" style="border-radius: 12px;">
<li>
<a class="dropdown-item py-2 <?= $active === 'shipper' ? 'active' : '' ?>" href="<?= e(url_with_lang('shipper_dashboard.php')) ?>">
<?= e(t('nav_shipper')) ?>
</a>
</li>
<li>
<a class="dropdown-item py-2 <?= $active === 'owner' ? 'active' : '' ?>" href="<?= e(url_with_lang('truck_owner_dashboard.php')) ?>">
<?= e(t('nav_owner')) ?>
</a>
</li>
<li><hr class="dropdown-divider"></li>
<li>
<a class="dropdown-item py-2 <?= $active === 'admin' ? 'active' : '' ?>" href="<?= e(url_with_lang('admin_dashboard.php')) ?>">
<?= e(t('nav_admin')) ?>
</a>
</li>
</ul>
</div>
<a class="btn btn-sm btn-outline-dark rounded-pill px-3 fw-bold" href="<?= e(current_url_with_lang($toggleLang)) ?>">
<?= e($toggleLabel) ?>
</a>
<a class="btn btn-primary btn-sm rounded-pill px-4 fw-bold shadow-sm d-none d-lg-block" href="<?= e(url_with_lang('register.php', ['role' => 'shipper'])) ?>">
Get Started
</a>
</div>
</div>
</div>
</nav>
<main class="container py-4">
<main class="container py-5">
<?php
}
function render_footer(): void
{
global $lang;
?>
</main>
<footer class="border-top py-3">
<div class="container d-flex flex-column flex-md-row justify-content-between align-items-center small text-muted">
<span><?= e(t('footer_note')) ?></span>
<span><?= e(date('Y-m-d H:i')) ?> UTC</span>
<footer class="bg-white border-top py-5 mt-5">
<div class="container">
<div class="row g-4 mb-4">
<div class="col-md-4">
<div class="d-flex align-items-center mb-3">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-box-seam me-2 text-primary" viewBox="0 0 16 16">
<path d="M8.186 1.113a.5.5 0 0 0-.372 0L1.846 3.5l2.404.961L10.404 2zm3.564 1.426L5.596 5 8 5.961 14.154 3.5zm3.25 1.7-6.5 2.6v7.922l6.5-2.6V4.24zM7.5 14.762V6.84L1 4.239v7.923zM7.443.184a1.5 1.5 0 0 1 1.114 0l7.129 2.852A.5.5 0 0 1 16 3.5v8.662a1 1 0 0 1-.629.928l-7.185 2.874a.5.5 0 0 1-.372 0L.63 13.09a1 1 0 0 1-.63-.928V3.5a.5.5 0 0 1 .314-.464z"/>
</svg>
<h5 class="fw-bold mb-0 text-dark"><?= e(t('app_name')) ?></h5>
</div>
<p class="text-muted small pe-md-4">
<?= e(t('motivation_phrase')) ?>
</p>
</div>
<div class="col-md-2 offset-md-2">
<h6 class="fw-bold mb-3">Company</h6>
<ul class="list-unstyled text-muted small">
<li class="mb-2"><a href="#" class="text-decoration-none text-muted">About Us</a></li>
<li class="mb-2"><a href="#" class="text-decoration-none text-muted">Careers</a></li>
<li class="mb-2"><a href="#" class="text-decoration-none text-muted">Contact</a></li>
</ul>
</div>
<div class="col-md-2">
<h6 class="fw-bold mb-3">Resources</h6>
<ul class="list-unstyled text-muted small">
<li class="mb-2"><a href="#" class="text-decoration-none text-muted">Help Center</a></li>
<li class="mb-2"><a href="#" class="text-decoration-none text-muted">Terms of Service</a></li>
<li class="mb-2"><a href="#" class="text-decoration-none text-muted">Privacy Policy</a></li>
</ul>
</div>
<div class="col-md-2">
<h6 class="fw-bold mb-3">Language</h6>
<div class="d-flex gap-2">
<a href="<?= e(current_url_with_lang('en')) ?>" class="text-decoration-none <?= $lang === 'en' ? 'text-primary fw-bold' : 'text-muted' ?>">EN</a>
<span class="text-muted">|</span>
<a href="<?= e(current_url_with_lang('ar')) ?>" class="text-decoration-none <?= $lang === 'ar' ? 'text-primary fw-bold' : 'text-muted' ?>">AR</a>
</div>
</div>
</div>
<div class="d-flex flex-column flex-md-row justify-content-between align-items-center pt-4 border-top small text-muted">
<span>&copy; <?= date('Y') ?> <?= e(t('app_name')) ?>. All rights reserved.</span>
<span class="mt-2 mt-md-0"><?= e(t('footer_note')) ?></span>
</div>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
@ -90,13 +149,16 @@ function render_admin_sidebar(string $active = 'dashboard'): void
{
$items = [
'dashboard' => ['label' => 'Dashboard', 'href' => url_with_lang('admin_dashboard.php')],
'locations' => ['label' => 'Locations', 'href' => url_with_lang('admin_manage_locations.php')],
'countries' => ['label' => 'Countries', 'href' => url_with_lang('admin_countries.php')],
'cities' => ['label' => 'Cities', 'href' => url_with_lang('admin_cities.php')],
'shippers' => ['label' => 'Shippers', 'href' => url_with_lang('admin_shippers.php')],
'truck_owners' => ['label' => 'Truck Owners', 'href' => url_with_lang('admin_truck_owners.php')],
'register' => ['label' => 'User Registration', 'href' => url_with_lang('register.php')],
];
?>
<aside class="admin-sidebar panel p-3">
<h2 class="h6 mb-3">Admin Panel</h2>
<nav class="nav flex-column gap-1">
<aside class="admin-sidebar panel p-4 shadow-sm border-0">
<h2 class="h5 fw-bold mb-4">Admin Panel</h2>
<nav class="nav flex-column gap-2">
<?php foreach ($items as $key => $item): ?>
<a class="admin-nav-link <?= $active === $key ? 'active' : '' ?>" href="<?= e($item['href']) ?>">
<?= e($item['label']) ?>
@ -105,4 +167,4 @@ function render_admin_sidebar(string $active = 'dashboard'): void
</nav>
</aside>
<?php
}
}

258
index.php
View File

@ -31,36 +31,142 @@ try {
render_header(t('app_name'), 'home');
?>
<section class="hero-card mb-4">
<div class="row align-items-center g-4">
<div class="col-lg-7">
<p class="text-uppercase small fw-semibold text-muted mb-2"><?= e(t('hero_tagline')) ?></p>
<h1 class="display-6 fw-semibold"><?= e(t('hero_title')) ?></h1>
<p class="muted mt-3"><?= e(t('hero_subtitle')) ?></p>
<div class="d-flex flex-wrap gap-2 mt-4">
<a class="btn btn-primary" href="<?= e(url_with_lang('shipper_dashboard.php')) ?>"><?= e(t('cta_shipper')) ?></a>
<a class="btn btn-outline-dark" href="<?= e(url_with_lang('truck_owner_dashboard.php')) ?>"><?= e(t('cta_owner')) ?></a>
<a class="btn btn-outline-dark" href="<?= e(url_with_lang('admin_dashboard.php')) ?>"><?= e(t('cta_admin')) ?></a>
<div class="hero-section mb-5">
<div class="row g-0">
<div class="col-lg-6 d-flex flex-column justify-content-center">
<div class="hero-content">
<span class="badge bg-primary bg-opacity-10 text-primary mb-3 px-3 py-2 fs-6 rounded-pill">
<?= e(t('hero_tagline')) ?>
</span>
<h1 class="display-5 fw-bold mb-4" style="line-height: 1.2;">
<?= e(t('hero_title')) ?>
</h1>
<p class="fs-5 text-muted mb-5">
<?= e(t('hero_subtitle')) ?>
</p>
<div class="d-flex flex-column flex-sm-row gap-3">
<a class="btn btn-primary btn-lg shadow-sm" href="<?= e(url_with_lang('register.php', ['role' => 'shipper'])) ?>">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-box-seam me-2" viewBox="0 0 16 16">
<path d="M8.186 1.113a.5.5 0 0 0-.372 0L1.846 3.5l2.404.961L10.404 2zm3.564 1.426L5.596 5 8 5.961 14.154 3.5zm3.25 1.7-6.5 2.6v7.922l6.5-2.6V4.24zM7.5 14.762V6.84L1 4.239v7.923zM7.443.184a1.5 1.5 0 0 1 1.114 0l7.129 2.852A.5.5 0 0 1 16 3.5v8.662a1 1 0 0 1-.629.928l-7.185 2.874a.5.5 0 0 1-.372 0L.63 13.09a1 1 0 0 1-.63-.928V3.5a.5.5 0 0 1 .314-.464z"/>
</svg>
<?= e(t('register_shipper')) ?>
</a>
<a class="btn btn-outline-primary btn-lg bg-white shadow-sm" href="<?= e(url_with_lang('register.php', ['role' => 'truck_owner'])) ?>">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-truck me-2" viewBox="0 0 16 16">
<path d="M0 3.5A1.5 1.5 0 0 1 1.5 2h9A1.5 1.5 0 0 1 12 3.5V5h1.02a1.5 1.5 0 0 1 1.17.563l1.481 1.85a1.5 1.5 0 0 1 .329.938V10.5a1.5 1.5 0 0 1-1.5 1.5H14a2 2 0 1 1-4 0H5a2 2 0 1 1-3.998-.085A1.5 1.5 0 0 1 0 10.5v-7zm1.294 7.456A1.999 1.999 0 0 1 4.732 11h5.536a2.01 2.01 0 0 1 .732-.732V3.5a.5.5 0 0 0-.5-.5h-9a.5.5 0 0 0-.5.5v7a.5.5 0 0 0 .294.456zM12 10a2 2 0 0 1 1.732 1h.768a.5.5 0 0 0 .5-.5V8.35a.5.5 0 0 0-.11-.312l-1.48-1.85A.5.5 0 0 0 13.02 6H12v4zm-9 1a1 1 0 1 0 0 2 1 1 0 0 0 0-2zm9 0a1 1 0 1 0 0 2 1 1 0 0 0 0-2z"/>
</svg>
<?= e(t('register_owner')) ?>
</a>
</div>
</div>
</div>
<div class="col-lg-5">
<div class="row g-3">
<div class="col-12">
<div class="stat-card">
<div class="small text-muted"><?= e(t('stats_shipments')) ?></div>
<div class="fs-4 fw-semibold"><?= e($stats['shipments']) ?></div>
<div class="col-lg-6">
<div class="hero-img-container" style="background-image: url('https://picsum.photos/id/1073/800/600');"></div>
</div>
</div>
</div>
<div class="row mb-5 g-4">
<div class="col-md-4">
<div class="stat-card">
<div class="fs-4"><?= e($stats['shipments']) ?>+</div>
<div class="text-muted fw-semibold mt-1"><?= e(t('stats_shipments')) ?></div>
</div>
</div>
<div class="col-md-4">
<div class="stat-card">
<div class="fs-4"><?= e($stats['offers']) ?>+</div>
<div class="text-muted fw-semibold mt-1"><?= e(t('stats_offers')) ?></div>
</div>
</div>
<div class="col-md-4">
<div class="stat-card">
<div class="fs-4"><?= e($stats['confirmed']) ?>+</div>
<div class="text-muted fw-semibold mt-1"><?= e(t('stats_confirmed')) ?></div>
</div>
</div>
</div>
<section class="mb-5">
<div class="text-center mb-5">
<h2 class="display-6 fw-bold mb-3"><?= e(t('why_choose_us')) ?></h2>
<p class="text-muted fs-5 mx-auto" style="max-width: 600px;"><?= e(t('motivation_phrase')) ?></p>
</div>
<div class="row g-4">
<div class="col-md-4">
<div class="panel p-4 h-100 text-center border-0 shadow-sm">
<div class="feature-icon mx-auto">
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" fill="currentColor" class="bi bi-lightning-charge" viewBox="0 0 16 16">
<path d="M11.251.068a.5.5 0 0 1 .227.58L9.677 6.5H13a.5.5 0 0 1 .364.843l-8 8.5a.5.5 0 0 1-.842-.49L6.323 9.5H3a.5.5 0 0 1-.364-.843l8-8.5a.5.5 0 0 1 .615-.09zM4.157 8.5H7a.5.5 0 0 1 .478.647L6.11 13.59l5.732-6.09H9a.5.5 0 0 1-.478-.647L9.89 2.41 4.157 8.5z"/>
</svg>
</div>
<h4 class="fw-bold mb-3"><?= e(t('feature_1_title')) ?></h4>
<p class="text-muted mb-0"><?= e(t('feature_1_desc')) ?></p>
</div>
</div>
<div class="col-md-4">
<div class="panel p-4 h-100 text-center border-0 shadow-sm">
<div class="feature-icon mx-auto">
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" fill="currentColor" class="bi bi-shield-check" viewBox="0 0 16 16">
<path d="M5.338 1.59a61.44 61.44 0 0 0-2.837.856.481.481 0 0 0-.328.39c-.554 4.157.726 7.19 2.253 9.188a10.725 10.725 0 0 0 2.287 2.233c.346.244.652.42.893.533.12.057.218.095.293.118a.55.55 0 0 0 .101.025.615.615 0 0 0 .1-.025c.076-.023.174-.061.294-.118.24-.113.547-.29.893-.533a10.726 10.726 0 0 0 2.287-2.233c1.527-1.997 2.807-5.031 2.253-9.188a.48.48 0 0 0-.328-.39c-.651-.213-1.75-.56-2.837-.855C9.552 1.29 8.531 1.067 8 1.067c-.53 0-1.552.223-2.662.524zM5.072.56C6.157.265 7.31 0 8 0s1.843.265 2.928.56c1.11.3 2.229.655 2.887.87a1.54 1.54 0 0 1 1.044 1.262c.596 4.477-.787 7.795-2.465 9.99a11.775 11.775 0 0 1-2.517 2.453 7.159 7.159 0 0 1-1.048.625c-.28.132-.581.24-.829.24s-.548-.108-.829-.24a7.158 7.158 0 0 1-1.048-.625 11.777 11.777 0 0 1-2.517-2.453C1.928 10.487.545 7.169 1.141 2.692A1.54 1.54 0 0 1 2.185 1.43 62.456 62.456 0 0 1 5.072.56z"/>
<path d="M10.854 5.146a.5.5 0 0 1 0 .708l-3 3a.5.5 0 0 1-.708 0l-1.5-1.5a.5.5 0 1 1 .708-.708L7.5 7.793l2.646-2.647a.5.5 0 0 1 .708 0z"/>
</svg>
</div>
<h4 class="fw-bold mb-3"><?= e(t('feature_2_title')) ?></h4>
<p class="text-muted mb-0"><?= e(t('feature_2_desc')) ?></p>
</div>
</div>
<div class="col-md-4">
<div class="panel p-4 h-100 text-center border-0 shadow-sm">
<div class="feature-icon mx-auto">
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" fill="currentColor" class="bi bi-people" viewBox="0 0 16 16">
<path d="M15 14s1 0 1-1-1-4-5-4-5 3-5 4 1 1 1 1h8Zm-7.978-1A.261.261 0 0 1 7 12.996c.001-.264.167-1.03.76-1.72C8.312 10.629 9.282 10 11 10c1.717 0 2.687.63 3.24 1.276.593.69.758 1.457.76 1.72l-.008.002a.274.274 0 0 1-.014.002H7.022ZM11 7a2 2 0 1 0 0-4 2 2 0 0 0 0 4Zm3-2a3 3 0 1 1-6 0 3 3 0 0 1 6 0ZM6.936 9.28a5.88 5.88 0 0 0-1.23-.247A7.35 7.35 0 0 0 5 9c-4 0-5 3-5 4 0 .667.333 1 1 1h4.216A2.238 2.238 0 0 1 5 13c0-1.01.377-2.042 1.09-2.904.243-.294.526-.569.846-.816ZM4.92 10A5.493 5.493 0 0 0 4 13H1c0-.26.164-1.03.76-1.724.545-.636 1.492-1.256 3.16-1.275ZM1.5 5.5a3 3 0 1 1 6 0 3 3 0 0 1-6 0Zm3-2a2 2 0 1 0 0 4 2 2 0 0 0 0-4Z"/>
</svg>
</div>
<h4 class="fw-bold mb-3"><?= e(t('feature_3_title')) ?></h4>
<p class="text-muted mb-0"><?= e(t('feature_3_desc')) ?></p>
</div>
</div>
</div>
</section>
<section class="mb-5">
<div class="row g-4 align-items-center">
<div class="col-md-6 order-md-2">
<img src="https://picsum.photos/id/1015/600/400" alt="Logistics" class="img-fluid rounded-4 shadow-sm" style="width: 100%; object-fit: cover; height: 350px;">
</div>
<div class="col-md-6 order-md-1">
<div class="p-lg-4">
<h2 class="display-6 fw-bold mb-4"><?= e(t('section_workflow')) ?></h2>
<div class="d-flex mb-4">
<div class="flex-shrink-0">
<div class="bg-primary text-white rounded-circle d-flex align-items-center justify-content-center fs-5 fw-bold" style="width: 45px; height: 45px;">1</div>
</div>
<div class="ms-3">
<h5 class="fw-bold mb-1"><?= e(t('step_post')) ?></h5>
<p class="text-muted mb-0"><?= e(t('marketing_desc_1')) ?></p>
</div>
</div>
<div class="col-6">
<div class="stat-card">
<div class="small text-muted"><?= e(t('stats_offers')) ?></div>
<div class="fs-5 fw-semibold"><?= e($stats['offers']) ?></div>
<div class="d-flex mb-4">
<div class="flex-shrink-0">
<div class="bg-primary text-white rounded-circle d-flex align-items-center justify-content-center fs-5 fw-bold" style="width: 45px; height: 45px;">2</div>
</div>
<div class="ms-3">
<h5 class="fw-bold mb-1"><?= e(t('step_offer')) ?></h5>
<p class="text-muted mb-0"><?= e(t('marketing_desc_2')) ?></p>
</div>
</div>
<div class="col-6">
<div class="stat-card">
<div class="small text-muted"><?= e(t('stats_confirmed')) ?></div>
<div class="fs-5 fw-semibold"><?= e($stats['confirmed']) ?></div>
<div class="d-flex">
<div class="flex-shrink-0">
<div class="bg-primary text-white rounded-circle d-flex align-items-center justify-content-center fs-5 fw-bold" style="width: 45px; height: 45px;">3</div>
</div>
<div class="ms-3">
<h5 class="fw-bold mb-1"><?= e(t('step_confirm')) ?></h5>
<p class="text-muted mb-0">Secure booking and track the delivery until completion.</p>
</div>
</div>
</div>
@ -68,67 +174,55 @@ render_header(t('app_name'), 'home');
</div>
</section>
<section class="mb-4">
<h2 class="section-title"><?= e(t('section_workflow')) ?></h2>
<div class="row g-3">
<div class="col-md-4">
<div class="panel p-3 h-100">
<div class="fw-semibold mb-1">1</div>
<p class="muted mb-0"><?= e(t('step_post')) ?></p>
</div>
</div>
<div class="col-md-4">
<div class="panel p-3 h-100">
<div class="fw-semibold mb-1">2</div>
<p class="muted mb-0"><?= e(t('step_offer')) ?></p>
</div>
</div>
<div class="col-md-4">
<div class="panel p-3 h-100">
<div class="fw-semibold mb-1">3</div>
<p class="muted mb-0"><?= e(t('step_confirm')) ?></p>
</div>
</div>
</div>
</section>
<section class="panel p-3">
<div class="d-flex justify-content-between align-items-center mb-2">
<?php if ($recentShipments): ?>
<section class="panel p-4 mb-5">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2 class="section-title mb-0"><?= e(t('recent_shipments')) ?></h2>
<a class="small text-decoration-none" href="<?= e(url_with_lang('shipper_dashboard.php')) ?>"><?= e(t('cta_shipper')) ?></a>
<a class="btn btn-outline-primary btn-sm rounded-pill px-3" href="<?= e(url_with_lang('register.php', ['role' => 'shipper'])) ?>"><?= e(t('cta_shipper')) ?></a>
</div>
<?php if (!$recentShipments): ?>
<div class="text-muted"><?= e(t('no_shipments')) ?></div>
<?php else: ?>
<div class="table-responsive">
<table class="table align-middle mb-0">
<thead>
<div class="table-responsive">
<table class="table align-middle mb-0">
<thead>
<tr>
<th><?= e(t('shipper_company')) ?></th>
<th><?= e(t('origin')) ?></th>
<th><?= e(t('destination')) ?></th>
<th><?= e(t('status')) ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($recentShipments as $row): ?>
<tr>
<th><?= e(t('shipper_company')) ?></th>
<th><?= e(t('origin')) ?></th>
<th><?= e(t('destination')) ?></th>
<th><?= e(t('status')) ?></th>
<th><?= e(t('actions')) ?></th>
<td class="fw-semibold"><?= e($row['shipper_company']) ?></td>
<td>
<div class="d-flex align-items-center">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="bi bi-geo-alt me-2 text-muted" viewBox="0 0 16 16"><path d="M12.166 8.94c-.524 1.062-1.234 2.12-1.96 3.07A31.493 31.493 0 0 1 8 14.58a31.481 31.481 0 0 1-2.206-2.57c-.726-.95-1.436-2.008-1.96-3.07C3.304 7.867 3 6.862 3 6a5 5 0 0 1 10 0c0 .862-.305 1.867-.834 2.94zM8 16s6-5.686 6-10A6 6 0 0 0 2 6c0 4.314 6 10 6 10z"/><path d="M8 8a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm0 1a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/></svg>
<?= e($row['origin_city']) ?>
</div>
</td>
<td>
<div class="d-flex align-items-center">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="bi bi-geo-alt-fill me-2 text-primary" viewBox="0 0 16 16"><path d="M8 16s6-5.686 6-10A6 6 0 0 0 2 6c0 4.314 6 10 6 10zm0-7a3 3 0 1 1 0-6 3 3 0 0 1 0 6z"/></svg>
<?= e($row['destination_city']) ?>
</div>
</td>
<td><span class="badge-status <?= e($row['status']) ?>"><?= e(status_label($row['status'])) ?></span></td>
</tr>
</thead>
<tbody>
<?php foreach ($recentShipments as $row): ?>
<tr>
<td><?= e($row['shipper_company']) ?></td>
<td><?= e($row['origin_city']) ?></td>
<td><?= e($row['destination_city']) ?></td>
<td><span class="badge-status <?= e($row['status']) ?>"><?= e(status_label($row['status'])) ?></span></td>
<td>
<a class="btn btn-sm btn-outline-dark" href="<?= e(url_with_lang('shipment_detail.php', ['id' => $row['id']])) ?>">
<?= e(t('view')) ?>
</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
<?php endforeach; ?>
</tbody>
</table>
</div>
</section>
<?php endif; ?>
<?php render_footer(); ?>
<div class="motivation-box mb-5">
<h3 class="display-6 fw-bold mb-3">Ready to transform your logistics?</h3>
<p class="fs-5 text-white-50 mb-4 mx-auto" style="max-width: 600px;">Join our platform today to find reliable trucks or secure the best shipments in the market.</p>
<div class="d-flex flex-wrap justify-content-center gap-3">
<a class="btn btn-light btn-lg px-4 text-primary fw-bold" href="<?= e(url_with_lang('register.php', ['role' => 'shipper'])) ?>"><?= e(t('register_shipper')) ?></a>
<a class="btn btn-outline-light btn-lg px-4 fw-bold" href="<?= e(url_with_lang('register.php', ['role' => 'truck_owner'])) ?>"><?= e(t('register_owner')) ?></a>
</div>
</div>
<?php render_footer(); ?>

View File

@ -2,50 +2,124 @@
declare(strict_types=1);
require_once __DIR__ . '/includes/layout.php';
ensure_schema();
$errors = [];
$saved = false;
$saved_role = '';
$values = [
'role' => $_GET['role'] ?? 'shipper',
'full_name' => '',
'email' => '',
'phone' => '',
'country_id' => '',
'city_id' => '',
'address_line' => '',
'company_name' => '',
'truck_type' => '',
'load_capacity' => '',
'plate_no' => '',
];
$countries = db()->query("SELECT id, name_en, name_ar FROM countries ORDER BY name_en ASC")->fetchAll();
$cities = db()->query("SELECT id, country_id, name_en, name_ar FROM cities ORDER BY name_en ASC")->fetchAll();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$role = $_POST['role'] ?? 'shipper';
$fullName = trim($_POST['full_name'] ?? '');
$email = trim($_POST['email'] ?? '');
$phone = trim($_POST['phone'] ?? '');
$countryId = (int)($_POST['country_id'] ?? 0);
$cityId = (int)($_POST['city_id'] ?? 0);
$addressLine = trim($_POST['address_line'] ?? '');
$companyName = trim($_POST['company_name'] ?? '');
$passwordRaw = (string)($_POST['password'] ?? '');
$values = [
'role' => $role,
'full_name' => $fullName,
'email' => $email,
'phone' => $phone,
'country_id' => $countryId > 0 ? (string)$countryId : '',
'city_id' => $cityId > 0 ? (string)$cityId : '',
'address_line' => $addressLine,
'company_name' => $companyName,
'truck_type' => trim($_POST['truck_type'] ?? ''),
'load_capacity' => trim($_POST['load_capacity'] ?? ''),
'plate_no' => trim($_POST['plate_no'] ?? ''),
];
if (!in_array($role, ['shipper', 'truck_owner'], true)) {
$errors[] = 'Invalid role selected.';
}
if ($fullName === '') {
$errors[] = 'Full name is required.';
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[] = 'Please provide a valid email address.';
}
if ($phone === '') {
$errors[] = 'Phone number is required.';
}
if ($countryId <= 0 || $cityId <= 0) {
$errors[] = 'Please select country and city.';
} else {
$cityCheck = db()->prepare("SELECT COUNT(*) FROM cities WHERE id = ? AND country_id = ?");
$cityCheck->execute([$cityId, $countryId]);
if ((int)$cityCheck->fetchColumn() === 0) {
$errors[] = 'Selected city does not belong to selected country.';
}
}
if ($addressLine === '') {
$errors[] = 'Address is required.';
}
if (strlen($passwordRaw) < 6) {
$errors[] = 'Password must be at least 6 characters.';
}
if ($role === 'shipper' && $companyName === '') {
$errors[] = 'Company name is required for shipper registration.';
}
if (!$errors) {
$password = password_hash($passwordRaw, PASSWORD_DEFAULT);
$stmt = db()->prepare("INSERT INTO users (email, password, role) VALUES (?, ?, ?)");
$stmt->execute([$email, $password, $role]);
$userId = (int)db()->lastInsertId();
$pdo = db();
if ($role === 'truck_owner') {
$truckType = trim($_POST['truck_type'] ?? '');
$loadCapacity = trim($_POST['load_capacity'] ?? '');
$plateNo = trim($_POST['plate_no'] ?? '');
try {
$pdo->beginTransaction();
if ($truckType === '' || $loadCapacity === '' || $plateNo === '') {
$errors[] = 'Please complete truck details.';
} elseif (!is_numeric($loadCapacity)) {
$errors[] = 'Load capacity must be numeric.';
}
$status = ($role === 'truck_owner') ? 'pending' : 'active';
$stmt = $pdo->prepare("INSERT INTO users (email, password, full_name, role, status) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$email, $password, $fullName, $role, $status]);
$userId = (int)$pdo->lastInsertId();
if ($role === 'shipper') {
$shipperStmt = $pdo->prepare(
"INSERT INTO shipper_profiles (user_id, company_name, phone, country_id, city_id, address_line)
VALUES (?, ?, ?, ?, ?, ?)"
);
$shipperStmt->execute([$userId, $companyName, $phone, $countryId, $cityId, $addressLine]);
} else {
$truckType = trim($_POST['truck_type'] ?? '');
$loadCapacity = trim($_POST['load_capacity'] ?? '');
$plateNo = trim($_POST['plate_no'] ?? '');
if ($truckType === '' || $loadCapacity === '' || $plateNo === '') {
$errors[] = 'Please complete truck details.';
} elseif (!is_numeric($loadCapacity) || (float)$loadCapacity <= 0) {
$errors[] = 'Load capacity must be numeric and greater than zero.';
}
if (!$errors) {
$uploadDir = __DIR__ . '/uploads/profiles/' . $userId . '/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0775, true);
}
$allowed = ['image/jpeg' => 'jpg', 'image/png' => 'png', 'image/webp' => 'webp'];
$saveImage = static function (string $tmpName, string $prefix) use ($uploadDir, $allowed): ?string {
$maxSize = 8 * 1024 * 1024;
$saveImage = static function (string $tmpName, int $size, string $prefix) use ($uploadDir, $allowed, $maxSize): ?string {
if ($size <= 0 || $size > $maxSize) {
return null;
}
$mime = mime_content_type($tmpName) ?: '';
if (!isset($allowed[$mime])) {
return null;
@ -59,42 +133,47 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
};
$idCardPaths = [];
foreach (array_slice($_FILES['id_card']['tmp_name'] ?? [], 0, 2) as $tmp) {
if (!is_uploaded_file($tmp)) {
continue;
}
$path = $saveImage($tmp, 'id_');
if ($path) {
$idCardPaths[] = $path;
}
if (is_uploaded_file($_FILES['id_card_front']['tmp_name'] ?? '')) {
$path = $saveImage($_FILES['id_card_front']['tmp_name'], (int)$_FILES['id_card_front']['size'], 'id_front_');
if ($path) $idCardPaths[] = $path;
}
if (is_uploaded_file($_FILES['id_card_back']['tmp_name'] ?? '')) {
$path = $saveImage($_FILES['id_card_back']['tmp_name'], (int)$_FILES['id_card_back']['size'], 'id_back_');
if ($path) $idCardPaths[] = $path;
}
$regPaths = [];
foreach (array_slice($_FILES['registration']['tmp_name'] ?? [], 0, 2) as $tmp) {
if (!is_uploaded_file($tmp)) {
continue;
}
$path = $saveImage($tmp, 'reg_');
if ($path) {
$regPaths[] = $path;
}
if (is_uploaded_file($_FILES['truck_reg_front']['tmp_name'] ?? '')) {
$path = $saveImage($_FILES['truck_reg_front']['tmp_name'], (int)$_FILES['truck_reg_front']['size'], 'reg_front_');
if ($path) $regPaths[] = $path;
}
if (is_uploaded_file($_FILES['truck_reg_back']['tmp_name'] ?? '')) {
$path = $saveImage($_FILES['truck_reg_back']['tmp_name'], (int)$_FILES['truck_reg_back']['size'], 'reg_back_');
if ($path) $regPaths[] = $path;
}
$truckPic = null;
$truckTmp = $_FILES['truck_picture']['tmp_name'] ?? '';
if (is_uploaded_file($truckTmp)) {
$truckPic = $saveImage($truckTmp, 'truck_');
$truckSize = (int)($_FILES['truck_picture']['size'] ?? 0);
$truckPic = $saveImage($truckTmp, $truckSize, 'truck_');
}
if (count($idCardPaths) < 2 || count($regPaths) < 2 || !$truckPic) {
$errors[] = 'Please upload all required truck-owner images (ID front/back, registration front/back, truck photo).';
} else {
$profileStmt = db()->prepare(
"INSERT INTO truck_owner_profiles (user_id, truck_type, load_capacity, plate_no, id_card_path, truck_pic_path, registration_path)
VALUES (?, ?, ?, ?, ?, ?, ?)"
}
if (!$errors) {
$ownerStmt = $pdo->prepare(
"INSERT INTO truck_owner_profiles (user_id, phone, country_id, city_id, address_line, truck_type, load_capacity, plate_no, id_card_path, truck_pic_path, registration_path)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
);
$profileStmt->execute([
$ownerStmt->execute([
$userId,
$phone,
$countryId,
$cityId,
$addressLine,
$truckType,
$loadCapacity,
$plateNo,
@ -104,25 +183,55 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
]);
}
}
}
if (!$errors) {
$saved = true;
if ($errors) {
$pdo->rollBack();
} else {
$pdo->commit();
$saved = true;
$saved_role = $role;
$values = [
'role' => $_GET['role'] ?? 'shipper',
'full_name' => '',
'email' => '',
'phone' => '',
'country_id' => '',
'city_id' => '',
'address_line' => '',
'company_name' => '',
'truck_type' => '',
'load_capacity' => '',
'plate_no' => '',
];
}
} catch (Throwable $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
if (stripos($e->getMessage(), 'Duplicate entry') !== false) {
$errors[] = 'This email is already registered.';
} else {
$errors[] = 'Registration failed. Please try again.';
}
}
}
}
render_header('Register Account');
render_header('Shipper & Truck Owner Registration');
?>
<div class="page-intro">
<h1 class="section-title mb-1">Create account</h1>
<p class="muted mb-0">Register as a shipper or truck owner using a clean onboarding form.</p>
<h1 class="section-title mb-1">Create your logistics account</h1>
<p class="muted mb-0">Shippers and truck owners can self-register with full profile details.</p>
</div>
<div class="panel p-4">
<?php if ($saved): ?>
<div class="alert alert-success">Registration completed successfully.</div>
<?php if ($saved_role === 'truck_owner'): ?>
<div class="alert alert-success">Registration completed successfully. Your account is pending admin approval.</div>
<?php else: ?>
<div class="alert alert-success">Registration completed successfully.</div>
<?php endif; ?>
<?php endif; ?>
<?php if ($errors): ?>
<div class="alert alert-warning"><?= e(implode(' ', $errors)) ?></div>
@ -130,21 +239,60 @@ render_header('Register Account');
<form method="post" enctype="multipart/form-data" id="regForm" novalidate>
<div class="row g-3">
<div class="col-md-4">
<div class="col-md-3">
<label class="form-label" for="role">Role</label>
<select name="role" id="role" class="form-select" onchange="toggleFields()" required>
<option value="shipper">Shipper</option>
<option value="truck_owner">Truck Owner</option>
<option value="shipper" <?= $values['role'] === 'shipper' ? 'selected' : '' ?>>Shipper</option>
<option value="truck_owner" <?= $values['role'] === 'truck_owner' ? 'selected' : '' ?>>Truck Owner</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label" for="email">Email</label>
<input type="email" name="email" id="email" class="form-control" required>
<div class="col-md-3">
<label class="form-label" for="full_name">Full name</label>
<input type="text" name="full_name" id="full_name" class="form-control" value="<?= e($values['full_name']) ?>" required>
</div>
<div class="col-md-4">
<div class="col-md-3">
<label class="form-label" for="email">Email</label>
<input type="email" name="email" id="email" class="form-control" value="<?= e($values['email']) ?>" required>
</div>
<div class="col-md-3">
<label class="form-label" for="password">Password</label>
<input type="password" name="password" id="password" class="form-control" minlength="6" required>
</div>
<div class="col-md-3">
<label class="form-label" for="phone">Phone</label>
<input type="text" name="phone" id="phone" class="form-control" value="<?= e($values['phone']) ?>" required>
</div>
<div class="col-md-3">
<label class="form-label" for="country_id">Country</label>
<select name="country_id" id="country_id" class="form-select" onchange="syncCities()" required>
<option value="">Select country</option>
<?php foreach ($countries as $country): ?>
<option value="<?= e((string)$country['id']) ?>" <?= $values['country_id'] === (string)$country['id'] ? 'selected' : '' ?>>
<?= e($lang === 'ar' && !empty($country['name_ar']) ? $country['name_ar'] : $country['name_en']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-3">
<label class="form-label" for="city_id">City</label>
<select name="city_id" id="city_id" class="form-select" required data-selected="<?= e($values['city_id']) ?>">
<option value="">Select city</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label" for="address_line">Address</label>
<input type="text" name="address_line" id="address_line" class="form-control" value="<?= e($values['address_line']) ?>" required>
</div>
</div>
<div id="shipperFields" class="mt-4">
<h2 class="h5 mb-3">Shipper details</h2>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label" for="company_name">Company name</label>
<input type="text" name="company_name" id="company_name" class="form-control" value="<?= e($values['company_name']) ?>">
</div>
</div>
</div>
<div id="truckFields" class="mt-4" style="display:none;">
@ -152,28 +300,37 @@ render_header('Register Account');
<div class="row g-3">
<div class="col-md-4">
<label class="form-label" for="truck_type">Truck type</label>
<input type="text" name="truck_type" id="truck_type" class="form-control">
<input type="text" name="truck_type" id="truck_type" class="form-control" value="<?= e($values['truck_type']) ?>">
</div>
<div class="col-md-4">
<label class="form-label" for="load_capacity">Load capacity (tons)</label>
<input type="number" name="load_capacity" id="load_capacity" class="form-control" step="0.01" min="0.1">
<input type="number" name="load_capacity" id="load_capacity" class="form-control" step="0.01" min="0.1" value="<?= e($values['load_capacity']) ?>">
</div>
<div class="col-md-4">
<label class="form-label" for="plate_no">Plate number</label>
<input type="text" name="plate_no" id="plate_no" class="form-control">
<input type="text" name="plate_no" id="plate_no" class="form-control" value="<?= e($values['plate_no']) ?>">
</div>
<div class="col-md-4">
<label class="form-label" for="id_card">ID card (front & back)</label>
<input type="file" name="id_card[]" id="id_card" class="form-control" accept="image/png,image/jpeg,image/webp" multiple>
<div class="col-md-6 mt-3">
<label class="form-label" for="id_card_front">ID card (Front Face)</label>
<input type="file" name="id_card_front" id="id_card_front" class="form-control" accept="image/png,image/jpeg,image/webp">
</div>
<div class="col-md-4">
<label class="form-label" for="truck_picture">Clear truck photo</label>
<div class="col-md-6 mt-3">
<label class="form-label" for="id_card_back">ID card (Back Face)</label>
<input type="file" name="id_card_back" id="id_card_back" class="form-control" accept="image/png,image/jpeg,image/webp">
</div>
<div class="col-md-6 mt-3">
<label class="form-label" for="truck_reg_front">Truck Registration (Front Face)</label>
<input type="file" name="truck_reg_front" id="truck_reg_front" class="form-control" accept="image/png,image/jpeg,image/webp">
</div>
<div class="col-md-6 mt-3">
<label class="form-label" for="truck_reg_back">Truck Registration (Back Face)</label>
<input type="file" name="truck_reg_back" id="truck_reg_back" class="form-control" accept="image/png,image/jpeg,image/webp">
</div>
<div class="col-md-12 mt-3">
<label class="form-label" for="truck_picture">Clear Truck Photo (showing plate number)</label>
<input type="file" name="truck_picture" id="truck_picture" class="form-control" accept="image/png,image/jpeg,image/webp">
</div>
<div class="col-md-4">
<label class="form-label" for="registration">Truck registration (front & back)</label>
<input type="file" name="registration[]" id="registration" class="form-control" accept="image/png,image/jpeg,image/webp" multiple>
</div>
</div>
</div>
@ -185,16 +342,45 @@ render_header('Register Account');
</div>
<script>
const allCities = <?= json_encode($cities, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
function syncCities() {
const countryId = document.getElementById('country_id').value;
const citySelect = document.getElementById('city_id');
const selectedValue = citySelect.dataset.selected || '';
citySelect.innerHTML = '<option value="">Select city</option>';
allCities.forEach((city) => {
if (String(city.country_id) !== String(countryId)) {
return;
}
const option = document.createElement('option');
option.value = city.id;
option.textContent = <?= $lang === 'ar' ? '(city.name_ar || city.name_en)' : '(city.name_en || city.name_ar)' ?>;
if (String(city.id) === String(selectedValue)) {
option.selected = true;
}
citySelect.appendChild(option);
});
citySelect.dataset.selected = '';
}
function toggleFields() {
const role = document.getElementById('role').value;
const truckFields = document.getElementById('truckFields');
const shipperFields = document.getElementById('shipperFields');
const companyInput = document.getElementById('company_name');
const isOwner = role === 'truck_owner';
shipperFields.style.display = isOwner ? 'none' : 'block';
truckFields.style.display = isOwner ? 'block' : 'none';
truckFields.querySelectorAll('input').forEach((input) => {
companyInput.required = !isOwner;
truckFields.querySelectorAll('input[type="text"], input[type="number"]').forEach((input) => {
input.required = isOwner;
});
}
syncCities();
toggleFields();
</script>
<?php render_footer(); ?>
<?php render_footer(); ?>