Autosave: 20260407-133146
This commit is contained in:
parent
be93531517
commit
4418e1cdac
@ -318,7 +318,9 @@ render_head(
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?php elseif ($page === 'courses'): ?>
|
||||
<?php elseif ($page === 'plans'): ?>
|
||||
<?php require_once __DIR__ . '/admin_plans.php'; ?>
|
||||
<?php elseif ($page === 'courses'): ?>
|
||||
<?php require_once __DIR__ . '/admin_courses.php'; ?>
|
||||
<?php elseif ($page === 'classes'): ?>
|
||||
<?php require_once __DIR__ . '/admin_classes.php'; ?>
|
||||
|
||||
@ -185,7 +185,7 @@ $items = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-light text-dark border"><?= h(number_format($row['price'] ?? 0, 2)) ?></span>
|
||||
<span class="badge bg-light text-dark border"><?= h(format_price((float)($row['price'] ?? 0))) ?></span>
|
||||
</td>
|
||||
<td>
|
||||
<?php if (($row['status'] ?? 'active') === 'active'): ?>
|
||||
@ -303,7 +303,7 @@ $items = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label"><?= h(t('Price', 'السعر')) ?></label>
|
||||
<input type="number" step="0.01" name="price" class="form-control" value="<?= h($row['price'] ?? 0) ?>">
|
||||
<input type="number" step="0.001" name="price" class="form-control" value="<?= h($row['price'] ?? 0) ?>">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label"><?= h(t('Max Students', 'الحد الأقصى للطلاب')) ?></label>
|
||||
@ -392,7 +392,7 @@ $items = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label"><?= h(t('Price', 'السعر')) ?></label>
|
||||
<input type="number" step="0.01" name="price" class="form-control" value="0.00">
|
||||
<input type="number" step="0.001" name="price" class="form-control" value="0.000">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label"><?= h(t('Max Students', 'الحد الأقصى للطلاب')) ?></label>
|
||||
|
||||
267
admin_plans.php
Normal file
267
admin_plans.php
Normal file
@ -0,0 +1,267 @@
|
||||
<?php
|
||||
// admin_plans.php
|
||||
require_once __DIR__ . '/includes/app.php';
|
||||
|
||||
$action = $_GET['action'] ?? 'list';
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$post_action = $_POST['action'] ?? $action;
|
||||
$post_id = (int)($_POST['id'] ?? $id);
|
||||
|
||||
if ($post_action === 'delete' && $post_id > 0) {
|
||||
$stmt = db()->prepare("DELETE FROM plans WHERE id = ?");
|
||||
$stmt->execute([$post_id]);
|
||||
header('Location: ' . app_url('admin.php', ['page' => 'plans']));
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($post_action === 'edit' || $post_action === 'add') {
|
||||
$plan_key = $_POST['plan_key'] ?? '';
|
||||
$name_en = $_POST['name_en'] ?? '';
|
||||
$name_ar = $_POST['name_ar'] ?? '';
|
||||
$price_monthly = (float)($_POST['price_monthly'] ?? 0);
|
||||
$price_yearly = (float)($_POST['price_yearly'] ?? 0);
|
||||
$subjects_limit = (int)($_POST['subjects_limit'] ?? 1);
|
||||
|
||||
$features_en_raw = $_POST['features_en'] ?? '';
|
||||
$features_ar_raw = $_POST['features_ar'] ?? '';
|
||||
|
||||
// Convert multiline text into JSON array
|
||||
$features_en = array_values(array_filter(array_map('trim', explode("\n", $features_en_raw))));
|
||||
$features_ar = array_values(array_filter(array_map('trim', explode("\n", $features_ar_raw))));
|
||||
|
||||
$features_en_json = json_encode($features_en, JSON_UNESCAPED_UNICODE);
|
||||
$features_ar_json = json_encode($features_ar, JSON_UNESCAPED_UNICODE);
|
||||
|
||||
if ($post_action === 'edit' && $post_id > 0) {
|
||||
$stmt = db()->prepare("UPDATE plans SET plan_key=?, name_en=?, name_ar=?, price_monthly=?, price_yearly=?, subjects_limit=?, features_en=?, features_ar=? WHERE id=?");
|
||||
$stmt->execute([$plan_key, $name_en, $name_ar, $price_monthly, $price_yearly, $subjects_limit, $features_en_json, $features_ar_json, $post_id]);
|
||||
} else {
|
||||
$stmt = db()->prepare("INSERT INTO plans (plan_key, name_en, name_ar, price_monthly, price_yearly, subjects_limit, features_en, features_ar) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$plan_key, $name_en, $name_ar, $price_monthly, $price_yearly, $subjects_limit, $features_en_json, $features_ar_json]);
|
||||
}
|
||||
header('Location: ' . app_url('admin.php', ['page' => 'plans']));
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// list view
|
||||
$search = $_GET['search'] ?? '';
|
||||
$page_num = max(1, (int)($_GET['p'] ?? 1));
|
||||
$limit = 10;
|
||||
$offset = ($page_num - 1) * $limit;
|
||||
|
||||
$where = "";
|
||||
$params = [];
|
||||
if ($search !== '') {
|
||||
$where = "WHERE name_en LIKE ? OR name_ar LIKE ? OR plan_key LIKE ?";
|
||||
$params[] = "%$search%";
|
||||
$params[] = "%$search%";
|
||||
$params[] = "%$search%";
|
||||
}
|
||||
|
||||
$total_stmt = db()->prepare("SELECT COUNT(*) FROM plans $where");
|
||||
$total_stmt->execute($params);
|
||||
$total = $total_stmt->fetchColumn();
|
||||
$pages = ceil($total / $limit);
|
||||
|
||||
$stmt = db()->prepare("SELECT * FROM plans $where ORDER BY id DESC LIMIT $limit OFFSET $offset");
|
||||
$stmt->execute($params);
|
||||
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
?>
|
||||
<div class="section-header mb-4 d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h1 class="section-title mb-2"><?= h(t('Plans', 'الخطط')) ?></h1>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2 justify-content-end">
|
||||
<button type="button" class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#addPlanModal" style="background-color: var(--accent); border-color: var(--accent);">+ <?= h(t('Add Plan', 'إضافة خطة')) ?></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-card mb-4">
|
||||
<form method="get" class="d-flex gap-2 align-items-center">
|
||||
<input type="hidden" name="page" value="plans">
|
||||
<input type="text" name="search" class="form-control w-auto" placeholder="<?= h(t('Search...', 'بحث...')) ?>" value="<?= h($search) ?>">
|
||||
<button type="submit" class="btn btn-outline-secondary"><?= h(t('Filter', 'تصفية')) ?></button>
|
||||
<?php if ($search): ?>
|
||||
<a href="<?= h(app_url('admin.php', ['page'=>'plans'])) ?>" class="btn btn-link text-secondary text-decoration-none"><?= h(t('Clear', 'مسح')) ?></a>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="panel-card">
|
||||
<div class="table-responsive">
|
||||
<table class="table align-middle dashboard-table mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th><?= h(t('Key', 'المفتاح')) ?></th>
|
||||
<th><?= h(t('Name', 'الاسم')) ?></th>
|
||||
<th><?= h(t('Monthly', 'شهري')) ?></th>
|
||||
<th><?= h(t('Yearly', 'سنوي')) ?></th>
|
||||
<th><?= h(t('Subjects', 'المواد')) ?></th>
|
||||
<th class="text-end"><?= h(t('Actions', 'إجراءات')) ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach($items as $row):
|
||||
$f_en = json_decode($row['features_en'] ?: '[]', true);
|
||||
$f_ar = json_decode($row['features_ar'] ?: '[]', true);
|
||||
$f_en_text = is_array($f_en) ? implode("\n", $f_en) : '';
|
||||
$f_ar_text = is_array($f_ar) ? implode("\n", $f_ar) : '';
|
||||
?>
|
||||
<tr>
|
||||
<td><?= h((string)$row['id']) ?></td>
|
||||
<td><span class="badge bg-light text-dark border"><?= h($row['plan_key']) ?></span></td>
|
||||
<td>
|
||||
<div class="fw-semibold"><?= h(current_lang() === 'ar' ? $row['name_ar'] : $row['name_en']) ?></div>
|
||||
</td>
|
||||
<td><?= h(format_price((float)$row['price_monthly'])) ?></td>
|
||||
<td><?= h(format_price((float)$row['price_yearly'])) ?></td>
|
||||
<td><?= h((string)$row['subjects_limit']) ?></td>
|
||||
<td class="text-end">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" data-bs-toggle="modal" data-bs-target="#editPlanModal<?= $row['id'] ?>" title="<?= h(t('Edit', 'تعديل')) ?>">
|
||||
<svg width="16" height="16" fill="currentColor" 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-.168l10-10zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.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.293l6.5-6.5zm-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-.325z"/></svg>
|
||||
</button>
|
||||
<form method="post" action="<?= h(app_url('admin.php', ['page'=>'plans', 'action'=>'delete', 'id'=>$row['id']])) ?>" class="d-inline" onsubmit="return confirm('<?= h(t('Are you sure?', 'هل أنت متأكد؟')) ?>');">
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger" title="<?= h(t('Delete', 'حذف')) ?>"><svg width="16" height="16" fill="currentColor" 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-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/><path fill-rule="evenodd" 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 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/></svg></button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Edit Modal for Row <?= $row['id'] ?> -->
|
||||
<div class="modal fade" id="editPlanModal<?= $row['id'] ?>" tabindex="-1" aria-labelledby="editPlanModalLabel<?= $row['id'] ?>" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||
<div class="modal-content border-0 shadow">
|
||||
<div class="modal-header border-0 bg-dark-blue">
|
||||
<h5 class="modal-title section-title" id="editPlanModalLabel<?= $row['id'] ?>"><?= h(t('Edit Plan', 'تعديل الخطة')) ?></h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form method="post" action="<?= h(app_url('admin.php', ['page'=>'plans'])) ?>">
|
||||
<input type="hidden" name="action" value="edit">
|
||||
<input type="hidden" name="id" value="<?= $row['id'] ?>">
|
||||
<div class="row">
|
||||
<div class="col-md-12 mb-3">
|
||||
<label class="form-label"><?= h(t('Plan Key (Unique identifier e.g. plus, basic)', 'مفتاح الخطة (معرف فريد مثل plus, basic)')) ?></label>
|
||||
<input type="text" name="plan_key" class="form-control" value="<?= h($row['plan_key']) ?>" required>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Name (EN)</label>
|
||||
<input type="text" name="name_en" class="form-control" value="<?= h($row['name_en']) ?>" required>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Name (AR)</label>
|
||||
<input type="text" name="name_ar" class="form-control" value="<?= h($row['name_ar']) ?>" required>
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label"><?= h(t('Monthly Price', 'السعر الشهري')) ?></label>
|
||||
<input type="number" step="0.001" name="price_monthly" class="form-control" value="<?= h($row['price_monthly']) ?>">
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label"><?= h(t('Yearly Price', 'السعر السنوي')) ?></label>
|
||||
<input type="number" step="0.001" name="price_yearly" class="form-control" value="<?= h($row['price_yearly']) ?>">
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label"><?= h(t('Subjects Limit', 'حد المواد')) ?></label>
|
||||
<input type="number" name="subjects_limit" class="form-control" value="<?= h($row['subjects_limit']) ?>">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label"><?= h(t('Features (EN) - One per line', 'الميزات (EN) - ميزة في كل سطر')) ?></label>
|
||||
<textarea name="features_en" class="form-control" rows="5"><?= h($f_en_text) ?></textarea>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label"><?= h(t('Features (AR) - One per line', 'الميزات (AR) - ميزة في كل سطر')) ?></label>
|
||||
<textarea name="features_ar" class="form-control" rows="5"><?= h($f_ar_text) ?></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end gap-2 mt-4">
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal"><?= h(t('Cancel', 'إلغاء')) ?></button>
|
||||
<button type="submit" class="btn btn-primary" style="background-color: var(--accent); border-color: var(--accent);"><?= h(t('Save Changes', 'حفظ التغييرات')) ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End Edit Modal -->
|
||||
|
||||
<?php endforeach; ?>
|
||||
<?php if(!$items): ?>
|
||||
<tr><td colspan="7" class="text-center text-secondary py-3"><?= h(t('No plans found.', 'لا توجد خطط.')) ?></td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($pages > 1): ?>
|
||||
<nav class="mt-4">
|
||||
<ul class="pagination justify-content-center">
|
||||
<?php for($i=1; $i<=$pages; $i++):
|
||||
$active_class = ($i === $page_num) ? ' active' : '';
|
||||
?>
|
||||
<li class="page-item<?= $active_class ?>">
|
||||
<a class="page-link" href="<?= h(app_url('admin.php', ['page'=>'plans', 'p'=>$i, 'search'=>$search])) ?>"><?= $i ?></a>
|
||||
</li>
|
||||
<?php endfor; ?>
|
||||
</ul>
|
||||
</nav>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Add Plan Modal -->
|
||||
<div class="modal fade" id="addPlanModal" tabindex="-1" aria-labelledby="addPlanModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||
<div class="modal-content border-0 shadow">
|
||||
<div class="modal-header border-0 bg-dark-blue">
|
||||
<h5 class="modal-title section-title" id="addPlanModalLabel"><?= h(t('Add Plan', 'إضافة خطة')) ?></h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form method="post" action="<?= h(app_url('admin.php', ['page'=>'plans'])) ?>">
|
||||
<input type="hidden" name="action" value="add">
|
||||
<div class="row">
|
||||
<div class="col-md-12 mb-3">
|
||||
<label class="form-label"><?= h(t('Plan Key (Unique identifier e.g. plus, basic)', 'مفتاح الخطة (معرف فريد مثل plus, basic)')) ?></label>
|
||||
<input type="text" name="plan_key" class="form-control" required>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Name (EN)</label>
|
||||
<input type="text" name="name_en" class="form-control" required>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Name (AR)</label>
|
||||
<input type="text" name="name_ar" class="form-control" required>
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label"><?= h(t('Monthly Price', 'السعر الشهري')) ?></label>
|
||||
<input type="number" step="0.001" name="price_monthly" class="form-control" value="0.000">
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label"><?= h(t('Yearly Price', 'السعر السنوي')) ?></label>
|
||||
<input type="number" step="0.001" name="price_yearly" class="form-control" value="0.000">
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label"><?= h(t('Subjects Limit', 'حد المواد')) ?></label>
|
||||
<input type="number" name="subjects_limit" class="form-control" value="1">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label"><?= h(t('Features (EN) - One per line', 'الميزات (EN) - ميزة في كل سطر')) ?></label>
|
||||
<textarea name="features_en" class="form-control" rows="5"></textarea>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label"><?= h(t('Features (AR) - One per line', 'الميزات (AR) - ميزة في كل سطر')) ?></label>
|
||||
<textarea name="features_ar" class="form-control" rows="5"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end gap-2 mt-4">
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal"><?= h(t('Cancel', 'إلغاء')) ?></button>
|
||||
<button type="submit" class="btn btn-primary" style="background-color: var(--accent); border-color: var(--accent);"><?= h(t('Save', 'حفظ')) ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End Add Modal -->
|
||||
@ -265,7 +265,7 @@ render_nav('pricing.php');
|
||||
</strong></div>
|
||||
<div class="summary-row"><span><?= h(t('Price', 'السعر')) ?></span><strong data-cycle-price>
|
||||
<?php if ($course): ?>
|
||||
<?= h('$' . number_format($course['price'], 2)) ?>
|
||||
<?= h(format_price((float)$course['price'])) ?>
|
||||
<?php else: ?>
|
||||
<?= h(price_label($plan, $cycle)) ?>
|
||||
<?php endif; ?>
|
||||
|
||||
@ -434,11 +434,16 @@ function subject_modules(array $subject): array
|
||||
return current_lang() === 'ar' ? $subject['modules_ar'] : $subject['modules_en'];
|
||||
}
|
||||
|
||||
function format_price(float $amount): string
|
||||
{
|
||||
return number_format($amount, 3) . ' ' . t('OMR', 'ر.ع.');
|
||||
}
|
||||
|
||||
function price_label(array $plan, string $cycle = 'monthly'): string
|
||||
{
|
||||
$amount = $cycle === 'yearly' ? $plan['price_yearly'] : $plan['price_monthly'];
|
||||
$suffix = $cycle === 'yearly' ? t('/year', '/سنة') : t('/month', '/شهر');
|
||||
return number_format((float)$amount, 3) . ' ' . t('OMR', 'ر.ع.') . $suffix;
|
||||
return format_price((float)$amount) . ' ' . $suffix;
|
||||
}
|
||||
|
||||
function ensure_subscription_table(): void
|
||||
|
||||
32
index.php
32
index.php
@ -39,36 +39,6 @@ $metrics = ['subjects' => count($subjects), 'teachers' => db()->query("SELECT CO
|
||||
<?php if ($hero_img = landing_setting('hero_image', '')): ?>
|
||||
<img src="<?= h($hero_img) ?>" class="img-fluid rounded shadow-lg w-100" style="object-fit: cover; max-height: 500px;" alt="Hero Image">
|
||||
<?php else: ?>
|
||||
<div class="panel-card">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h2 class="h5 mb-0"><?= h(t('Today inside the platform', 'اليوم داخل المنصة')) ?></h2>
|
||||
<span class="badge bg-dark-subtle text-dark-emphasis border"><?= h(t('Live ready', 'جاهز للبث')) ?></span>
|
||||
</div>
|
||||
<div class="stack-list">
|
||||
<div class="stack-item">
|
||||
<div>
|
||||
<strong><?= h(t('Student subscription', 'اشتراك الطالب')) ?></strong>
|
||||
<p><?= h(t('Choose a plan, confirm payment intent, and unlock classrooms.', 'اختر خطة وأكد نية الدفع وافتح الفصول.')) ?></p>
|
||||
</div>
|
||||
<span class="mini-tag">Thawani</span>
|
||||
</div>
|
||||
<div class="stack-item">
|
||||
<div>
|
||||
<strong><?= h(t('Live lessons', 'الدروس المباشرة')) ?></strong>
|
||||
<p><?= h(t('Weekly Google Meet rooms with schedules shown inside every subject.', 'غرف Google Meet أسبوعية مع الجداول داخل كل مادة.')) ?></p>
|
||||
</div>
|
||||
<span class="mini-tag">Meet</span>
|
||||
</div>
|
||||
<div class="stack-item">
|
||||
<div>
|
||||
<strong><?= h(t('WhatsApp reminders', 'تذكيرات واتساب')) ?></strong>
|
||||
<p><?= h(t('Opt-in reminders for payment confirmations and class updates.', 'تذكيرات اختيارية لتأكيد الدفع وتحديثات الحصص.')) ?></p>
|
||||
</div>
|
||||
<span class="mini-tag">Wablas</span>
|
||||
</div>
|
||||
</div>
|
||||
<a class="btn btn-outline-dark w-100 mt-3" href="<?= h(app_url('dashboard.php')) ?>"><?= h(t('Open student dashboard', 'افتح لوحة الطالب')) ?></a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
@ -108,7 +78,7 @@ $metrics = ['subjects' => count($subjects), 'teachers' => db()->query("SELECT CO
|
||||
<div class="card-body d-flex flex-column p-4">
|
||||
<div class="d-flex justify-content-between align-items-start mb-3">
|
||||
<h3 class="h5 mb-0 fw-bold"><?= h(current_lang() === 'ar' ? $course['name_ar'] : $course['name_en']) ?></h3>
|
||||
<span class="badge bg-primary text-white rounded-pill fs-6 px-3 py-2"><?= h('$' . number_format($course['price'], 2)) ?></span>
|
||||
<span class="badge bg-primary text-white rounded-pill fs-6 px-3 py-2"><?= h(format_price((float)$course['price'])) ?></span>
|
||||
</div>
|
||||
<p class="text-secondary mb-4 flex-grow-1"><?= h(current_lang() === 'ar' ? $course['description_ar'] : $course['description_en']) ?></p>
|
||||
<div class="d-grid mt-auto">
|
||||
|
||||
@ -38,6 +38,15 @@ render_nav('login.php');
|
||||
<div class="col-md-5 col-lg-4">
|
||||
<div class="card border-0 shadow-sm" style="border-radius: 1rem;">
|
||||
<div class="card-body p-4 p-md-5">
|
||||
<div class="text-center mb-4">
|
||||
<?php $prof = get_platform_profile(); if (!empty($prof['logo_path'])): ?>
|
||||
<img src="<?= h(asset_url($prof['logo_path'])) ?>" alt="Logo" style="max-height: 64px; object-fit: contain;">
|
||||
<?php else: ?>
|
||||
<div class="d-inline-flex align-items-center justify-content-center bg-dark text-white rounded-circle shadow-sm" style="width: 64px; height: 64px; font-size: 1.5rem; font-weight: bold;">
|
||||
<?= h(mb_strtoupper(mb_substr(app_name(), 0, 1))) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<h1 class="h4 mb-4 text-center fw-bold"><?= h(t('Welcome back', 'مرحباً بعودتك')) ?></h1>
|
||||
<?php if ($error): ?>
|
||||
<div class="alert alert-danger py-2 small"><?= h($error) ?></div>
|
||||
|
||||
15
patch_index2.py
Normal file
15
patch_index2.py
Normal file
@ -0,0 +1,15 @@
|
||||
import re
|
||||
|
||||
with open("index.php", "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
# Pattern to remove the entire panel-card block
|
||||
pattern = r'<div class="panel-card">[\s\S]*?</div>\s*<\?php endif; \?>'
|
||||
replacement = '<?php endif; ?>'
|
||||
|
||||
new_content = re.sub(pattern, replacement, content)
|
||||
|
||||
with open("index.php", "w", encoding="utf-8") as f:
|
||||
f.write(new_content)
|
||||
|
||||
print("done")
|
||||
69
profile.php
69
profile.php
@ -14,14 +14,38 @@ $error = '';
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$name = trim($_POST['name'] ?? '');
|
||||
$email = trim($_POST['email'] ?? '');
|
||||
$phone = trim($_POST['phone'] ?? '');
|
||||
$password = $_POST['password'] ?? '';
|
||||
$password_confirm = $_POST['password_confirm'] ?? '';
|
||||
|
||||
$profile_picture = $user['profile_picture'] ?? null;
|
||||
|
||||
// Handle image upload
|
||||
if (isset($_FILES['profile_picture']) && $_FILES['profile_picture']['error'] === UPLOAD_ERR_OK) {
|
||||
$allowed_types = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'];
|
||||
if (in_array($_FILES['profile_picture']['type'], $allowed_types)) {
|
||||
$ext = pathinfo($_FILES['profile_picture']['name'], PATHINFO_EXTENSION);
|
||||
$filename = 'user_' . $user['id'] . '_' . time() . '.' . $ext;
|
||||
$upload_dir = __DIR__ . '/assets/images/uploads/';
|
||||
if (!is_dir($upload_dir)) {
|
||||
mkdir($upload_dir, 0775, true);
|
||||
}
|
||||
$dest = $upload_dir . $filename;
|
||||
if (move_uploaded_file($_FILES['profile_picture']['tmp_name'], $dest)) {
|
||||
$profile_picture = 'assets/images/uploads/' . $filename;
|
||||
} else {
|
||||
$error = t('Failed to move uploaded file.', 'فشل نقل الملف المرفوع.');
|
||||
}
|
||||
} else {
|
||||
$error = t('Invalid image format. Only JPG, PNG, WEBP, and GIF are allowed.', 'تنسيق الصورة غير صالح. يُسمح فقط بـ JPG و PNG و WEBP و GIF.');
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($name) || empty($email)) {
|
||||
$error = t('Name and email are required.', 'الاسم والبريد الإلكتروني مطلوبان.');
|
||||
if (!$error) $error = t('Name and email are required.', 'الاسم والبريد الإلكتروني مطلوبان.');
|
||||
} elseif ($password !== $password_confirm) {
|
||||
$error = t('Passwords do not match.', 'كلمتا المرور غير متطابقتين.');
|
||||
} else {
|
||||
if (!$error) $error = t('Passwords do not match.', 'كلمتا المرور غير متطابقتين.');
|
||||
} elseif (!$error) {
|
||||
$stmt = db()->prepare("SELECT id FROM users WHERE email = ? AND id != ?");
|
||||
$stmt->execute([$email, $user['id']]);
|
||||
if ($stmt->fetchColumn()) {
|
||||
@ -29,11 +53,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
} else {
|
||||
if ($password) {
|
||||
$hash = password_hash($password, PASSWORD_DEFAULT);
|
||||
$update = db()->prepare("UPDATE users SET name = ?, email = ?, password = ? WHERE id = ?");
|
||||
$update->execute([$name, $email, $hash, $user['id']]);
|
||||
$update = db()->prepare("UPDATE users SET name = ?, email = ?, phone = ?, profile_picture = ?, password = ? WHERE id = ?");
|
||||
$update->execute([$name, $email, $phone, $profile_picture, $hash, $user['id']]);
|
||||
} else {
|
||||
$update = db()->prepare("UPDATE users SET name = ?, email = ? WHERE id = ?");
|
||||
$update->execute([$name, $email, $user['id']]);
|
||||
$update = db()->prepare("UPDATE users SET name = ?, email = ?, phone = ?, profile_picture = ? WHERE id = ?");
|
||||
$update->execute([$name, $email, $phone, $profile_picture, $user['id']]);
|
||||
}
|
||||
$success = t('Profile updated successfully.', 'تم تحديث الملف الشخصي بنجاح.');
|
||||
$user = get_logged_in_user(); // Refresh
|
||||
@ -50,22 +74,45 @@ render_nav('profile.php');
|
||||
<div class="col-md-8 col-lg-6">
|
||||
<div class="card border-0 shadow-sm" style="border-radius: 1rem;">
|
||||
<div class="card-body p-4 p-md-5">
|
||||
<h1 class="h4 mb-4 fw-bold"><?= h(t('My Profile', 'الملف الشخصي')) ?></h1>
|
||||
|
||||
<div class="d-flex align-items-center justify-content-between mb-4">
|
||||
<h1 class="h4 mb-0 fw-bold"><?= h(t('My Profile', 'الملف الشخصي')) ?></h1>
|
||||
<?php if (!empty($user['profile_picture'])): ?>
|
||||
<img src="<?= h($user['profile_picture']) ?>?v=<?= time() ?>" alt="Profile Picture" class="rounded-circle shadow-sm" style="width: 60px; height: 60px; object-fit: cover;">
|
||||
<?php else: ?>
|
||||
<div class="rounded-circle shadow-sm bg-secondary text-white d-flex align-items-center justify-content-center" style="width: 60px; height: 60px;">
|
||||
<?= h(strtoupper(mb_substr($user['name'] ?? '?', 0, 1, 'UTF-8'))) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($success): ?>
|
||||
<div class="alert alert-success py-2 small"><?= h($success) ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($error): ?>
|
||||
<div class="alert alert-danger py-2 small"><?= h($error) ?></div>
|
||||
<?php endif; ?>
|
||||
<form method="post" action="profile.php">
|
||||
<form method="post" action="profile.php" enctype="multipart/form-data">
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-semibold"><?= h(t('Profile Picture', 'صورة الملف الشخصي')) ?></label>
|
||||
<input type="file" name="profile_picture" class="form-control" accept="image/*">
|
||||
<div class="form-text small"><?= h(t('Leave blank to keep current picture.', 'اتركه فارغاً للاحتفاظ بالصورة الحالية.')) ?></div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-semibold"><?= h(t('Full Name', 'الاسم الكامل')) ?></label>
|
||||
<input type="text" name="name" class="form-control" value="<?= h($user['name']) ?>" required>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-semibold"><?= h(t('Email address', 'البريد الإلكتروني')) ?></label>
|
||||
<input type="email" name="email" class="form-control" value="<?= h($user['email']) ?>" required>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="form-label small fw-semibold"><?= h(t('Telephone Number', 'رقم الهاتف')) ?></label>
|
||||
<input type="tel" name="phone" class="form-control" value="<?= h($user['phone'] ?? '') ?>">
|
||||
</div>
|
||||
|
||||
<h5 class="h6 mb-3 fw-bold border-top pt-4"><?= h(t('Change Password', 'تغيير كلمة المرور')) ?> <small class="text-secondary fw-normal"><?= h(t('(Optional)', '(اختياري)')) ?></small></h5>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-semibold"><?= h(t('New Password', 'كلمة المرور الجديدة')) ?></label>
|
||||
@ -83,4 +130,4 @@ render_nav('profile.php');
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<?php render_footer(); ?>
|
||||
<?php render_footer(); ?>
|
||||
Loading…
x
Reference in New Issue
Block a user