updating landing page
This commit is contained in:
parent
022932f8fe
commit
d424fc2360
@ -107,14 +107,14 @@ if ($editCityId > 0) {
|
||||
}
|
||||
}
|
||||
|
||||
render_header('Manage Cities', 'admin');
|
||||
render_header('Manage Cities', 'admin', true);
|
||||
?>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-3">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-2 bg-white border-end min-vh-100">
|
||||
<?php render_admin_sidebar('cities'); ?>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<div class="col-md-10 p-4">
|
||||
<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>
|
||||
|
||||
@ -87,14 +87,14 @@ $currentTermsAr = $settings['terms_ar'] ?? '';
|
||||
$currentPrivacyEn = $settings['privacy_en'] ?? '';
|
||||
$currentPrivacyAr = $settings['privacy_ar'] ?? '';
|
||||
|
||||
render_header('Company Profile', 'admin');
|
||||
render_header('Company Profile', 'admin', true);
|
||||
?>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-3">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-2 bg-white border-end min-vh-100">
|
||||
<?php render_admin_sidebar('company_profile'); ?>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<div class="col-md-10 p-4">
|
||||
<div class="page-intro mb-4">
|
||||
<h1 class="section-title mb-1">Company Profile</h1>
|
||||
<p class="muted mb-0">Update your app name, logo, favicon, contact details, platform charge, and legal policies.</p>
|
||||
@ -109,7 +109,7 @@ render_header('Company Profile', 'admin');
|
||||
|
||||
<div class="panel p-4">
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
<div class="row g-4">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-bold">Company / App Name</label>
|
||||
<input type="text" name="company_name" class="form-control" value="<?= e($currentName) ?>" required>
|
||||
|
||||
@ -85,14 +85,14 @@ if ($editCountryId > 0) {
|
||||
}
|
||||
}
|
||||
|
||||
render_header('Manage Countries', 'admin');
|
||||
render_header('Manage Countries', 'admin', true);
|
||||
?>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-3">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-2 bg-white border-end min-vh-100">
|
||||
<?php render_admin_sidebar('countries'); ?>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<div class="col-md-10 p-4">
|
||||
<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>
|
||||
|
||||
@ -33,14 +33,14 @@ try {
|
||||
|
||||
$flash = get_flash();
|
||||
|
||||
render_header(t('admin_dashboard'), 'admin');
|
||||
render_header(t('admin_dashboard'), 'admin', true);
|
||||
?>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-3">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-2 bg-white border-end min-vh-100">
|
||||
<?php render_admin_sidebar('dashboard'); ?>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<div class="col-md-10 p-4">
|
||||
<div class="page-intro mb-4">
|
||||
<h1 class="section-title mb-1"><?= e(t('admin_dashboard')) ?></h1>
|
||||
<p class="muted mb-0">Overview of your platform's performance and recent activity.</p>
|
||||
@ -82,7 +82,7 @@ render_header(t('admin_dashboard'), 'admin');
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="row g-0">
|
||||
<!-- Main Content: Shipments -->
|
||||
<div class="col-lg-8">
|
||||
<div class="panel p-4 shadow-sm border-0 h-100 rounded-4 d-flex flex-column">
|
||||
|
||||
@ -101,14 +101,14 @@ if ($editFaqId > 0) {
|
||||
}
|
||||
}
|
||||
|
||||
render_header('Manage FAQs', 'admin');
|
||||
render_header('Manage FAQs', 'admin', true);
|
||||
?>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-3">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-2 bg-white border-end min-vh-100">
|
||||
<?php render_admin_sidebar('faqs'); ?>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<div class="col-md-10 p-4">
|
||||
<div class="page-intro mb-4">
|
||||
<h1 class="h3 mb-1">Frequently Asked Questions</h1>
|
||||
<p class="text-muted mb-0">Manage the Q&A list displayed on the public FAQ page.</p>
|
||||
|
||||
@ -50,14 +50,14 @@ $smtpPass = $settings['smtp_pass'] ?? '';
|
||||
$mailFrom = $settings['mail_from'] ?? '';
|
||||
$mailFromName = $settings['mail_from_name'] ?? '';
|
||||
|
||||
render_header('Integrations', 'admin');
|
||||
render_header('Integrations', 'admin', true);
|
||||
?>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-3">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-2 bg-white border-end min-vh-100">
|
||||
<?php render_admin_sidebar('integrations'); ?>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<div class="col-md-10 p-4">
|
||||
<div class="page-intro mb-4">
|
||||
<h1 class="section-title mb-1">Integrations</h1>
|
||||
<p class="muted mb-0">Manage your payment gateway and communication APIs.</p>
|
||||
@ -82,7 +82,7 @@ render_header('Integrations', 'admin');
|
||||
</h3>
|
||||
<p class="text-muted small mb-4">Configure your Oman-based Thawani Pay integration to process shipment payments.</p>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-bold">Environment</label>
|
||||
<select name="thawani_environment" class="form-select">
|
||||
@ -114,7 +114,7 @@ render_header('Integrations', 'admin');
|
||||
</h3>
|
||||
<p class="text-muted small mb-4">Connect Wablas to automatically send WhatsApp notifications to Shippers and Truck Owners.</p>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-bold">Wablas Server Domain</label>
|
||||
<input type="text" name="wablas_domain" class="form-control" value="<?= e($wablasDomain) ?>" placeholder="e.g. https://solo.wablas.com">
|
||||
@ -144,7 +144,7 @@ render_header('Integrations', 'admin');
|
||||
</h3>
|
||||
<p class="text-muted small mb-4">Configure your SMTP server to send emails and system notifications.</p>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-bold">SMTP Host</label>
|
||||
<input type="text" name="smtp_host" class="form-control" value="<?= e($smtpHost) ?>" placeholder="smtp.example.com">
|
||||
|
||||
@ -18,10 +18,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if ($action === 'create' || $action === 'edit') {
|
||||
$id = $_POST['id'] ?? null;
|
||||
$title = $_POST['title'] ?? '';
|
||||
$title_ar = $_POST['title_ar'] ?? '';
|
||||
$subtitle = $_POST['subtitle'] ?? '';
|
||||
$subtitle_ar = $_POST['subtitle_ar'] ?? '';
|
||||
$content = $_POST['content'] ?? '';
|
||||
$content_ar = $_POST['content_ar'] ?? '';
|
||||
$layout = $_POST['layout'] ?? 'text_left';
|
||||
$button_text = $_POST['button_text'] ?? '';
|
||||
$button_text_ar = $_POST['button_text_ar'] ?? '';
|
||||
$button_link = $_POST['button_link'] ?? '';
|
||||
$section_order = (int)($_POST['section_order'] ?? 0);
|
||||
$is_active = isset($_POST['is_active']) ? 1 : 0;
|
||||
@ -42,12 +46,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
}
|
||||
|
||||
if ($action === 'create') {
|
||||
$stmt = $pdo->prepare("INSERT INTO landing_sections (title, subtitle, content, image_path, layout, button_text, button_link, section_order, is_active, section_type) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'custom')");
|
||||
$stmt->execute([$title, $subtitle, $content, $image_path, $layout, $button_text, $button_link, $section_order, $is_active]);
|
||||
$stmt = $pdo->prepare("INSERT INTO landing_sections (title, title_ar, subtitle, subtitle_ar, content, content_ar, image_path, layout, button_text, button_text_ar, button_link, section_order, is_active, section_type) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'custom')");
|
||||
$stmt->execute([$title, $title_ar, $subtitle, $subtitle_ar, $content, $content_ar, $image_path, $layout, $button_text, $button_text_ar, $button_link, $section_order, $is_active]);
|
||||
set_flash('success', 'Section created successfully.');
|
||||
} else {
|
||||
$stmt = $pdo->prepare("UPDATE landing_sections SET title=?, subtitle=?, content=?, image_path=?, layout=?, button_text=?, button_link=?, section_order=?, is_active=? WHERE id=?");
|
||||
$stmt->execute([$title, $subtitle, $content, $image_path, $layout, $button_text, $button_link, $section_order, $is_active, $id]);
|
||||
$stmt = $pdo->prepare("UPDATE landing_sections SET title=?, title_ar=?, subtitle=?, subtitle_ar=?, content=?, content_ar=?, image_path=?, layout=?, button_text=?, button_text_ar=?, button_link=?, section_order=?, is_active=? WHERE id=?");
|
||||
$stmt->execute([$title, $title_ar, $subtitle, $subtitle_ar, $content, $content_ar, $image_path, $layout, $button_text, $button_text_ar, $button_link, $section_order, $is_active, $id]);
|
||||
set_flash('success', 'Section updated successfully.');
|
||||
}
|
||||
header('Location: ' . url_with_lang('admin_landing_pages.php'));
|
||||
@ -82,14 +86,14 @@ if ($editId) {
|
||||
$editSection = $stmt->fetch();
|
||||
}
|
||||
|
||||
render_header(t('app_name') . ' - Landing Pages', 'admin');
|
||||
render_header(t('app_name') . ' - Landing Pages', 'admin', true);
|
||||
?>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-3">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-2 bg-white border-end min-vh-100">
|
||||
<?php render_admin_sidebar('landing_pages'); ?>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<div class="col-md-10 p-4">
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2 class="fw-bold mb-0">Landing Page Customization</h2>
|
||||
@ -103,7 +107,7 @@ render_header(t('app_name') . ' - Landing Pages', 'admin');
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-5">
|
||||
<div class="panel p-4 shadow-sm border-0 rounded-4 bg-white">
|
||||
<h4 class="mb-4"><?= $editSection ? 'Edit Section' : 'Add New Section' ?></h4>
|
||||
@ -115,21 +119,36 @@ render_header(t('app_name') . ' - Landing Pages', 'admin');
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Title <span class="text-danger">*</span></label>
|
||||
<label class="form-label">Title (English) <span class="text-danger">*</span></label>
|
||||
<input type="text" name="title" class="form-control" value="<?= e($editSection['title'] ?? '') ?>" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Title (Arabic)</label>
|
||||
<input type="text" name="title_ar" class="form-control" dir="rtl" value="<?= e($editSection['title_ar'] ?? '') ?>">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Subtitle (Optional)</label>
|
||||
<label class="form-label">Subtitle (English) (Optional)</label>
|
||||
<textarea name="subtitle" class="form-control" rows="2"><?= e($editSection['subtitle'] ?? '') ?></textarea>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Content (HTML allowed)</label>
|
||||
<label class="form-label">Subtitle (Arabic) (Optional)</label>
|
||||
<textarea name="subtitle_ar" class="form-control" rows="2" dir="rtl"><?= e($editSection['subtitle_ar'] ?? '') ?></textarea>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Content (English) (HTML allowed)</label>
|
||||
<textarea name="content" class="form-control" rows="5"><?= e($editSection['content'] ?? '') ?></textarea>
|
||||
<small class="text-muted">Not applicable for most built-in sections.</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Content (Arabic) (HTML allowed)</label>
|
||||
<textarea name="content_ar" class="form-control" rows="5" dir="rtl"><?= e($editSection['content_ar'] ?? '') ?></textarea>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Layout Type</label>
|
||||
@ -147,14 +166,19 @@ render_header(t('app_name') . ' - Landing Pages', 'admin');
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Button Text</label>
|
||||
<label class="form-label">Button Text (English)</label>
|
||||
<input type="text" name="button_text" class="form-control" value="<?= e($editSection['button_text'] ?? '') ?>">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Button Link (e.g. login.php)</label>
|
||||
<input type="text" name="button_link" class="form-control" value="<?= e($editSection['button_link'] ?? '') ?>">
|
||||
<label class="form-label">Button Text (Arabic)</label>
|
||||
<input type="text" name="button_text_ar" class="form-control" dir="rtl" value="<?= e($editSection['button_text_ar'] ?? '') ?>">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Button Link (e.g. login.php)</label>
|
||||
<input type="text" name="button_link" class="form-control" value="<?= e($editSection['button_link'] ?? '') ?>">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Upload Picture</label>
|
||||
@ -193,6 +217,9 @@ render_header(t('app_name') . ' - Landing Pages', 'admin');
|
||||
<div>
|
||||
<h6 class="mb-1 fw-bold"><?= e($sec['title']) ?> <span class="badge bg-<?= $sec['is_active'] ? 'success' : 'secondary' ?> ms-2"><?= $sec['is_active'] ? 'Active' : 'Draft' ?></span></h6>
|
||||
<small class="text-muted">Order: <?= e($sec['section_order']) ?> | Type: <?= e(ucfirst($sec['section_type'])) ?> <?= $sec['section_type']==='custom' ? '| Layout: '.e($sec['layout']) : '' ?></small>
|
||||
<?php if (!empty($sec['title_ar'])): ?>
|
||||
<div class="mt-1 small text-muted">AR: <?= e($sec['title_ar']) ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<a href="<?= e(url_with_lang('admin_landing_pages.php', ['edit' => $sec['id']])) ?>" class="btn btn-sm btn-outline-primary">Edit</a>
|
||||
|
||||
201
admin_notification_templates.php
Normal file
201
admin_notification_templates.php
Normal file
@ -0,0 +1,201 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/includes/layout.php';
|
||||
|
||||
ensure_schema();
|
||||
|
||||
// Access Control
|
||||
if (($_SESSION['user_role'] ?? '') !== 'admin') {
|
||||
header('Location: ' . url_with_lang('login.php'));
|
||||
exit;
|
||||
}
|
||||
|
||||
// Ensure table exists (idempotent)
|
||||
try {
|
||||
db()->exec("
|
||||
CREATE TABLE IF NOT EXISTS notification_templates (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
event_name VARCHAR(50) NOT NULL UNIQUE,
|
||||
email_subject_en VARCHAR(255),
|
||||
email_body_en TEXT,
|
||||
email_subject_ar VARCHAR(255),
|
||||
email_body_ar TEXT,
|
||||
whatsapp_body_en TEXT,
|
||||
whatsapp_body_ar TEXT,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
||||
");
|
||||
} catch (Throwable $e) {
|
||||
// Ignore if table exists or permission issue, subsequent queries will fail if critical
|
||||
}
|
||||
|
||||
$action = $_GET['action'] ?? 'list';
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
$errors = [];
|
||||
$flash = get_flash();
|
||||
|
||||
if ($action === 'edit' && $id > 0) {
|
||||
// Handle Edit
|
||||
$stmt = db()->prepare("SELECT * FROM notification_templates WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$template = $stmt->fetch();
|
||||
|
||||
if (!$template) {
|
||||
set_flash('error', 'Template not found.');
|
||||
header('Location: ' . url_with_lang('admin_notification_templates.php'));
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$email_subject_en = trim($_POST['email_subject_en'] ?? '');
|
||||
$email_body_en = trim($_POST['email_body_en'] ?? '');
|
||||
$email_subject_ar = trim($_POST['email_subject_ar'] ?? '');
|
||||
$email_body_ar = trim($_POST['email_body_ar'] ?? '');
|
||||
$whatsapp_body_en = trim($_POST['whatsapp_body_en'] ?? '');
|
||||
$whatsapp_body_ar = trim($_POST['whatsapp_body_ar'] ?? '');
|
||||
|
||||
if ($email_subject_en === '' || $email_body_en === '') {
|
||||
$errors[] = 'English subject and body are required.';
|
||||
}
|
||||
|
||||
if (!$errors) {
|
||||
$stmt = db()->prepare("
|
||||
UPDATE notification_templates SET
|
||||
email_subject_en = ?, email_body_en = ?,
|
||||
email_subject_ar = ?, email_body_ar = ?,
|
||||
whatsapp_body_en = ?, whatsapp_body_ar = ?
|
||||
WHERE id = ?
|
||||
");
|
||||
$stmt->execute([
|
||||
$email_subject_en, $email_body_en,
|
||||
$email_subject_ar, $email_body_ar,
|
||||
$whatsapp_body_en, $whatsapp_body_ar,
|
||||
$id
|
||||
]);
|
||||
set_flash('success', 'Template updated successfully.');
|
||||
header('Location: ' . url_with_lang('admin_notification_templates.php'));
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
render_header('Edit Notification Template', 'admin', true);
|
||||
?>
|
||||
<div class="row g-0">
|
||||
<div class="col-md-2 bg-white border-end min-vh-100">
|
||||
<?= render_admin_sidebar('notification_templates') ?>
|
||||
</div>
|
||||
<div class="col-md-10">
|
||||
<div class="p-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2 class="h4 mb-0">Edit Template: <?= e($template['event_name']) ?></h2>
|
||||
<a href="<?= e(url_with_lang('admin_notification_templates.php')) ?>" class="btn btn-outline-secondary">Back to List</a>
|
||||
</div>
|
||||
|
||||
<?php if ($errors): ?>
|
||||
<div class="alert alert-danger"><?= e(implode('<br>', $errors)) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body">
|
||||
<form method="post">
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6">
|
||||
<h5 class="mb-3 border-bottom pb-2">English</h5>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Email Subject</label>
|
||||
<input type="text" class="form-control" name="email_subject_en" value="<?= e($template['email_subject_en']) ?>" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Email Body</label>
|
||||
<textarea class="form-control" name="email_body_en" rows="6" required><?= e($template['email_body_en']) ?></textarea>
|
||||
<div class="form-text text-muted">Use placeholders like {shipment_id}, {user_name}, {offer_price}.</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">WhatsApp Body</label>
|
||||
<textarea class="form-control" name="whatsapp_body_en" rows="4"><?= e($template['whatsapp_body_en']) ?></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h5 class="mb-3 border-bottom pb-2">Arabic</h5>
|
||||
<div class="mb-3" dir="rtl">
|
||||
<label class="form-label">موضوع البريد الإلكتروني</label>
|
||||
<input type="text" class="form-control" name="email_subject_ar" value="<?= e($template['email_subject_ar']) ?>">
|
||||
</div>
|
||||
<div class="mb-3" dir="rtl">
|
||||
<label class="form-label">نص البريد الإلكتروني</label>
|
||||
<textarea class="form-control" name="email_body_ar" rows="6"><?= e($template['email_body_ar']) ?></textarea>
|
||||
</div>
|
||||
<div class="mb-3" dir="rtl">
|
||||
<label class="form-label">نص الواتساب</label>
|
||||
<textarea class="form-control" name="whatsapp_body_ar" rows="4"><?= e($template['whatsapp_body_ar']) ?></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="text-end">
|
||||
<button type="submit" class="btn btn-primary px-4">Save Changes</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
render_footer();
|
||||
exit;
|
||||
}
|
||||
|
||||
// List View
|
||||
$stmt = db()->query("SELECT * FROM notification_templates ORDER BY event_name ASC");
|
||||
$templates = $stmt->fetchAll();
|
||||
|
||||
render_header('Notification Templates', 'admin', true);
|
||||
?>
|
||||
<div class="row g-0">
|
||||
<div class="col-md-2 bg-white border-end min-vh-100">
|
||||
<?= render_admin_sidebar('notification_templates') ?>
|
||||
</div>
|
||||
<div class="col-md-10">
|
||||
<div class="p-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2 class="h4 mb-0">Notification Templates</h2>
|
||||
</div>
|
||||
|
||||
<?php if ($flash): ?>
|
||||
<div class="alert alert-<?= $flash['type'] === 'error' ? 'danger' : 'success' ?>"><?= e($flash['message']) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th class="ps-4">Event Name</th>
|
||||
<th>Subject (EN)</th>
|
||||
<th>Subject (AR)</th>
|
||||
<th class="text-end pe-4">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($templates as $t): ?>
|
||||
<tr>
|
||||
<td class="ps-4 fw-medium"><?= e($t['event_name']) ?></td>
|
||||
<td><?= e($t['email_subject_en']) ?></td>
|
||||
<td><?= e($t['email_subject_ar']) ?></td>
|
||||
<td class="text-end pe-4">
|
||||
<a href="<?= e(url_with_lang('admin_notification_templates.php', ['action' => 'edit', 'id' => $t['id']])) ?>" class="btn btn-sm btn-outline-primary">
|
||||
Edit
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php render_footer(); ?>
|
||||
@ -109,14 +109,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
}
|
||||
}
|
||||
|
||||
render_header('Edit Shipment', 'admin');
|
||||
render_header('Edit Shipment', 'admin', true);
|
||||
?>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-3">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-2 bg-white border-end min-vh-100">
|
||||
<?php render_admin_sidebar('shipments'); ?>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<div class="col-md-10 p-4">
|
||||
<div class="d-flex align-items-center gap-3 mb-4">
|
||||
<a href="admin_shipments.php" class="btn btn-light border text-secondary" title="Back">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-left" viewBox="0 0 16 16">
|
||||
|
||||
@ -66,14 +66,14 @@ $stmt = db()->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$shipments = $stmt->fetchAll();
|
||||
|
||||
render_header('Manage Shipments', 'admin');
|
||||
render_header('Manage Shipments', 'admin', true);
|
||||
?>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-3">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-2 bg-white border-end min-vh-100">
|
||||
<?php render_admin_sidebar('shipments'); ?>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<div class="col-md-10 p-4">
|
||||
<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">Shipments</h1>
|
||||
|
||||
@ -101,14 +101,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
}
|
||||
}
|
||||
|
||||
render_header('Edit Shipper', 'admin');
|
||||
render_header('Edit Shipper', 'admin', true);
|
||||
?>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-3">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-2 bg-white border-end min-vh-100">
|
||||
<?php render_admin_sidebar('shippers'); ?>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<div class="col-md-10 p-4">
|
||||
<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">← Back to Shippers</a>
|
||||
|
||||
@ -75,14 +75,14 @@ $stmt = db()->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$shippers = $stmt->fetchAll();
|
||||
|
||||
render_header('Manage Shippers', 'admin');
|
||||
render_header('Manage Shippers', 'admin', true);
|
||||
?>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-3">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-2 bg-white border-end min-vh-100">
|
||||
<?php render_admin_sidebar('shippers'); ?>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<div class="col-md-10 p-4">
|
||||
<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>
|
||||
|
||||
@ -126,14 +126,14 @@ $idCards = json_decode($owner['id_card_path'] ?? '[]', true) ?: [];
|
||||
$regs = json_decode($owner['registration_path'] ?? '[]', true) ?: [];
|
||||
$pic = $owner['truck_pic_path'];
|
||||
|
||||
render_header('Edit Truck Owner', 'admin');
|
||||
render_header('Edit Truck Owner', 'admin', true);
|
||||
?>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-3">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-2 bg-white border-end min-vh-100">
|
||||
<?php render_admin_sidebar('truck_owners'); ?>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<div class="col-md-10 p-4">
|
||||
<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">← Back to Truck Owners</a>
|
||||
|
||||
@ -75,14 +75,14 @@ $stmt = db()->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$owners = $stmt->fetchAll();
|
||||
|
||||
render_header('Manage Truck Owners', 'admin');
|
||||
render_header('Manage Truck Owners', 'admin', true);
|
||||
?>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-3">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-2 bg-white border-end min-vh-100">
|
||||
<?php render_admin_sidebar('truck_owners'); ?>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<div class="col-md-10 p-4">
|
||||
<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>
|
||||
|
||||
@ -238,8 +238,10 @@ body.app-body {
|
||||
|
||||
.admin-sidebar {
|
||||
position: sticky;
|
||||
top: 88px;
|
||||
border-radius: 16px;
|
||||
top: 76px;
|
||||
height: calc(100vh - 76px);
|
||||
overflow-y: auto;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.admin-nav-link {
|
||||
@ -289,5 +291,7 @@ body.app-body {
|
||||
|
||||
.admin-sidebar {
|
||||
position: static;
|
||||
height: auto;
|
||||
overflow-y: visible;
|
||||
}
|
||||
}
|
||||
24
db/migrations/add_arabic_landing_fields.php
Normal file
24
db/migrations/add_arabic_landing_fields.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
$pdo = db();
|
||||
|
||||
try {
|
||||
$pdo->exec("ALTER TABLE landing_sections ADD COLUMN title_ar VARCHAR(255) NULL AFTER title");
|
||||
echo "Added title_ar.\n";
|
||||
} catch (PDOException $e) { echo "title_ar likely exists.\n"; }
|
||||
|
||||
try {
|
||||
$pdo->exec("ALTER TABLE landing_sections ADD COLUMN subtitle_ar TEXT NULL AFTER subtitle");
|
||||
echo "Added subtitle_ar.\n";
|
||||
} catch (PDOException $e) { echo "subtitle_ar likely exists.\n"; }
|
||||
|
||||
try {
|
||||
$pdo->exec("ALTER TABLE landing_sections ADD COLUMN content_ar TEXT NULL AFTER content");
|
||||
echo "Added content_ar.\n";
|
||||
} catch (PDOException $e) { echo "content_ar likely exists.\n"; }
|
||||
|
||||
try {
|
||||
$pdo->exec("ALTER TABLE landing_sections ADD COLUMN button_text_ar VARCHAR(100) NULL AFTER button_text");
|
||||
echo "Added button_text_ar.\n";
|
||||
} catch (PDOException $e) { echo "button_text_ar likely exists.\n"; }
|
||||
|
||||
70
db/migrations/add_notification_templates.php
Normal file
70
db/migrations/add_notification_templates.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../config.php';
|
||||
$pdo = db();
|
||||
|
||||
try {
|
||||
// Create the table
|
||||
$pdo->exec("
|
||||
CREATE TABLE IF NOT EXISTS notification_templates (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
event_name VARCHAR(50) NOT NULL UNIQUE,
|
||||
email_subject_en VARCHAR(255),
|
||||
email_body_en TEXT,
|
||||
email_subject_ar VARCHAR(255),
|
||||
email_body_ar TEXT,
|
||||
whatsapp_body_en TEXT,
|
||||
whatsapp_body_ar TEXT,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
||||
");
|
||||
|
||||
// Insert default templates
|
||||
$defaults = [
|
||||
[
|
||||
'event_name' => 'shipment_created',
|
||||
'email_subject_en' => 'Shipment Created: #{shipment_id}',
|
||||
'email_body_en' => "Dear {user_name},\n\nYour shipment from {origin} to {destination} has been successfully created.\n\nShipment ID: {shipment_id}\n\nWe will notify you when a truck owner submits an offer.",
|
||||
'email_subject_ar' => 'تم إنشاء الشحنة: #{shipment_id}',
|
||||
'email_body_ar' => "عزيزي {user_name}،\n\nتم إنشاء شحنتك من {origin} إلى {destination} بنجاح.\n\nرقم الشحنة: {shipment_id}\n\nسنقوم بإعلامك عند تقديم عرض من قبل صاحب شاحنة.",
|
||||
'whatsapp_body_en' => "Shipment #{shipment_id} created successfully from {origin} to {destination}.",
|
||||
'whatsapp_body_ar' => "تم إنشاء الشحنة #{shipment_id} بنجاح من {origin} إلى {destination}."
|
||||
],
|
||||
[
|
||||
'event_name' => 'shipment_offered',
|
||||
'email_subject_en' => 'New Offer for Shipment #{shipment_id}',
|
||||
'email_body_en' => "Dear {user_name},\n\nA truck owner has submitted an offer for your shipment #{shipment_id}.\n\nOffer Price: {offer_price}\nTruck Owner: {offer_owner}\n\nPlease login to your dashboard to accept or reject this offer.",
|
||||
'email_subject_ar' => 'عرض جديد للشحنة #{shipment_id}',
|
||||
'email_body_ar' => "عزيزي {user_name}،\n\nلقد قدم صاحب شاحنة عرضًا لشحنتك #{shipment_id}.\n\nسعر العرض: {offer_price}\nصاحب الشاحنة: {offer_owner}\n\nيرجى تسجيل الدخول إلى لوحة التحكم لقبول أو رفض العرض.",
|
||||
'whatsapp_body_en' => "New offer for shipment #{shipment_id}: {offer_price}. Check your dashboard.",
|
||||
'whatsapp_body_ar' => "عرض جديد للشحنة #{shipment_id}: {offer_price}. تحقق من لوحة التحكم."
|
||||
],
|
||||
[
|
||||
'event_name' => 'shipment_accepted',
|
||||
'email_subject_en' => 'Offer Accepted: Shipment #{shipment_id}',
|
||||
'email_body_en' => "Dear {user_name},\n\nYour offer for shipment #{shipment_id} has been accepted and paid for by the shipper.\n\nPlease proceed with the shipment.",
|
||||
'email_subject_ar' => 'تم قبول العرض: الشحنة #{shipment_id}',
|
||||
'email_body_ar' => "عزيزي {user_name}،\n\nتم قبول عرضك للشحنة #{shipment_id} ودفع ثمنه من قبل الشاحن.\n\nيرجى المتابعة في إجراءات الشحن.",
|
||||
'whatsapp_body_en' => "Your offer for shipment #{shipment_id} was accepted. Please proceed.",
|
||||
'whatsapp_body_ar' => "تم قبول عرضك للشحنة #{shipment_id}. يرجى المتابعة."
|
||||
],
|
||||
[
|
||||
'event_name' => 'shipment_rejected',
|
||||
'email_subject_en' => 'Offer Rejected: Shipment #{shipment_id}',
|
||||
'email_body_en' => "Dear {user_name},\n\nYour offer for shipment #{shipment_id} was not accepted by the shipper.\n\nThe shipment is now back to 'Posted' status.",
|
||||
'email_subject_ar' => 'تم رفض العرض: الشحنة #{shipment_id}',
|
||||
'email_body_ar' => "عزيزي {user_name}،\n\nلم يتم قبول عرضك للشحنة #{shipment_id} من قبل الشاحن.\n\nعادت الشحنة الآن إلى حالة 'منشورة'.",
|
||||
'whatsapp_body_en' => "Your offer for shipment #{shipment_id} was rejected.",
|
||||
'whatsapp_body_ar' => "تم رفض عرضك للشحنة #{shipment_id}."
|
||||
]
|
||||
];
|
||||
|
||||
$stmt = $pdo->prepare("INSERT IGNORE INTO notification_templates (event_name, email_subject_en, email_body_en, email_subject_ar, email_body_ar, whatsapp_body_en, whatsapp_body_ar) VALUES (:event_name, :email_subject_en, :email_body_en, :email_subject_ar, :email_body_ar, :whatsapp_body_en, :whatsapp_body_ar)");
|
||||
|
||||
foreach ($defaults as $d) {
|
||||
$stmt->execute($d);
|
||||
}
|
||||
|
||||
echo "Notification templates table created and defaults inserted.";
|
||||
} catch (PDOException $e) {
|
||||
echo "Error: " . $e->getMessage();
|
||||
}
|
||||
15
db/migrations/add_user_ids_to_shipments.php
Normal file
15
db/migrations/add_user_ids_to_shipments.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../config.php';
|
||||
$pdo = db();
|
||||
|
||||
try {
|
||||
$pdo->exec("ALTER TABLE shipments ADD COLUMN shipper_id INT NULL AFTER id");
|
||||
$pdo->exec("ALTER TABLE shipments ADD COLUMN truck_owner_id INT NULL AFTER offer_price");
|
||||
|
||||
// Add FK constraints (optional but good practice, keeping it simple for now to avoid issues with existing data)
|
||||
// We won't enforce FK strictly on existing data as it might be NULL
|
||||
|
||||
echo "Added shipper_id and truck_owner_id to shipments table.";
|
||||
} catch (PDOException $e) {
|
||||
echo "Error (might already exist): " . $e->getMessage();
|
||||
}
|
||||
73
includes/NotificationService.php
Normal file
73
includes/NotificationService.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
require_once __DIR__ . '/../mail/MailService.php';
|
||||
|
||||
class NotificationService
|
||||
{
|
||||
/**
|
||||
* Send a notification for a specific event.
|
||||
*
|
||||
* @param string $eventName The event name (e.g., 'shipment_created')
|
||||
* @param array $user The recipient user array (must contain 'email' and 'full_name')
|
||||
* @param array $data Data to replace in placeholders (e.g., ['shipment_id' => 123])
|
||||
* @param string|null $lang 'en' or 'ar'. If null, sends both combined.
|
||||
*/
|
||||
public static function send(string $eventName, array $user, array $data = [], ?string $lang = null)
|
||||
{
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT * FROM notification_templates WHERE event_name = ?");
|
||||
$stmt->execute([$eventName]);
|
||||
$template = $stmt->fetch();
|
||||
|
||||
if (!$template) {
|
||||
error_log("Notification template not found: $eventName");
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare data for replacement
|
||||
$placeholders = [];
|
||||
$values = [];
|
||||
$data['user_name'] = $user['full_name'] ?? 'User';
|
||||
|
||||
foreach ($data as $key => $val) {
|
||||
$placeholders[] = '{' . $key . '}';
|
||||
$values[] = $val;
|
||||
}
|
||||
|
||||
// Determine Subject and Body based on Lang
|
||||
$subject = '';
|
||||
$body = '';
|
||||
$whatsapp = '';
|
||||
|
||||
if ($lang === 'en') {
|
||||
$subject = $template['email_subject_en'];
|
||||
$body = $template['email_body_en'];
|
||||
$whatsapp = $template['whatsapp_body_en'];
|
||||
} elseif ($lang === 'ar') {
|
||||
$subject = $template['email_subject_ar'];
|
||||
$body = $template['email_body_ar'];
|
||||
$whatsapp = $template['whatsapp_body_ar'];
|
||||
} else {
|
||||
// Combined
|
||||
$subject = $template['email_subject_en'] . ' / ' . $template['email_subject_ar'];
|
||||
$body = $template['email_body_en'] . "\n\n---\n\n" . $template['email_body_ar'];
|
||||
$whatsapp = $template['whatsapp_body_en'] . "\n\n" . $template['whatsapp_body_ar'];
|
||||
}
|
||||
|
||||
// Replace
|
||||
$subject = str_replace($placeholders, $values, $subject);
|
||||
$body = str_replace($placeholders, $values, $body);
|
||||
$whatsapp = str_replace($placeholders, $values, $whatsapp);
|
||||
|
||||
// Send Email
|
||||
if (!empty($user['email'])) {
|
||||
// Convert newlines to BR for HTML
|
||||
$htmlBody = nl2br(htmlspecialchars($body));
|
||||
MailService::sendMail($user['email'], $subject, $htmlBody, $body);
|
||||
}
|
||||
|
||||
// Log WhatsApp (Mock)
|
||||
// In a real app, this would call Twilio/Meta API
|
||||
error_log("WHATSAPP Notification to {$user['email']} (Phone N/A): $whatsapp");
|
||||
}
|
||||
}
|
||||
510
includes/app.php
510
includes/app.php
@ -13,142 +13,366 @@ $_SESSION['lang'] = $lang;
|
||||
$dir = $lang === 'ar' ? 'rtl' : 'ltr';
|
||||
|
||||
$translations = [
|
||||
'en' => [
|
||||
'app_name' => 'CargoLink',
|
||||
'nav_home' => 'Overview',
|
||||
'nav_shipper' => 'Shipper Desk',
|
||||
'nav_owner' => 'Truck Owner Desk',
|
||||
'nav_admin' => 'Admin Panel',
|
||||
'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',
|
||||
'stats_shipments' => 'Shipments posted',
|
||||
'stats_offers' => 'Active offers',
|
||||
'stats_confirmed' => 'Confirmed trips',
|
||||
'section_workflow' => 'How it works',
|
||||
'recent_shipments' => 'Recent shipments',
|
||||
'step_post' => 'Shipper posts cargo details and preferred payment.',
|
||||
'step_offer' => 'Truck owners respond with their best rate.',
|
||||
'step_confirm' => 'Admin confirms booking and status.',
|
||||
'shipper_dashboard' => 'Shipper Dashboard',
|
||||
'new_shipment' => 'Create shipment',
|
||||
'shipper_name' => 'Shipper name',
|
||||
'shipper_company' => 'Company',
|
||||
'origin' => 'Origin city',
|
||||
'destination' => 'Destination city',
|
||||
'cargo' => 'Cargo description',
|
||||
'weight' => 'Weight (tons)',
|
||||
'pickup_date' => 'Pickup date',
|
||||
'delivery_date' => 'Delivery date',
|
||||
'payment_method' => 'Payment method',
|
||||
'payment_thawani' => 'Thawani online payment',
|
||||
'payment_bank' => 'Bank transfer',
|
||||
'submit_shipment' => 'Submit shipment',
|
||||
'shipments_list' => 'Your latest shipments',
|
||||
'status' => 'Status',
|
||||
'offer' => 'Best offer',
|
||||
'actions' => 'Actions',
|
||||
'view' => 'View',
|
||||
'owner_dashboard' => 'Truck Owner Dashboard',
|
||||
'available_shipments' => 'Available shipments',
|
||||
'offer_price' => 'Offer price',
|
||||
'offer_owner' => 'Truck owner name',
|
||||
'submit_offer' => 'Send offer',
|
||||
'admin_dashboard' => 'Admin Dashboard',
|
||||
'update_status' => 'Update status',
|
||||
'save' => 'Save',
|
||||
'shipment_detail' => 'Shipment detail',
|
||||
'created_at' => 'Created',
|
||||
'best_offer' => 'Best offer',
|
||||
'assign_owner' => 'Assigned owner',
|
||||
'no_shipments' => 'No shipments yet. Create the first one to get started.',
|
||||
'no_offers' => 'No offers yet.',
|
||||
'success_shipment' => 'Shipment posted successfully.',
|
||||
'success_offer' => 'Offer submitted to the shipper.',
|
||||
'success_status' => 'Status updated.',
|
||||
'error_required' => 'Please fill in all required fields.',
|
||||
'error_invalid' => 'Please enter valid values.',
|
||||
'status_posted' => 'Posted',
|
||||
'status_offered' => 'Offered',
|
||||
'status_confirmed' => 'Confirmed',
|
||||
'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' => 'لماذا تختار كارجو لينك؟', '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',
|
||||
'nav_home' => 'نظرة عامة',
|
||||
'nav_shipper' => 'لوحة الشاحن',
|
||||
'nav_owner' => 'لوحة مالك الشاحنة',
|
||||
'nav_admin' => 'لوحة الإدارة',
|
||||
'hero_title' => 'انقل شحنتك بسرعة مع شاحنات موثوقة.',
|
||||
'hero_subtitle' => 'أنشئ شحنة، استلم عروضاً، وادفع عبر ثواني أو التحويل البنكي.',
|
||||
'hero_tagline' => 'منصة لوجستية متعددة اللغات',
|
||||
'register_shipper' => 'التسجيل كشاحن',
|
||||
'register_owner' => 'التسجيل كمالك شاحنة',
|
||||
'cta_shipper' => 'إنشاء شحنة',
|
||||
'cta_owner' => 'البحث عن الشحنات',
|
||||
'cta_admin' => 'الدخول للإدارة',
|
||||
'stats_shipments' => 'الشحنات المنشورة',
|
||||
'stats_offers' => 'العروض الحالية',
|
||||
'stats_confirmed' => 'الرحلات المؤكدة',
|
||||
'section_workflow' => 'طريقة العمل',
|
||||
'recent_shipments' => 'أحدث الشحنات',
|
||||
'step_post' => 'يقوم الشاحن بإدخال تفاصيل الشحنة وطريقة الدفع.',
|
||||
'step_offer' => 'يرسل أصحاب الشاحنات أفضل عروضهم.',
|
||||
'step_confirm' => 'تؤكد الإدارة الحجز وتحدث الحالة.',
|
||||
'shipper_dashboard' => 'لوحة الشاحن',
|
||||
'new_shipment' => 'إنشاء شحنة',
|
||||
'shipper_name' => 'اسم الشاحن',
|
||||
'shipper_company' => 'الشركة',
|
||||
'origin' => 'مدينة الانطلاق',
|
||||
'destination' => 'مدينة الوصول',
|
||||
'cargo' => 'وصف الحمولة',
|
||||
'weight' => 'الوزن (طن)',
|
||||
'pickup_date' => 'تاريخ الاستلام',
|
||||
'delivery_date' => 'تاريخ التسليم',
|
||||
'payment_method' => 'طريقة الدفع',
|
||||
'payment_thawani' => 'الدفع الإلكتروني عبر ثواني',
|
||||
'payment_bank' => 'تحويل بنكي',
|
||||
'submit_shipment' => 'إرسال الشحنة',
|
||||
'shipments_list' => 'أحدث الشحنات',
|
||||
'status' => 'الحالة',
|
||||
'offer' => 'أفضل عرض',
|
||||
'actions' => 'إجراءات',
|
||||
'view' => 'عرض',
|
||||
'owner_dashboard' => 'لوحة مالك الشاحنة',
|
||||
'available_shipments' => 'الشحنات المتاحة',
|
||||
'offer_price' => 'سعر العرض',
|
||||
'offer_owner' => 'اسم مالك الشاحنة',
|
||||
'submit_offer' => 'إرسال العرض',
|
||||
'admin_dashboard' => 'لوحة الإدارة',
|
||||
'update_status' => 'تحديث الحالة',
|
||||
'save' => 'حفظ',
|
||||
'shipment_detail' => 'تفاصيل الشحنة',
|
||||
'created_at' => 'تم الإنشاء',
|
||||
'best_offer' => 'أفضل عرض',
|
||||
'assign_owner' => 'المالك المعتمد',
|
||||
'no_shipments' => 'لا توجد شحنات بعد. ابدأ بإنشاء أول شحنة.',
|
||||
'no_offers' => 'لا توجد عروض بعد.',
|
||||
'success_shipment' => 'تم نشر الشحنة بنجاح.',
|
||||
'success_offer' => 'تم إرسال العرض إلى الشاحن.',
|
||||
'success_status' => 'تم تحديث الحالة.',
|
||||
'error_required' => 'يرجى تعبئة جميع الحقول المطلوبة.',
|
||||
'error_invalid' => 'يرجى إدخال قيم صحيحة.',
|
||||
'status_posted' => 'منشورة',
|
||||
'status_offered' => 'بعرض',
|
||||
'status_confirmed' => 'مؤكدة',
|
||||
'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' => 'نقوم بالتحقق من جميع أصحاب الشاحنات لضمان راحتك.',
|
||||
],
|
||||
"en" => array (
|
||||
'app_name' => 'CargoLink',
|
||||
'nav_home' => 'Overview',
|
||||
'nav_shipper' => 'Shipper Desk',
|
||||
'nav_owner' => 'Truck Owner Desk',
|
||||
'nav_admin' => 'Admin Panel',
|
||||
'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',
|
||||
'stats_shipments' => 'Shipments posted',
|
||||
'stats_offers' => 'Active offers',
|
||||
'stats_confirmed' => 'Confirmed trips',
|
||||
'section_workflow' => 'How it works',
|
||||
'recent_shipments' => 'Recent shipments',
|
||||
'step_post' => 'Shipper posts cargo details and preferred payment.',
|
||||
'step_offer' => 'Truck owners respond with their best rate.',
|
||||
'step_confirm' => 'Admin confirms booking and status.',
|
||||
'step_confirm_desc' => 'Secure booking and track the delivery until completion.',
|
||||
'shipper_dashboard' => 'Shipper Dashboard',
|
||||
'new_shipment' => 'Create shipment',
|
||||
'shipper_name' => 'Shipper name',
|
||||
'shipper_company' => 'Company',
|
||||
'origin' => 'Origin city',
|
||||
'destination' => 'Destination city',
|
||||
'cargo' => 'Cargo description',
|
||||
'cargo_placeholder' => 'e.g. 20 Pallets of Electronics',
|
||||
'weight' => 'Weight (tons)',
|
||||
'pickup_date' => 'Pickup date',
|
||||
'delivery_date' => 'Delivery date',
|
||||
'payment_method' => 'Payment method',
|
||||
'payment_thawani' => 'Thawani online payment',
|
||||
'payment_bank' => 'Bank transfer',
|
||||
'submit_shipment' => 'Submit shipment',
|
||||
'shipments_list' => 'Your latest shipments',
|
||||
'status' => 'Status',
|
||||
'offer' => 'Best offer',
|
||||
'actions' => 'Actions',
|
||||
'view' => 'View',
|
||||
'owner_dashboard' => 'Truck Owner Dashboard',
|
||||
'available_shipments' => 'Available shipments',
|
||||
'offer_price' => 'Offer price',
|
||||
'offer_owner' => 'Truck owner name',
|
||||
'submit_offer' => 'Send offer',
|
||||
'admin_dashboard' => 'Admin Dashboard',
|
||||
'update_status' => 'Update status',
|
||||
'save' => 'Save',
|
||||
'shipment_detail' => 'Shipment detail',
|
||||
'created_at' => 'Created',
|
||||
'best_offer' => 'Best offer',
|
||||
'assign_owner' => 'Assigned owner',
|
||||
'no_shipments' => 'No shipments yet. Create the first one to get started.',
|
||||
'no_offers' => 'No offers yet.',
|
||||
'success_shipment' => 'Shipment posted successfully.',
|
||||
'success_offer' => 'Offer submitted to the shipper.',
|
||||
'success_status' => 'Status updated.',
|
||||
'error_required' => 'Please fill in all required fields.',
|
||||
'error_invalid' => 'Please enter valid values.',
|
||||
'status_posted' => 'Posted',
|
||||
'status_offered' => 'Offered',
|
||||
'status_confirmed' => 'Confirmed',
|
||||
'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.',
|
||||
'view_faq' => 'View FAQ',
|
||||
'faq_title' => 'Have Questions?',
|
||||
'faq_subtitle' => 'Check out our Frequently Asked Questions to learn more about how our platform works.',
|
||||
'motivation_title' => 'Ready to transform your logistics?',
|
||||
'motivation_subtitle' => 'Join our platform today to find reliable trucks or secure the best shipments in the market.',
|
||||
'company' => 'Company',
|
||||
'about_us' => 'About Us',
|
||||
'careers' => 'Careers',
|
||||
'contact' => 'Contact',
|
||||
'resources' => 'Resources',
|
||||
'help_center' => 'Help Center / FAQ',
|
||||
'terms_of_service' => 'Terms of Service',
|
||||
'privacy_policy' => 'Privacy Policy',
|
||||
'language' => 'Language',
|
||||
'all_rights_reserved' => 'All rights reserved.',
|
||||
'dashboard' => 'Dashboard',
|
||||
'settings' => 'Settings',
|
||||
'company_setting' => 'Company Setting',
|
||||
'integrations' => 'Integrations',
|
||||
'locations' => 'Locations',
|
||||
'countries' => 'Countries',
|
||||
'cities' => 'Cities',
|
||||
'users' => 'Users',
|
||||
'shippers' => 'Shippers',
|
||||
'truck_owners' => 'Truck Owners',
|
||||
'user_registration' => 'User Registration',
|
||||
'pages' => 'Pages',
|
||||
'faqs' => 'FAQs',
|
||||
'landing_pages' => 'Landing Pages',
|
||||
'login_title' => 'Welcome Back',
|
||||
'login_subtitle' => 'Sign in to your account to continue',
|
||||
'email_address' => 'Email address',
|
||||
'email_placeholder' => 'name@example.com',
|
||||
'password' => 'Password',
|
||||
'forgot_password' => 'Forgot password?',
|
||||
'password_placeholder' => 'Enter your password',
|
||||
'change_password' => 'Change Password',
|
||||
'new_password' => 'New Password',
|
||||
'confirm_password' => 'Confirm Password',
|
||||
'passwords_do_not_match' => 'Passwords do not match.',
|
||||
'password_too_short' => 'Password must be at least 6 characters.',
|
||||
'password_updated' => 'Password updated successfully.',
|
||||
'sign_in' => 'Sign In',
|
||||
'dont_have_account' => 'Don\'t have an account?',
|
||||
'register_now' => 'Register now',
|
||||
'reset_password_title' => 'Reset Password',
|
||||
'reset_password_subtitle' => 'Enter your email and we\'ll send you a link to reset your password',
|
||||
'send_reset_link' => 'Send Reset Link',
|
||||
'back_to_login' => 'Back to login',
|
||||
'invalid_image' => 'Invalid image format. Please upload JPG, PNG, GIF, or WEBP.',
|
||||
'upload_failed' => 'Failed to save uploaded file.',
|
||||
'profile_updated' => 'Profile updated successfully.',
|
||||
'my_profile' => 'My Profile',
|
||||
'profile_picture' => 'Profile Picture',
|
||||
'change_picture' => 'Change Picture',
|
||||
'picture_hint' => 'JPG, PNG, or GIF up to 5MB',
|
||||
'full_name' => 'Full Name',
|
||||
'email_hint' => 'Email address cannot be changed.',
|
||||
'account_role' => 'Account Role',
|
||||
'save_changes' => 'Save Changes',
|
||||
'reg_title' => 'Create your logistics account',
|
||||
'reg_subtitle' => 'Shippers and truck owners can self-register with full profile details.',
|
||||
'reg_success_pending' => 'Registration completed successfully. Your account is pending admin approval.',
|
||||
'reg_success' => 'Registration completed successfully.',
|
||||
'role' => 'Role',
|
||||
'shipper' => 'Shipper',
|
||||
'truck_owner' => 'Truck Owner',
|
||||
'email' => 'Email',
|
||||
'phone' => 'Phone',
|
||||
'country' => 'Country',
|
||||
'select_country' => 'Select country',
|
||||
'city' => 'City',
|
||||
'select_city' => 'Select city',
|
||||
'address' => 'Address',
|
||||
'shipper_details' => 'Shipper details',
|
||||
'company_name' => 'Company name',
|
||||
'truck_details' => 'Truck owner details',
|
||||
'truck_type' => 'Truck type',
|
||||
'load_capacity' => 'Load capacity (tons)',
|
||||
'plate_no' => 'Plate number',
|
||||
'bank_account' => 'Bank Account / IBAN',
|
||||
'bank_name' => 'Bank Name',
|
||||
'bank_branch' => 'Bank Branch',
|
||||
'id_card_front' => 'ID card (Front Face)',
|
||||
'id_card_back' => 'ID card (Back Face)',
|
||||
'truck_reg_front' => 'Truck Registration (Front Face)',
|
||||
'truck_reg_back' => 'Truck Registration (Back Face)',
|
||||
'truck_picture' => 'Clear Truck Photo (showing plate number)',
|
||||
'create_account' => 'Create account',
|
||||
'back_to_admin' => 'Back to admin',
|
||||
'welcome_back' => 'Welcome to your dashboard. Manage your cargo shipments here.',
|
||||
'total_shipments_posted' => 'Total Shipments',
|
||||
'active_shipments' => 'Active Shipments',
|
||||
'delivered_shipments' => 'Delivered Shipments',
|
||||
'route_label' => 'Route',
|
||||
'total_label' => 'total',
|
||||
'welcome_back_owner' => 'Find loads and submit your best rate.',
|
||||
'total_offers' => 'Total Offers',
|
||||
'won_shipments' => 'Won Shipments',
|
||||
),
|
||||
"ar" => array (
|
||||
'app_name' => 'CargoLink',
|
||||
'nav_home' => 'نظرة عامة',
|
||||
'nav_shipper' => 'لوحة الشاحن',
|
||||
'nav_owner' => 'لوحة مالك الشاحنة',
|
||||
'nav_admin' => 'لوحة الإدارة',
|
||||
'hero_title' => 'انقل شحنتك بسرعة مع شاحنات موثوقة.',
|
||||
'hero_subtitle' => 'أنشئ شحنة، استلم عروضاً، وادفع عبر ثواني أو التحويل البنكي.',
|
||||
'hero_tagline' => 'منصة لوجستية متعددة اللغات',
|
||||
'register_shipper' => 'التسجيل كشاحن',
|
||||
'register_owner' => 'التسجيل كمالك شاحنة',
|
||||
'cta_shipper' => 'إنشاء شحنة',
|
||||
'cta_owner' => 'البحث عن الشحنات',
|
||||
'cta_admin' => 'الدخول للإدارة',
|
||||
'stats_shipments' => 'الشحنات المنشورة',
|
||||
'stats_offers' => 'العروض الحالية',
|
||||
'stats_confirmed' => 'الرحلات المؤكدة',
|
||||
'section_workflow' => 'طريقة العمل',
|
||||
'recent_shipments' => 'أحدث الشحنات',
|
||||
'step_post' => 'يقوم الشاحن بإدخال تفاصيل الشحنة وطريقة الدفع.',
|
||||
'step_offer' => 'يرسل أصحاب الشاحنات أفضل عروضهم.',
|
||||
'step_confirm' => 'تؤكد الإدارة الحجز وتحدث الحالة.',
|
||||
'step_confirm_desc' => 'حجز آمن وتتبع التسليم حتى الانتهاء.',
|
||||
'shipper_dashboard' => 'لوحة الشاحن',
|
||||
'new_shipment' => 'إنشاء شحنة',
|
||||
'shipper_name' => 'اسم الشاحن',
|
||||
'shipper_company' => 'الشركة',
|
||||
'origin' => 'مدينة الانطلاق',
|
||||
'destination' => 'مدينة الوصول',
|
||||
'cargo' => 'وصف الحمولة',
|
||||
'cargo_placeholder' => 'مثال: 20 منصة إلكترونيات',
|
||||
'weight' => 'الوزن (طن)',
|
||||
'pickup_date' => 'تاريخ الاستلام',
|
||||
'delivery_date' => 'تاريخ التسليم',
|
||||
'payment_method' => 'طريقة الدفع',
|
||||
'payment_thawani' => 'الدفع الإلكتروني عبر ثواني',
|
||||
'payment_bank' => 'تحويل بنكي',
|
||||
'submit_shipment' => 'إرسال الشحنة',
|
||||
'shipments_list' => 'أحدث الشحنات',
|
||||
'status' => 'الحالة',
|
||||
'offer' => 'أفضل عرض',
|
||||
'actions' => 'إجراءات',
|
||||
'view' => 'عرض',
|
||||
'owner_dashboard' => 'لوحة مالك الشاحنة',
|
||||
'available_shipments' => 'الشحنات المتاحة',
|
||||
'offer_price' => 'سعر العرض',
|
||||
'offer_owner' => 'اسم مالك الشاحنة',
|
||||
'submit_offer' => 'إرسال العرض',
|
||||
'admin_dashboard' => 'لوحة الإدارة',
|
||||
'update_status' => 'تحديث الحالة',
|
||||
'save' => 'حفظ',
|
||||
'shipment_detail' => 'تفاصيل الشحنة',
|
||||
'created_at' => 'تم الإنشاء',
|
||||
'best_offer' => 'أفضل عرض',
|
||||
'assign_owner' => 'المالك المعتمد',
|
||||
'no_shipments' => 'لا توجد شحنات بعد. ابدأ بإنشاء أول شحنة.',
|
||||
'no_offers' => 'لا توجد عروض بعد.',
|
||||
'success_shipment' => 'تم نشر الشحنة بنجاح.',
|
||||
'success_offer' => 'تم إرسال العرض إلى الشاحن.',
|
||||
'success_status' => 'تم تحديث الحالة.',
|
||||
'error_required' => 'يرجى تعبئة جميع الحقول المطلوبة.',
|
||||
'error_invalid' => 'يرجى إدخال قيم صحيحة.',
|
||||
'status_posted' => 'منشورة',
|
||||
'status_offered' => 'بعرض',
|
||||
'status_confirmed' => 'مؤكدة',
|
||||
'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' => 'نقوم بالتحقق من جميع أصحاب الشاحنات لضمان راحتك.',
|
||||
'view_faq' => 'عرض الأسئلة الشائعة',
|
||||
'faq_title' => 'لديك أسئلة؟',
|
||||
'faq_subtitle' => 'اطلع على الأسئلة الشائعة لمعرفة المزيد حول كيفية عمل منصتنا.',
|
||||
'motivation_title' => 'هل أنت مستعد لتحويل خدماتك اللوجستية؟',
|
||||
'motivation_subtitle' => 'انضم إلى منصتنا اليوم للعثور على شاحنات موثوقة أو تأمين أفضل الشحنات في السوق.',
|
||||
'company' => 'الشركة',
|
||||
'about_us' => 'معلومات عنا',
|
||||
'careers' => 'الوظائف',
|
||||
'contact' => 'اتصل بنا',
|
||||
'resources' => 'الموارد',
|
||||
'help_center' => 'مركز المساعدة / الأسئلة الشائعة',
|
||||
'terms_of_service' => 'شروط الخدمة',
|
||||
'privacy_policy' => 'سياسة الخصوصية',
|
||||
'language' => 'اللغة',
|
||||
'all_rights_reserved' => 'جميع الحقوق محفوظة.',
|
||||
'dashboard' => 'لوحة القيادة',
|
||||
'settings' => 'الإعدادات',
|
||||
'company_setting' => 'إعدادات الشركة',
|
||||
'integrations' => 'التكاملات',
|
||||
'locations' => 'المواقع',
|
||||
'countries' => 'البلدان',
|
||||
'cities' => 'المدن',
|
||||
'users' => 'المستخدمون',
|
||||
'shippers' => 'الشاحنون',
|
||||
'truck_owners' => 'أصحاب الشاحنات',
|
||||
'user_registration' => 'تسجيل المستخدم',
|
||||
'pages' => 'الصفحات',
|
||||
'faqs' => 'الأسئلة الشائعة',
|
||||
'landing_pages' => 'صفحات الهبوط',
|
||||
'login_title' => 'مرحبًا بعودتك',
|
||||
'login_subtitle' => 'قم بتسجيل الدخول إلى حسابك للمتابعة',
|
||||
'email_address' => 'البريد الإلكتروني',
|
||||
'email_placeholder' => 'name@example.com',
|
||||
'password' => 'كلمة المرور',
|
||||
'forgot_password' => 'هل نسيت كلمة المرور؟',
|
||||
'password_placeholder' => 'أدخل كلمة المرور',
|
||||
'change_password' => 'تغيير كلمة المرور',
|
||||
'new_password' => 'كلمة المرور الجديدة',
|
||||
'confirm_password' => 'تأكيد كلمة المرور',
|
||||
'passwords_do_not_match' => 'كلمات المرور غير متطابقة.',
|
||||
'password_too_short' => 'يجب أن تتكون كلمة المرور من 6 أحرف على الأقل.',
|
||||
'password_updated' => 'تم تحديث كلمة المرور بنجاح.',
|
||||
'sign_in' => 'تسجيل الدخول',
|
||||
'dont_have_account' => 'ليس لديك حساب؟',
|
||||
'register_now' => 'سجل الآن',
|
||||
'reset_password_title' => 'إعادة تعيين كلمة المرور',
|
||||
'reset_password_subtitle' => 'أدخل بريدك الإلكتروني وسنرسل لك رابطًا لإعادة تعيين كلمة المرور',
|
||||
'send_reset_link' => 'إرسال رابط إعادة التعيين',
|
||||
'back_to_login' => 'العودة لتسجيل الدخول',
|
||||
'invalid_image' => 'صيغة صورة غير صالحة. يرجى تحميل JPG أو PNG أو GIF أو WEBP.',
|
||||
'upload_failed' => 'فشل في حفظ الملف المحمل.',
|
||||
'profile_updated' => 'تم تحديث الملف الشخصي بنجاح.',
|
||||
'my_profile' => 'ملفي الشخصي',
|
||||
'profile_picture' => 'صورة الملف الشخصي',
|
||||
'change_picture' => 'تغيير الصورة',
|
||||
'picture_hint' => 'JPG، PNG، أو GIF حتى 5 ميغابايت',
|
||||
'full_name' => 'الاسم الكامل',
|
||||
'email_hint' => 'لا يمكن تغيير عنوان البريد الإلكتروني.',
|
||||
'account_role' => 'دور الحساب',
|
||||
'save_changes' => 'حفظ التغييرات',
|
||||
'reg_title' => 'أنشئ حسابك اللوجستي',
|
||||
'reg_subtitle' => 'يمكن للشاحنين وأصحاب الشاحنات التسجيل الذاتي ببيانات الملف الشخصي الكاملة.',
|
||||
'reg_success_pending' => 'اكتمل التسجيل بنجاح. حسابك في انتظار موافقة الإدارة.',
|
||||
'reg_success' => 'اكتمل التسجيل بنجاح.',
|
||||
'role' => 'الدور',
|
||||
'shipper' => 'شاحن',
|
||||
'truck_owner' => 'مالك شاحنة',
|
||||
'email' => 'البريد الإلكتروني',
|
||||
'phone' => 'الهاتف',
|
||||
'country' => 'البلد',
|
||||
'select_country' => 'اختر البلد',
|
||||
'city' => 'المدينة',
|
||||
'select_city' => 'اختر المدينة',
|
||||
'address' => 'العنوان',
|
||||
'shipper_details' => 'تفاصيل الشاحن',
|
||||
'company_name' => 'اسم الشركة',
|
||||
'truck_details' => 'تفاصيل مالك الشاحنة',
|
||||
'truck_type' => 'نوع الشاحنة',
|
||||
'load_capacity' => 'سعة الحمولة (طن)',
|
||||
'plate_no' => 'رقم اللوحة',
|
||||
'bank_account' => 'الحساب البنكي / الآيبان',
|
||||
'bank_name' => 'اسم البنك',
|
||||
'bank_branch' => 'فرع البنك',
|
||||
'id_card_front' => 'البطاقة الشخصية (الوجه الأمامي)',
|
||||
'id_card_back' => 'البطاقة الشخصية (الوجه الخلفي)',
|
||||
'truck_reg_front' => 'تسجيل الشاحنة (الوجه الأمامي)',
|
||||
'truck_reg_back' => 'تسجيل الشاحنة (الوجه الخلفي)',
|
||||
'truck_picture' => 'صورة واضحة للشاحنة (تظهر رقم اللوحة)',
|
||||
'create_account' => 'إنشاء حساب',
|
||||
'back_to_admin' => 'العودة للإدارة',
|
||||
'welcome_back' => 'مرحبًا بك في لوحة القيادة الخاصة بك. قم بإدارة شحناتك هنا.',
|
||||
'total_shipments_posted' => 'إجمالي الشحنات',
|
||||
'active_shipments' => 'الشحنات النشطة',
|
||||
'delivered_shipments' => 'الشحنات المسلمة',
|
||||
'route_label' => 'المسار',
|
||||
'total_label' => 'المجموع',
|
||||
'welcome_back_owner' => 'ابحث عن الأحمال وقدم أفضل سعر لديك.',
|
||||
'total_offers' => 'إجمالي العروض',
|
||||
'won_shipments' => 'الشحنات الفائزة',
|
||||
)
|
||||
];
|
||||
|
||||
function t(string $key): string
|
||||
@ -164,8 +388,7 @@ function e($value): string
|
||||
|
||||
function ensure_schema(): void
|
||||
{
|
||||
db()->exec("
|
||||
CREATE TABLE IF NOT EXISTS landing_sections (
|
||||
db()->exec("\n CREATE TABLE IF NOT EXISTS landing_sections (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
subtitle TEXT NULL,
|
||||
@ -179,6 +402,11 @@ function ensure_schema(): void
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
");
|
||||
|
||||
try { db()->exec("ALTER TABLE landing_sections ADD COLUMN title_ar VARCHAR(255) NULL AFTER title"); } catch (Exception $e) {}
|
||||
try { db()->exec("ALTER TABLE landing_sections ADD COLUMN subtitle_ar TEXT NULL AFTER subtitle"); } catch (Exception $e) {}
|
||||
try { db()->exec("ALTER TABLE landing_sections ADD COLUMN content_ar TEXT NULL AFTER content"); } catch (Exception $e) {}
|
||||
try { db()->exec("ALTER TABLE landing_sections ADD COLUMN button_text_ar VARCHAR(100) NULL AFTER button_text"); } catch (Exception $e) {}
|
||||
|
||||
$sql = <<<SQL
|
||||
CREATE TABLE IF NOT EXISTS shipments (
|
||||
@ -199,8 +427,8 @@ 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) {}
|
||||
try { db()->exec("ALTER TABLE users ADD COLUMN profile_picture VARCHAR(255) NULL AFTER full_name"); } catch (Exception $e) {}
|
||||
try { db()->exec("ALTER TABLE users ADD COLUMN status ENUM('pending','active','rejected') NOT NULL DEFAULT 'active'"); } catch (Exception $e) {}
|
||||
try { db()->exec("ALTER TABLE users ADD COLUMN profile_picture VARCHAR(255) NULL AFTER full_name"); } catch (Exception $e) {}
|
||||
|
||||
// Payment fields for shipments
|
||||
try { db()->exec("ALTER TABLE shipments ADD COLUMN platform_fee DECIMAL(10,2) DEFAULT 0.00 AFTER offer_price"); } catch (Exception $e) {}
|
||||
@ -320,4 +548,4 @@ function get_setting(string $key, $default = ''): string
|
||||
{
|
||||
$settings = get_settings();
|
||||
return $settings[$key] ?? $default;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/app.php';
|
||||
|
||||
function render_header(string $title, string $active = ''): void
|
||||
function render_header(string $title, string $active = '', bool $isFluid = false): void
|
||||
{
|
||||
global $lang, $dir;
|
||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
|
||||
@ -40,8 +40,8 @@ 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 shadow-sm py-3">
|
||||
<div class="container">
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-white border-bottom sticky-top shadow-sm py-3 z-3">
|
||||
<div class="<?= $isFluid ? 'container-fluid px-4' : 'container' ?>">
|
||||
<a class="navbar-brand fs-4 d-flex align-items-center" href="<?= e(url_with_lang('index.php')) ?>">
|
||||
<?php if ($logoPath): ?>
|
||||
<img src="<?= e($logoPath) ?>" alt="<?= e($appName) ?> Logo" height="32" class="me-2 rounded">
|
||||
@ -126,7 +126,7 @@ function render_header(string $title, string $active = ''): void
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<main class="container py-5">
|
||||
<main class="<?= $isFluid ? 'container-fluid p-0' : 'container py-5' ?>">
|
||||
<?php
|
||||
}
|
||||
|
||||
@ -140,7 +140,7 @@ function render_footer(): void
|
||||
$companyAddress = get_setting('company_address', '');
|
||||
?>
|
||||
</main>
|
||||
<footer class="bg-white border-top py-5 mt-5">
|
||||
<footer class="bg-white border-top py-5 mt-auto">
|
||||
<div class="container">
|
||||
<div class="row g-4 mb-4">
|
||||
<div class="col-md-4">
|
||||
@ -212,14 +212,14 @@ function render_footer(): void
|
||||
|
||||
function render_admin_sidebar(string $active = 'dashboard'): void
|
||||
{
|
||||
$settingsActive = in_array($active, ['company_profile', 'integrations']);
|
||||
$settingsActive = in_array($active, ['company_profile', 'integrations', 'notification_templates']);
|
||||
$locationsActive = in_array($active, ['countries', 'cities']);
|
||||
$usersActive = in_array($active, ['shippers', 'truck_owners', 'register']);
|
||||
$pagesActive = in_array($active, ['faqs', 'landing_pages']);
|
||||
?>
|
||||
<aside class="admin-sidebar panel p-4 shadow-sm border-0">
|
||||
<h2 class="h5 fw-bold mb-4"><i class="bi bi-shield-lock me-2 text-primary"></i><?= e(t('nav_admin')) ?></h2>
|
||||
<nav class="nav flex-column gap-2">
|
||||
<aside class="admin-sidebar d-flex flex-column h-100 py-4 px-3">
|
||||
<h2 class="h5 fw-bold mb-4 px-2"><i class="bi bi-shield-lock me-2 text-primary"></i><?= e(t('nav_admin')) ?></h2>
|
||||
<nav class="nav flex-column gap-1 flex-grow-1">
|
||||
<a class="admin-nav-link <?= $active === 'shipments' ? 'active' : '' ?>" href="<?= e(url_with_lang('admin_shipments.php')) ?>">
|
||||
<i class="bi bi-box2-fill me-2"></i><?= e(t('shipments') ?: 'Shipments') ?>
|
||||
</a>
|
||||
@ -239,6 +239,9 @@ function render_admin_sidebar(string $active = 'dashboard'): void
|
||||
<a class="admin-nav-link <?= $active === 'integrations' ? 'active' : '' ?>" href="<?= e(url_with_lang('admin_integrations.php')) ?>">
|
||||
<i class="bi bi-plug me-2"></i><?= e(t('integrations')) ?>
|
||||
</a>
|
||||
<a class="admin-nav-link <?= $active === 'notification_templates' ? 'active' : '' ?>" href="<?= e(url_with_lang('admin_notification_templates.php')) ?>">
|
||||
<i class="bi bi-bell-fill me-2"></i><?= e(t('notification_templates') ?: 'Notifications') ?>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -291,6 +294,12 @@ function render_admin_sidebar(string $active = 'dashboard'): void
|
||||
</div>
|
||||
|
||||
</nav>
|
||||
|
||||
<div class="mt-4 pt-4 border-top text-center">
|
||||
<a href="<?= e(url_with_lang('logout.php')) ?>" class="btn btn-outline-danger w-100 btn-sm">
|
||||
<i class="bi bi-box-arrow-right me-2"></i>Logout
|
||||
</a>
|
||||
</div>
|
||||
</aside>
|
||||
<?php
|
||||
}
|
||||
59
index.php
59
index.php
@ -34,6 +34,13 @@ try {
|
||||
$stmt = db()->query("SELECT * FROM landing_sections WHERE is_active = 1 ORDER BY section_order ASC, id ASC");
|
||||
$landingSections = $stmt->fetchAll();
|
||||
foreach ($landingSections as $sec):
|
||||
// Determine correct language for dynamic content
|
||||
$isAr = ($lang ?? 'en') === 'ar';
|
||||
$title = ($isAr && !empty($sec['title_ar'])) ? $sec['title_ar'] : $sec['title'];
|
||||
$subtitle = ($isAr && !empty($sec['subtitle_ar'])) ? $sec['subtitle_ar'] : $sec['subtitle'];
|
||||
$content = ($isAr && !empty($sec['content_ar'])) ? $sec['content_ar'] : $sec['content'];
|
||||
$button_text = ($isAr && !empty($sec['button_text_ar'])) ? $sec['button_text_ar'] : $sec['button_text'];
|
||||
|
||||
if ($sec['section_type'] === 'hero'):
|
||||
?>
|
||||
<div class="hero-section mb-5">
|
||||
@ -44,10 +51,10 @@ try {
|
||||
<?= e(t('hero_tagline')) ?>
|
||||
</span>
|
||||
<h1 class="display-5 fw-bold mb-4" style="line-height: 1.2;">
|
||||
<?= e($sec['title'] ?: t('hero_title')) ?>
|
||||
<?= e($title ?: t('hero_title')) ?>
|
||||
</h1>
|
||||
<p class="fs-5 text-muted mb-5">
|
||||
<?= e($sec['subtitle'] ?: t('hero_subtitle')) ?>
|
||||
<?= e($subtitle ?: 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'])) ?>">
|
||||
@ -72,8 +79,8 @@ try {
|
||||
</div>
|
||||
<?php elseif ($sec['section_type'] === 'stats'): ?>
|
||||
<div class="row mb-5 g-4">
|
||||
<?php if ($sec['title']): ?>
|
||||
<div class="col-12 text-center mb-3"><h2 class="display-6 fw-bold"><?= e($sec['title']) ?></h2></div>
|
||||
<?php if ($title): ?>
|
||||
<div class="col-12 text-center mb-3"><h2 class="display-6 fw-bold"><?= e($title) ?></h2></div>
|
||||
<?php endif; ?>
|
||||
<div class="col-md-4">
|
||||
<div class="stat-card">
|
||||
@ -97,8 +104,8 @@ try {
|
||||
<?php elseif ($sec['section_type'] === 'features'): ?>
|
||||
<section class="mb-5">
|
||||
<div class="text-center mb-5">
|
||||
<h2 class="display-6 fw-bold mb-3"><?= e($sec['title'] ?: t('why_choose_us')) ?></h2>
|
||||
<p class="text-muted fs-5 mx-auto" style="max-width: 600px;"><?= e($sec['subtitle'] ?: t('motivation_phrase')) ?></p>
|
||||
<h2 class="display-6 fw-bold mb-3"><?= e($title ?: t('why_choose_us')) ?></h2>
|
||||
<p class="text-muted fs-5 mx-auto" style="max-width: 600px;"><?= e($subtitle ?: t('motivation_phrase')) ?></p>
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
@ -139,7 +146,7 @@ try {
|
||||
</div>
|
||||
<div class="col-md-6 order-md-1">
|
||||
<div class="p-lg-4">
|
||||
<h2 class="display-6 fw-bold mb-4"><?= e($sec['title'] ?: t('section_workflow')) ?></h2>
|
||||
<h2 class="display-6 fw-bold mb-4"><?= e($title ?: t('section_workflow')) ?></h2>
|
||||
|
||||
<div class="d-flex mb-4">
|
||||
<div class="flex-shrink-0">
|
||||
@ -178,7 +185,7 @@ try {
|
||||
<?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($sec['title'] ?: t('recent_shipments')) ?></h2>
|
||||
<h2 class="section-title mb-0"><?= e($title ?: t('recent_shipments')) ?></h2>
|
||||
<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>
|
||||
|
||||
@ -219,8 +226,8 @@ try {
|
||||
<?php elseif ($sec['section_type'] === 'faq'): ?>
|
||||
<section class="mb-5 text-center">
|
||||
<div class="panel p-5 border-0 shadow-sm rounded-4" style="background-color: #f8f9fa;">
|
||||
<h2 class="display-6 fw-bold mb-3"><?= e($sec['title'] ?: t('faq_title')) ?></h2>
|
||||
<p class="text-muted fs-5 mb-4 mx-auto" style="max-width: 600px;"><?= e($sec['subtitle'] ?: t('faq_subtitle')) ?></p>
|
||||
<h2 class="display-6 fw-bold mb-3"><?= e($title ?: t('faq_title')) ?></h2>
|
||||
<p class="text-muted fs-5 mb-4 mx-auto" style="max-width: 600px;"><?= e($subtitle ?: t('faq_subtitle')) ?></p>
|
||||
<a href="<?= e(url_with_lang('faq.php')) ?>" class="btn btn-primary btn-lg px-4 rounded-pill shadow-sm">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-question-circle me-2" viewBox="0 0 16 16"><path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"/><path d="M5.255 5.786a.237.237 0 0 0 .241.247h.825c.138 0 .248-.113.266-.25.09-.656.54-1.134 1.342-1.134.686 0 1.314.343 1.314 1.168 0 .635-.374.927-.965 1.371-.673.489-1.206 1.06-1.168 1.987l.003.217a.25.25 0 0 0 .25.246h.811a.25.25 0 0 0 .25-.25v-.105c0-.718.273-.927 1.01-1.486.609-.463 1.244-.977 1.244-2.056 0-1.511-1.276-2.241-2.673-2.241-1.267 0-2.655.59-2.75 2.286zm1.557 5.763c0 .533.425.927 1.01.927.609 0 1.028-.394 1.028-.927 0-.552-.42-.94-1.029-.94-.584 0-1.009.388-1.009.94z"/></svg><?= e(t('view_faq')) ?>
|
||||
</a>
|
||||
@ -228,8 +235,8 @@ try {
|
||||
</section>
|
||||
<?php elseif ($sec['section_type'] === 'motivation'): ?>
|
||||
<div class="motivation-box mb-5" <?php if ($sec['image_path']) { echo 'style="background-image: url(' . e($sec['image_path']) . '); background-size: cover; background-position: center;"'; } ?>>
|
||||
<h3 class="display-6 fw-bold mb-3"><?= e($sec['title'] ?: t('motivation_title')) ?></h3>
|
||||
<p class="fs-5 text-white-50 mb-4 mx-auto" style="max-width: 600px;"><?= e($sec['subtitle'] ?: t('motivation_subtitle')) ?></p>
|
||||
<h3 class="display-6 fw-bold mb-3"><?= e($title ?: t('motivation_title')) ?></h3>
|
||||
<p class="fs-5 text-white-50 mb-4 mx-auto" style="max-width: 600px;"><?= e($subtitle ?: t('motivation_subtitle')) ?></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>
|
||||
@ -242,15 +249,15 @@ try {
|
||||
<?php if ($sec['image_path']): ?>
|
||||
<img src="<?= e($sec['image_path']) ?>" alt="" class="img-fluid mb-4 rounded-4 shadow-sm" style="max-height: 300px; object-fit: cover;">
|
||||
<?php endif; ?>
|
||||
<h2 class="display-6 fw-bold mb-3"><?= e($sec['title']) ?></h2>
|
||||
<?php if ($sec['subtitle']): ?>
|
||||
<p class="text-muted fs-5 mb-4 mx-auto" style="max-width: 600px;"><?= e($sec['subtitle']) ?></p>
|
||||
<h2 class="display-6 fw-bold mb-3"><?= e($title) ?></h2>
|
||||
<?php if ($subtitle): ?>
|
||||
<p class="text-muted fs-5 mb-4 mx-auto" style="max-width: 600px;"><?= e($subtitle) ?></p>
|
||||
<?php endif; ?>
|
||||
<?php if ($sec['content']): ?>
|
||||
<div class="mb-4 text-start mx-auto" style="max-width: 800px;"><?= $sec['content'] ?></div>
|
||||
<?php if ($content): ?>
|
||||
<div class="mb-4 text-start mx-auto" style="max-width: 800px;"><?= $content ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($sec['button_text']): ?>
|
||||
<a href="<?= e(url_with_lang($sec['button_link'] ?: '#')) ?>" class="btn btn-primary btn-lg px-4 rounded-pill shadow-sm"><?= e($sec['button_text']) ?></a>
|
||||
<?php if ($button_text): ?>
|
||||
<a href="<?= e(url_with_lang($sec['button_link'] ?: '#')) ?>" class="btn btn-primary btn-lg px-4 rounded-pill shadow-sm"><?= e($button_text) ?></a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
@ -262,15 +269,15 @@ try {
|
||||
</div>
|
||||
<div class="col-md-6 <?= $sec['layout'] === 'text_right' ? 'order-md-2' : 'order-md-1' ?>">
|
||||
<div class="p-lg-4">
|
||||
<h2 class="display-6 fw-bold mb-4"><?= e($sec['title']) ?></h2>
|
||||
<?php if ($sec['subtitle']): ?>
|
||||
<h5 class="fw-bold mb-3 text-muted"><?= e($sec['subtitle']) ?></h5>
|
||||
<h2 class="display-6 fw-bold mb-4"><?= e($title) ?></h2>
|
||||
<?php if ($subtitle): ?>
|
||||
<h5 class="fw-bold mb-3 text-muted"><?= e($subtitle) ?></h5>
|
||||
<?php endif; ?>
|
||||
<?php if ($sec['content']): ?>
|
||||
<div class="mb-4"><?= $sec['content'] ?></div>
|
||||
<?php if ($content): ?>
|
||||
<div class="mb-4"><?= $content ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($sec['button_text']): ?>
|
||||
<a href="<?= e(url_with_lang($sec['button_link'] ?: '#')) ?>" class="btn btn-primary btn-lg px-4 rounded-pill shadow-sm"><?= e($sec['button_text']) ?></a>
|
||||
<?php if ($button_text): ?>
|
||||
<a href="<?= e(url_with_lang($sec['button_link'] ?: '#')) ?>" class="btn btn-primary btn-lg px-4 rounded-pill shadow-sm"><?= e($button_text) ?></a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -117,6 +117,12 @@ $translations_en = [
|
||||
'password' => 'Password',
|
||||
'forgot_password' => 'Forgot password?',
|
||||
'password_placeholder' => 'Enter your password',
|
||||
'change_password' => 'Change Password',
|
||||
'new_password' => 'New Password',
|
||||
'confirm_password' => 'Confirm Password',
|
||||
'passwords_do_not_match' => 'Passwords do not match.',
|
||||
'password_too_short' => 'Password must be at least 6 characters.',
|
||||
'password_updated' => 'Password updated successfully.',
|
||||
'sign_in' => 'Sign In',
|
||||
'dont_have_account' => 'Don\'t have an account?',
|
||||
'register_now' => 'Register now',
|
||||
@ -292,6 +298,12 @@ $translations_ar = [
|
||||
'password' => 'كلمة المرور',
|
||||
'forgot_password' => 'هل نسيت كلمة المرور؟',
|
||||
'password_placeholder' => 'أدخل كلمة المرور',
|
||||
'change_password' => 'تغيير كلمة المرور',
|
||||
'new_password' => 'كلمة المرور الجديدة',
|
||||
'confirm_password' => 'تأكيد كلمة المرور',
|
||||
'passwords_do_not_match' => 'كلمات المرور غير متطابقة.',
|
||||
'password_too_short' => 'يجب أن تتكون كلمة المرور من 6 أحرف على الأقل.',
|
||||
'password_updated' => 'تم تحديث كلمة المرور بنجاح.',
|
||||
'sign_in' => 'تسجيل الدخول',
|
||||
'dont_have_account' => 'ليس لديك حساب؟',
|
||||
'register_now' => 'سجل الآن',
|
||||
|
||||
52
profile.php
52
profile.php
@ -58,15 +58,43 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'updat
|
||||
}
|
||||
}
|
||||
|
||||
// Password Update Logic
|
||||
$newPassword = trim($_POST['new_password'] ?? '');
|
||||
$confirmPassword = trim($_POST['confirm_password'] ?? '');
|
||||
|
||||
if ($newPassword !== '') {
|
||||
if ($newPassword !== $confirmPassword) {
|
||||
$errors[] = t('passwords_do_not_match');
|
||||
} elseif (strlen($newPassword) < 6) {
|
||||
$errors[] = t('password_too_short');
|
||||
}
|
||||
}
|
||||
|
||||
if (!$errors) {
|
||||
try {
|
||||
$updateStmt = db()->prepare("UPDATE users SET full_name = :name, profile_picture = :pic WHERE id = :id");
|
||||
$updateStmt->execute([
|
||||
$sql = "UPDATE users SET full_name = :name, profile_picture = :pic";
|
||||
$params = [
|
||||
':name' => $fullName,
|
||||
':pic' => $profilePicPath,
|
||||
':id' => $userId
|
||||
]);
|
||||
set_flash('success', t('profile_updated'));
|
||||
];
|
||||
|
||||
if ($newPassword !== '') {
|
||||
$sql .= ", password = :pass";
|
||||
$params[':pass'] = password_hash($newPassword, PASSWORD_DEFAULT);
|
||||
}
|
||||
|
||||
$sql .= " WHERE id = :id";
|
||||
|
||||
$updateStmt = db()->prepare($sql);
|
||||
$updateStmt->execute($params);
|
||||
|
||||
$msg = t('profile_updated');
|
||||
if ($newPassword !== '') {
|
||||
$msg .= ' ' . t('password_updated');
|
||||
}
|
||||
|
||||
set_flash('success', $msg);
|
||||
header("Location: " . url_with_lang('profile.php'));
|
||||
exit;
|
||||
} catch (Throwable $e) {
|
||||
@ -140,6 +168,20 @@ $flash = get_flash();
|
||||
<input type="text" class="form-control form-control-lg bg-light text-capitalize" value="<?= e($user['role'] ?? '') ?>" readonly disabled>
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
|
||||
<h4 class="mb-3"><?= e(t('change_password')) ?></h4>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-bold"><?= e(t('new_password')) ?></label>
|
||||
<input type="password" class="form-control form-control-lg" name="new_password" autocomplete="new-password">
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="form-label fw-bold"><?= e(t('confirm_password')) ?></label>
|
||||
<input type="password" class="form-control form-control-lg" name="confirm_password" autocomplete="new-password">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary btn-lg w-100 shadow-sm rounded-pill fw-bold">
|
||||
<?= e(t('save_changes')) ?>
|
||||
</button>
|
||||
@ -173,4 +215,4 @@ function previewImage(input) {
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php render_footer(); ?>
|
||||
<?php render_footer(); ?>
|
||||
@ -2,6 +2,7 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/includes/layout.php';
|
||||
require_once __DIR__ . '/includes/NotificationService.php';
|
||||
|
||||
ensure_schema();
|
||||
|
||||
@ -37,15 +38,31 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
} elseif (!is_numeric($offerPrice)) {
|
||||
$errors[] = t('error_invalid');
|
||||
} else {
|
||||
$truckOwnerId = isset($_SESSION['user_id']) ? (int)$_SESSION['user_id'] : null;
|
||||
|
||||
$stmt = db()->prepare(
|
||||
"UPDATE shipments SET offer_owner = :offer_owner, offer_price = :offer_price, status = 'offered'
|
||||
"UPDATE shipments SET offer_owner = :offer_owner, offer_price = :offer_price, status = 'offered', truck_owner_id = :truck_owner_id
|
||||
WHERE id = :id"
|
||||
);
|
||||
$stmt->execute([
|
||||
':offer_owner' => $offerOwner,
|
||||
':offer_price' => $offerPrice,
|
||||
':truck_owner_id' => $truckOwnerId,
|
||||
':id' => $shipmentId,
|
||||
]);
|
||||
|
||||
// Notify Shipper
|
||||
if (!empty($shipment['shipper_id'])) {
|
||||
$shipperUser = db()->query("SELECT * FROM users WHERE id = " . (int)$shipment['shipper_id'])->fetch();
|
||||
if ($shipperUser) {
|
||||
NotificationService::send('shipment_offered', $shipperUser, [
|
||||
'shipment_id' => $shipmentId,
|
||||
'offer_price' => $offerPrice,
|
||||
'offer_owner' => $offerOwner
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
set_flash('success', t('success_offer'));
|
||||
header('Location: ' . url_with_lang('shipment_detail.php', ['id' => $shipmentId]));
|
||||
exit;
|
||||
@ -70,10 +87,46 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
':id' => $shipmentId
|
||||
]);
|
||||
|
||||
// Notify Truck Owner
|
||||
if (!empty($shipment['truck_owner_id'])) {
|
||||
$truckOwnerUser = db()->query("SELECT * FROM users WHERE id = " . (int)$shipment['truck_owner_id'])->fetch();
|
||||
if ($truckOwnerUser) {
|
||||
NotificationService::send('shipment_accepted', $truckOwnerUser, [
|
||||
'shipment_id' => $shipmentId
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
set_flash('success', t('Payment successful. Shipment confirmed!'));
|
||||
header('Location: ' . url_with_lang('shipment_detail.php', ['id' => $shipmentId]));
|
||||
exit;
|
||||
}
|
||||
} elseif ($action === 'reject_offer' && $isShipper) {
|
||||
if ($shipment && $shipment['status'] === 'offered') {
|
||||
// Notify Truck Owner first (before clearing ID)
|
||||
if (!empty($shipment['truck_owner_id'])) {
|
||||
$truckOwnerUser = db()->query("SELECT * FROM users WHERE id = " . (int)$shipment['truck_owner_id'])->fetch();
|
||||
if ($truckOwnerUser) {
|
||||
NotificationService::send('shipment_rejected', $truckOwnerUser, [
|
||||
'shipment_id' => $shipmentId
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$stmt = db()->prepare(
|
||||
"UPDATE shipments
|
||||
SET status = 'posted',
|
||||
offer_price = NULL,
|
||||
offer_owner = NULL,
|
||||
truck_owner_id = NULL
|
||||
WHERE id = :id"
|
||||
);
|
||||
$stmt->execute([':id' => $shipmentId]);
|
||||
|
||||
set_flash('success', 'Offer rejected. Shipment is now open for new offers.');
|
||||
header('Location: ' . url_with_lang('shipment_detail.php', ['id' => $shipmentId]));
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -209,15 +262,23 @@ render_header(t('shipment_detail'));
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="post">
|
||||
<input type="hidden" name="action" value="accept_offer">
|
||||
<button class="btn btn-success w-100 py-3 fw-bold shadow-sm" type="submit">
|
||||
<i class="bi bi-credit-card-2-front me-2"></i>Accept & Pay Now
|
||||
</button>
|
||||
<p class="text-center text-muted small mt-3 mb-0">
|
||||
<i class="bi bi-shield-lock me-1"></i> Secure payment via <?= e($shipment['payment_method'] === 'bank_transfer' ? 'Bank Transfer' : 'Thawani') ?>
|
||||
</p>
|
||||
</form>
|
||||
<div class="d-grid gap-2">
|
||||
<form method="post">
|
||||
<input type="hidden" name="action" value="accept_offer">
|
||||
<button class="btn btn-success w-100 py-3 fw-bold shadow-sm" type="submit">
|
||||
<i class="bi bi-credit-card-2-front me-2"></i>Accept & Pay Now
|
||||
</button>
|
||||
</form>
|
||||
<form method="post" onsubmit="return confirm('Are you sure you want to reject this offer?');">
|
||||
<input type="hidden" name="action" value="reject_offer">
|
||||
<button class="btn btn-outline-danger w-100 py-2 fw-bold" type="submit">
|
||||
<i class="bi bi-x-circle me-2"></i>Reject Offer
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<p class="text-center text-muted small mt-3 mb-0">
|
||||
<i class="bi bi-shield-lock me-1"></i> Secure payment via <?= e($shipment['payment_method'] === 'bank_transfer' ? 'Bank Transfer' : 'Thawani') ?>
|
||||
</p>
|
||||
<?php elseif ($shipment['status'] === 'posted'): ?>
|
||||
<div class="text-center py-5">
|
||||
<i class="bi bi-hourglass-split fs-1 text-muted mb-3 d-block"></i>
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/includes/layout.php';
|
||||
require_once __DIR__ . '/includes/NotificationService.php';
|
||||
|
||||
ensure_schema();
|
||||
|
||||
@ -40,11 +41,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'creat
|
||||
}
|
||||
|
||||
if (!$errors) {
|
||||
$shipperId = isset($_SESSION['user_id']) ? (int)$_SESSION['user_id'] : null;
|
||||
|
||||
$stmt = db()->prepare(
|
||||
"INSERT INTO shipments (shipper_name, shipper_company, origin_city, destination_city, cargo_description, weight_tons, pickup_date, delivery_date, payment_method)
|
||||
VALUES (:shipper_name, :shipper_company, :origin_city, :destination_city, :cargo_description, :weight_tons, :pickup_date, :delivery_date, :payment_method)"
|
||||
"INSERT INTO shipments (shipper_id, shipper_name, shipper_company, origin_city, destination_city, cargo_description, weight_tons, pickup_date, delivery_date, payment_method)
|
||||
VALUES (:shipper_id, :shipper_name, :shipper_company, :origin_city, :destination_city, :cargo_description, :weight_tons, :pickup_date, :delivery_date, :payment_method)"
|
||||
);
|
||||
$stmt->execute([
|
||||
':shipper_id' => $shipperId,
|
||||
':shipper_name' => $shipperName,
|
||||
':shipper_company' => $shipperCompany,
|
||||
':origin_city' => $origin,
|
||||
@ -55,8 +59,23 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'creat
|
||||
':delivery_date' => $deliveryDate,
|
||||
':payment_method' => $payment,
|
||||
]);
|
||||
|
||||
$newShipmentId = db()->lastInsertId();
|
||||
$_SESSION['shipper_name'] = $shipperName;
|
||||
$_SESSION['shipper_company_session'] = $shipperCompany; // for rudimentary filtering
|
||||
|
||||
// Notify Shipper (Confirmation)
|
||||
if ($shipperId) {
|
||||
$user = db()->query("SELECT * FROM users WHERE id = $shipperId")->fetch();
|
||||
if ($user) {
|
||||
NotificationService::send('shipment_created', $user, [
|
||||
'shipment_id' => $newShipmentId,
|
||||
'origin' => $origin,
|
||||
'destination' => $destination
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
set_flash('success', t('success_shipment'));
|
||||
header('Location: ' . url_with_lang('shipper_dashboard.php'));
|
||||
exit;
|
||||
|
||||
BIN
uploads/pages/img_69ad2b68c8965.jfif
Normal file
BIN
uploads/pages/img_69ad2b68c8965.jfif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.2 KiB |
BIN
uploads/pages/img_69ad2b9960534.jfif
Normal file
BIN
uploads/pages/img_69ad2b9960534.jfif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.6 KiB |
BIN
uploads/pages/img_69ad2bff407d4.jpg
Normal file
BIN
uploads/pages/img_69ad2bff407d4.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 115 KiB |
BIN
uploads/profiles/profile_7_1772962468.png
Normal file
BIN
uploads/profiles/profile_7_1772962468.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 542 B |
Loading…
x
Reference in New Issue
Block a user