راتاااااااا

This commit is contained in:
Flatlogic Bot 2025-12-04 21:21:10 +00:00
parent 20dd5c8f61
commit 3f48850ff5
43 changed files with 2838 additions and 1808 deletions

View File

@ -0,0 +1 @@
{"labels":["1404-09"],"data":[2940000]}

3
.gitignore vendored
View File

@ -1,3 +1,6 @@
node_modules/
*/node_modules/
*/build/
# Ignore environment files
.env

View File

@ -3,59 +3,58 @@ $page_title = 'درباره ما';
require_once 'includes/header.php';
?>
<main class="container py-5">
<div class="text-center mb-5" data-aos="fade-down">
<h1 class="display-4 fw-bold">داستان آتیمه</h1>
<div class="container py-5 my-5">
<div class="section-title text-center mb-5" data-aos="fade-down">
<h1>داستان آتیمه</h1>
<p class="fs-5 text-muted">تلفیق هنر سنتی و طراحی مدرن</p>
</div>
<div class="row justify-content-center">
<div class="col-lg-10">
<div class="card bg-dark-2 border-0 shadow-lg rounded-4 overflow-hidden">
<div class="row g-0">
<div class="col-lg-6" data-aos="fade-right">
<img src="assets/images/pexels/about-us-34942790.jpg" class="img-fluid h-100" alt="هنر چرم‌دوزی" style="object-fit: cover; min-height: 400px;">
</div>
<div class="col-lg-6 d-flex align-items-center" data-aos="fade-left">
<div class="card-body p-4 p-md-5">
<h2 class="card-title fw-bold mb-3">باور ما</h2>
<p class="fs-5 lh-lg">ما در آتیمه، به قدرت دست‌ها و اصالت مواد اولیه باور داریم. داستان ما از یک کارگاه کوچک و عشقی عمیق به هنر چرم‌دوزی آغاز شد. هدف ما خلق آثاری است که نه تنها یک وسیله کاربردی، بلکه بخشی از داستان و استایل روزمره شما باشند؛ آثاری که با گذر زمان، زیباتر و شخصی‌تر می‌شوند.</p>
<p class="fs-5 lh-lg mt-3">هر محصول، حاصل ساعت‌ها کار دست هنرمندان ماهر و استفاده از بهترین و باکیفیت‌ترین چرم‌های طبیعی است. ما به جزئیات اهمیت می‌دهیم، از انتخاب نخ گرفته تا طراحی هر برش و دوخت. این تعهد به کیفیت، تضمین می‌کند که هر ساخته‌ دست ما، اثری ماندگار و بی‌همتا باشد.</p>
<a href="shop.php" class="btn btn-primary mt-4">مشاهده مجموعه ما</a>
</div>
</div>
<div class="about-card p-4 p-lg-5 mb-5" data-aos="fade-up">
<div class="row g-0 align-items-center">
<div class="col-lg-6" data-aos="fade-right" data-aos-delay="100">
<img src="assets/images/pexels/about-us-34942790.jpg" class="img-fluid about-image" alt="هنر چرم‌دوزی">
</div>
<div class="col-lg-6" data-aos="fade-left" data-aos-delay="200">
<div class="card-body p-4 p-md-5">
<h2 class="fw-bold mb-4">باور ما</h2>
<p class="lh-lg">ما در آتیمه، به قدرت دست‌ها و اصالت مواد اولیه باور داریم. داستان ما از یک کارگاه کوچک و عشقی عمیق به هنر چرم‌دوزی آغاز شد. هدف ما خلق آثاری است که نه تنها یک وسیله کاربردی، بلکه بخشی از داستان و استایل روزمره شما باشند؛ آثاری که با گذر زمان، زیباتر و شخصی‌تر می‌شوند.</p>
<p class="lh-lg mt-3">هر محصول، حاصل ساعت‌ها کار دست هنرمندان ماهر و استفاده از بهترین و باکیفیت‌ترین چرم‌های طبیعی است. ما به جزئیات اهمیت می‌دهیم، از انتخاب نخ گرفته تا طراحی هر برش و دوخت. این تعهد به کیفیت، تضمین می‌کند که هر ساخته‌ دست ما، اثری ماندگار و بی‌همتا باشد.</p>
<a href="shop.php" class="btn btn-primary mt-4">مجموعه ما را ببینید</a>
</div>
</div>
</div>
</div>
<!-- Team Section or Values -->
<section class="py-5 mt-4">
<div class="row text-center g-4">
<section class="py-5">
<div class="text-center mb-5" data-aos="fade-down">
<h2 class="fw-bold">ارزش‌های ما</h2>
</div>
<div class="row text-center g-4 justify-content-center">
<div class="col-md-4" data-aos="fade-up" data-aos-delay="100">
<div class="card bg-dark-2 border-secondary h-100 p-4 rounded-3">
<i class="fas fa-gem fa-3x text-primary mb-3"></i>
<div class="values-card h-100">
<i class="fas fa-gem mb-3"></i>
<h4 class="fw-bold">تعهد به کیفیت</h4>
<p class="text-muted">استفاده از بهترین مواد اولیه و کنترل کیفی دقیق در تمام مراحل تولید.</p>
<p class="text-muted px-3">استفاده از بهترین مواد اولیه و کنترل کیفی دقیق در تمام مراحل تولید.</p>
</div>
</div>
<div class="col-md-4" data-aos="fade-up" data-aos-delay="200">
<div class="card bg-dark-2 border-secondary h-100 p-4 rounded-3">
<i class="fas fa-hand-holding-heart fa-3x text-primary mb-3"></i>
<div class="values-card h-100">
<i class="fas fa-hand-holding-heart mb-3"></i>
<h4 class="fw-bold">هنر دست</h4>
<p class="text-muted">تمام محصولات ما با عشق و دقت توسط هنرمندان ماهر ساخته می‌شوند.</p>
<p class="text-muted px-3">تمام محصولات ما با عشق و دقت توسط هنرمندان ماهر ساخته می‌شوند.</p>
</div>
</div>
<div class="col-md-4" data-aos="fade-up" data-aos-delay="300">
<div class="card bg-dark-2 border-secondary h-100 p-4 rounded-3">
<i class="fas fa-leaf fa-3x text-primary mb-3"></i>
<div class="values-card h-100">
<i class="fas fa-leaf mb-3"></i>
<h4 class="fw-bold">طراحی ماندگار</h4>
<p class="text-muted">خلق آثاری مدرن و در عین حال کلاسیک که هیچ‌گاه از مد نمی‌افتند.</p>
<p class="text-muted px-3">خلق آثاری مدرن و در عین حال کلاسیک که هیچ‌گاه از مد نمی‌افتند.</p>
</div>
</div>
</div>
</section>
</main>
</div>
<?php require_once 'includes/footer.php'; ?>

View File

@ -1,8 +1,6 @@
<?php
session_start();
require_once __DIR__ . '/auth_check.php';
// New header
require_once __DIR__ . '/header.php';
$flash_message = $_SESSION['flash_message'] ?? null;
@ -11,69 +9,82 @@ if ($flash_message) {
}
?>
<div class="row justify-content-center">
<div class="col-lg-8">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h2">افزودن محصول جدید</h1>
<a href="products.php" class="btn btn-secondary">انصراف</a>
</div>
<style>
.form-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1.5rem;
}
</style>
<div class="card shadow-sm" style="background-color: var(--admin-surface); border-color: var(--admin-border);">
<div class="card-body p-4">
<form action="handler.php?action=add" method="post" enctype="multipart/form-data">
<div class="mb-3">
<label for="name" class="form-label">نام محصول</label>
<input type="text" class="form-control" id="name" name="name" required>
</div>
<div class="mb-3">
<label for="description" class="form-label">توضیحات</label>
<textarea class="form-control" id="description" name="description" rows="4" required></textarea>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="price" class="form-label">قیمت (تومان)</label>
<input type="number" class="form-control" id="price" name="price" required>
</div>
<div class="col-md-6 mb-3">
<label for="image" class="form-label">تصویر محصول</label>
<input type="file" class="form-control" id="image" name="image" accept="image/*">
</div>
</div>
<div class="mb-3">
<label for="colors" class="form-label">کدهای رنگ (اختیاری)</label>
<input type="text" class="form-control" id="colors" name="colors" placeholder="مثال: #8B4513, #2C2C2C">
<div class="form-text" style="color: var(--admin-text-secondary);">کدهای رنگ هگزادسیمال را با کاما جدا کنید.</div>
</div>
<div class="mb-4 form-check">
<input type="checkbox" class="form-check-input" id="is_featured" name="is_featured" value="1" style="border-color: var(--admin-border);">
<label class="form-check-label" for="is_featured">این یک محصول ویژه است</label>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary btn-lg">افزودن محصول</button>
</div>
</form>
<div class="admin-header">
<h1>افزودن محصول جدید</h1>
<a href="products.php" class="btn" style="background: var(--admin-border); color: var(--admin-text);">بازگشت</a>
</div>
<div class="card">
<div class="card-body">
<form action="handler.php?action=add" method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="name" class="form-label">نام محصول</label>
<input type="text" class="form-control" id="name" name="name" required>
</div>
</div>
<div class="form-group">
<label for="description" class="form-label">توضیحات</label>
<textarea class="form-control" id="description" name="description" rows="5" required></textarea>
</div>
<div class="form-grid">
<div class="form-group">
<label for="price" class="form-label">قیمت (تومان)</label>
<input type="number" class="form-control" id="price" name="price" required>
</div>
<div class="form-group">
<label for="stock" class="form-label">موجودی</label>
<input type="number" class="form-control" id="stock" name="stock" required value="0">
</div>
</div>
<div class="form-group">
<label for="image" class="form-label">تصویر محصول</label>
<input type="file" class="form-control" id="image" name="image" accept="image/*">
</div>
<div class="form-group">
<label for="colors" class="form-label">کدهای رنگ (اختیاری)</label>
<input type="text" class="form-control" id="colors" name="colors" placeholder="مثال: #8B4513, #2C2C2C">
<small style="color: var(--admin-text-muted);">کدهای رنگ هگزادسیمال را با کاما جدا کنید.</small>
</div>
<div class="form-group">
<label style="display: flex; align-items: center; gap: 0.5rem;">
<input type="checkbox" id="is_featured" name="is_featured" value="1" style="width: 20px; height: 20px;">
<span>این یک محصول ویژه است</span>
</label>
</div>
<div style="text-align: left; margin-top: 2rem;">
<button type="submit" class="btn btn-primary">افزودن محصول</button>
</div>
</form>
</div>
</div>
<!-- Page-specific scripts -->
<script>
document.addEventListener('DOMContentLoaded', function () {
const style = getComputedStyle(document.body);
<?php if ($flash_message): ?>
Swal.fire({
title: '<?php echo $flash_message["type"] === "success" ? "عالی" : "خطا"; ?>',
html: '<?php echo addslashes($flash_message["message"]); ?>',
icon: '<?php echo $flash_message["type"]; ?>',
confirmButtonText: 'باشه',
background: 'var(--admin-surface)',
color: 'var(--admin-text-primary)'
background: style.getPropertyValue('--admin-surface'),
color: style.getPropertyValue('--admin-text')
});
<?php endif; ?>
});
</script>
<?php
// New footer
require_once __DIR__ . '/footer.php';
?>
<?php require_once __DIR__ . '/footer.php'; ?>

136
admin/api.php Normal file
View File

@ -0,0 +1,136 @@
<?php
header('Content-Type: application/json');
require_once __DIR__ . '/../db/config.php';
require_once __DIR__ . '/auth_handler.php';
// Start the session to check for admin status
if (!is_admin()) {
http_response_code(403);
echo json_encode(['error' => 'Unauthorized']);
exit;
}
// IMPORTANT: Close the session immediately after use to prevent locking.
// This allows other concurrent requests from the same user to be processed.
session_write_close();
$action = $_GET['action'] ?? '';
$pdo = db();
if ($action === 'get_sales_data') {
require_once __DIR__ . '/../includes/jdf.php';
$cache_file = __DIR__ . '/cache/sales_chart.json';
$cache_lifetime = 3600; // 1 hour
// Clear PHP's stat cache to ensure we get the most up-to-date file status
clearstatcache();
if (file_exists($cache_file) && is_readable($cache_file) && (time() - filemtime($cache_file) < $cache_lifetime)) {
$cached_data = file_get_contents($cache_file);
// Verify that the cache content is a valid JSON
if ($cached_data && json_decode($cached_data) !== null) {
header('X-Cache: HIT');
echo $cached_data;
exit;
}
}
// CACHE MISS: Regenerate the data
try {
$stmt = $pdo->prepare("
SELECT
YEAR(created_at) as year,
MONTH(created_at) as month,
SUM(total_amount) as total_sales
FROM orders
WHERE status = 'Delivered'
GROUP BY year, month
ORDER BY year ASC, month ASC
");
$stmt->execute();
$sales_data = $stmt->fetchAll(PDO::FETCH_ASSOC);
$labels = [];
$data = [];
foreach ($sales_data as $row) {
$jalali_date = gregorian_to_jalali($row['year'], $row['month'], 1);
$labels[] = $jalali_date[0] . '-' . str_pad($jalali_date[1], 2, '0', STR_PAD_LEFT);
$data[] = (float)$row['total_sales'];
}
$response_data = json_encode(['labels' => $labels, 'data' => $data]);
// Atomic Write Operation
$cache_dir = dirname($cache_file);
if (!is_dir($cache_dir)) {
mkdir($cache_dir, 0755, true);
}
$temp_file = $cache_file . '.' . uniqid() . '.tmp';
if (file_put_contents($temp_file, $response_data) !== false) {
// If rename fails, the old (possibly stale) cache will be used, which is acceptable.
// The temp file will be cleaned up on subsequent runs or by a cron job.
rename($temp_file, $cache_file);
}
header('X-Cache: MISS');
echo $response_data;
} catch (PDOException $e) {
http_response_code(500);
error_log("FATAL: DB Exception during sales data generation: " . $e->getMessage());
echo json_encode(['error' => 'Database error while fetching sales data.']);
}
exit;
}
if ($action === 'get_stats') {
try {
// Optimized: Fetch all stats in a single query
$query = "
SELECT
(SELECT SUM(total_amount) FROM orders WHERE status = 'Delivered') as total_sales,
(SELECT COUNT(*) FROM orders WHERE status = 'Shipped') as shipped_orders,
(SELECT COUNT(*) FROM orders WHERE status = 'Cancelled') as cancelled_orders,
(SELECT COUNT(*) FROM orders WHERE status = 'Processing') as processing_orders,
(SELECT COUNT(*) FROM users) as total_users,
(SELECT COUNT(*) FROM page_views) as total_views,
(SELECT COUNT(*) FROM page_views WHERE YEAR(view_timestamp) = YEAR(CURDATE()) AND MONTH(view_timestamp) = MONTH(CURDATE())) as this_month_views,
(SELECT COUNT(*) FROM page_views WHERE YEAR(view_timestamp) = YEAR(CURDATE() - INTERVAL 1 MONTH) AND MONTH(view_timestamp) = MONTH(CURDATE() - INTERVAL 1 MONTH)) as last_month_views
";
$stmt = $pdo->query($query);
$stats = $stmt->fetch(PDO::FETCH_ASSOC);
$this_month_views = (int)($stats['this_month_views'] ?? 0);
$last_month_views = (int)($stats['last_month_views'] ?? 0);
$percentage_change = 0;
if ($last_month_views > 0) {
$percentage_change = (($this_month_views - $last_month_views) / $last_month_views) * 100;
} elseif ($this_month_views > 0) {
$percentage_change = 100;
}
echo json_encode([
'total_sales' => (float)($stats['total_sales'] ?? 0),
'shipped_orders' => (int)($stats['shipped_orders'] ?? 0),
'cancelled_orders' => (int)($stats['cancelled_orders'] ?? 0),
'processing_orders' => (int)($stats['processing_orders'] ?? 0),
'total_users' => (int)($stats['total_users'] ?? 0),
'total_page_views' => [
'count' => (int)($stats['total_views'] ?? 0),
'percentage_change' => round($percentage_change, 2)
],
]);
} catch (PDOException $e) {
http_response_code(500);
error_log("API Error (get_stats): " . $e->getMessage());
echo json_encode(['error' => 'Database error while fetching stats.']);
}
exit;
}
http_response_code(400);
echo json_encode(['error' => 'Invalid action']);

View File

@ -1,259 +1,348 @@
/* =================================================================
ADMIN PANEL MODERN STYLES
================================================================= */
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;600;700&display=swap');
@import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css');
:root {
--admin-bg: #1A202C; /* Very dark blue */
--admin-surface: #2D3748; /* Lighter dark blue for cards, tables */
--admin-border: #4A5568; /* Subtle borders */
--admin-accent: #FBBF24; /* Amber/Gold for highlights */
--admin-accent-hover: #F59E0B; /* Darker gold for hover */
--admin-text-primary: #EDF2F7; /* Bright, light gray for main text */
--admin-text-secondary: #A0AEC0; /* Softer gray for subtitles */
--admin-success: #38A169; /* Green */
--admin-danger: #E53E3E; /* Red */
--admin-info: #3182CE; /* Blue */
--admin-font: 'Vazirmatn', sans-serif;
--admin-bg: #111214;
--admin-surface: #1a1b1e;
--admin-text: #eceff1;
--admin-text-muted: #90a4ae;
--admin-primary: #c09f80; /* Soft gold from luxury theme */
--admin-border: #37474f;
--admin-success: #4caf50;
--admin-danger: #f44336;
--admin-warning: #ff9800;
--admin-info: #2196f3;
}
/* --- General Body & Typography --- */
body.admin-page {
body.admin-dark-theme {
background-color: var(--admin-bg);
color: var(--admin-text-primary);
font-family: var(--admin-font);
padding-right: 0; /* Reset previous style */
color: var(--admin-text);
font-family: 'Vazirmatn', sans-serif;
line-height: 1.6;
margin: 0;
padding: 0;
display: flex;
min-height: 100vh;
}
.admin-main-content {
padding: 2rem;
margin-right: 280px; /* Space for the new sidebar */
transition: margin-right 0.3s ease;
.admin-wrapper {
display: flex;
width: 100%;
}
h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
color: var(--admin-text-primary);
/* --- Sidebar / Navigation --- */
.admin-sidebar {
width: 260px;
background-color: var(--admin-surface);
border-right: 1px solid var(--admin-border);
padding: 1.5rem 0;
display: flex;
flex-direction: column;
transition: width 0.3s ease;
}
.sidebar-header {
padding: 0 1.5rem 1.5rem 1.5rem;
text-align: center;
border-bottom: 1px solid var(--admin-border);
}
.sidebar-header h2 a {
color: var(--admin-text);
text-decoration: none;
font-size: 1.5rem;
font-weight: 700;
}
a {
color: var(--admin-accent);
}
a:hover {
color: var(--admin-accent-hover);
.sidebar-header h2 a span {
color: var(--admin-primary);
}
/* --- Override Bootstrap Dark Components --- */
.table-dark {
--bs-table-bg: var(--admin-surface);
--bs-table-border-color: var(--admin-border);
--bs-table-color: var(--admin-text-primary);
--bs-table-striped-bg: #353c4a; /* Slightly lighter for striped rows */
}
.table > :not(caption) > * > * {
border-bottom-width: 1px;
box-shadow: inset 0 0 0 9999px var(--bs-table-accent-bg);
}
.form-control {
background-color: var(--admin-surface);
color: var(--admin-text-primary);
border-color: var(--admin-border);
border-radius: 0.375rem;
padding: 0.75rem 1rem;
}
.form-control:focus {
background-color: var(--admin-surface);
color: var(--admin-text-primary);
border-color: var(--admin-accent);
box-shadow: 0 0 0 0.25rem rgba(var(--admin-accent), 0.2);
}
.form-select {
background-color: var(--admin-surface);
color: var(--admin-text-primary);
border-color: var(--admin-border);
}
/* --- Buttons --- */
.btn-primary {
background-color: var(--admin-accent);
border-color: var(--admin-accent);
color: #1A202C; /* Dark text on gold button */
font-weight: 600;
}
.btn-primary:hover {
background-color: var(--admin-accent-hover);
border-color: var(--admin-accent-hover);
color: #1A202C;
}
.btn-success { background-color: var(--admin-success); border-color: var(--admin-success); }
.btn-danger { background-color: var(--admin-danger); border-color: var(--admin-danger); }
.btn-info { background-color: var(--admin-info); border-color: var(--admin-info); }
/* --- New Sidebar --- */
.admin-sidebar {
position: fixed;
top: 0;
right: 0;
width: 280px;
height: 100vh;
background-color: var(--admin-surface);
border-left: 1px solid var(--admin-border);
display: flex;
flex-direction: column;
padding: 1.5rem 0;
z-index: 1100;
}
.admin-sidebar-header {
text-align: center;
padding: 0 1.5rem 1.5rem 1.5rem;
border-bottom: 1px solid var(--admin-border);
}
.admin-sidebar-header .logo {
font-size: 1.75rem;
font-weight: 800;
color: var(--admin-text-primary);
text-decoration: none;
}
.admin-sidebar-header .logo span {
color: var(--admin-accent);
}
.admin-sidebar .nav {
.admin-nav {
flex-grow: 1;
padding-top: 1rem;
list-style: none;
padding: 1.5rem 0 0 0;
margin: 0;
}
.admin-sidebar .nav-link {
color: var(--admin-text-secondary);
.admin-nav-item {
margin: 0;
}
.admin-nav-link {
display: flex;
align-items: center;
font-size: 1rem;
font-weight: 500;
gap: 0.8rem;
padding: 0.9rem 1.5rem;
margin: 0.25rem 0;
border-right: 4px solid transparent;
transition: all 0.2s ease-in-out;
}
.admin-sidebar .nav-link:hover {
color: var(--admin-text-primary);
background-color: rgba(45, 55, 72, 0.5); /* #2D3748 with opacity */
}
.admin-sidebar .nav-link.active {
color: var(--admin-text-primary);
background-color: var(--admin-bg);
border-right-color: var(--admin-accent);
color: var(--admin-text-muted);
text-decoration: none;
font-weight: 600;
}
.admin-sidebar .nav-link .bi {
font-size: 1.2rem;
margin-left: 0.75rem; /* For RTL, it should be margin-left */
font-size: 0.95rem;
border-left: 4px solid transparent;
transition: all 0.3s ease;
}
.admin-sidebar-footer {
padding: 1rem 1.5rem;
.admin-nav-link i {
font-size: 1.1rem;
width: 20px;
text-align: center;
}
.admin-nav-link:hover {
background-color: var(--admin-bg);
color: var(--admin-primary);
border-left-color: var(--admin-primary);
}
.admin-nav-link.active {
background-color: var(--admin-bg);
color: var(--admin-text);
border-left-color: var(--admin-primary);
font-weight: 700;
}
.sidebar-footer {
padding: 1.5rem;
text-align: center;
border-top: 1px solid var(--admin-border);
}
/* --- Dashboard Stat Cards --- */
.stat-card {
.sidebar-footer a {
color: var(--admin-text-muted);
text-decoration: none;
font-size: 0.9rem;
}
.sidebar-footer a:hover {
color: var(--admin-primary);
}
/* --- Main Content --- */
.admin-main-content {
flex-grow: 1;
padding: 2rem;
overflow-y: auto;
}
.admin-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
}
.admin-header h1 {
margin: 0;
font-size: 2rem;
font-weight: 700;
}
/* --- General Components --- */
.card {
background-color: var(--admin-surface);
border: 1px solid var(--admin-border);
border-radius: 0.75rem;
border-radius: 12px;
margin-bottom: 2rem;
}
.card-header {
padding: 1rem 1.5rem;
border-bottom: 1px solid var(--admin-border);
font-size: 1.1rem;
font-weight: 600;
}
.card-body {
padding: 1.5rem;
}
.table {
width: 100%;
border-collapse: collapse;
}
.table th, .table td {
padding: 1rem;
text-align: right;
border-bottom: 1px solid var(--admin-border);
}
.table th {
font-weight: 700;
color: var(--admin-text-muted);
font-size: 0.9rem;
text-transform: uppercase;
}
.table tbody tr:last-child td {
border-bottom: none;
}
.table tbody tr:hover {
background-color: var(--admin-bg);
}
.btn {
padding: 0.6rem 1.2rem;
border-radius: 8px;
text-decoration: none;
font-weight: 600;
transition: all 0.3s ease;
border: none;
cursor: pointer;
}
.btn-primary {
background-color: var(--admin-primary);
color: var(--admin-bg);
}
.btn-primary:hover {
opacity: 0.9;
}
.btn-danger {
background-color: var(--admin-danger);
color: var(--admin-text);
}
/* --- Stat Cards (from dashboard) --- */
.stat-card {
background-color: var(--admin-surface);
border-radius: 12px;
padding: 1.5rem;
display: flex;
align-items: center;
transition: all 0.3s ease;
gap: 1.5rem;
border: 1px solid var(--admin-border);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.stat-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0,0,0,0.2);
border-color: var(--admin-accent);
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
}
.stat-card .icon-container {
.stat-card .icon {
font-size: 2rem;
color: var(--admin-accent);
background-color: #363e4d;
width: 60px;
height: 60px;
padding: 1rem;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-left: 1.5rem; /* For RTL */
}
.stat-card .stat-info h3 {
font-size: 2.25rem;
font-weight: 800;
margin: 0;
color: var(--admin-text);
}
.stat-card .stat-info p {
margin: 0;
color: var(--admin-text-secondary);
font-size: 0.9rem;
color: var(--admin-text-muted);
}
/* --- Dashboard Tables & Badges --- */
.card-table .card-header {
background-color: transparent;
border-bottom: 1px solid var(--admin-border);
padding: 1rem 1.5rem;
font-weight: 600;
.stat-card .stat-info h3 {
margin: 0;
font-size: 2rem;
font-weight: 700;
}
.badge.bg-processing { background-color: var(--admin-info) !important; }
.badge.bg-shipped { background-color: var(--admin-success) !important; }
.badge.bg-cancelled { background-color: var(--admin-danger) !important; }
.badge.bg-pending { background-color: #DD6B20 !important; } /* Orange */
.icon.bg-success { background-color: var(--admin-success); }
.icon.bg-danger { background-color: var(--admin-danger); }
.icon.bg-warning { background-color: var(--admin-warning); }
.icon.bg-info { background-color: var(--admin-info); }
.icon.bg-primary { background-color: var(--admin-primary); }
/* --- Modal Styling --- */
.modal-content {
/* --- Chart Container --- */
.chart-container {
background-color: var(--admin-surface);
color: var(--admin-text-primary);
padding: 2rem;
border-radius: 12px;
border: 1px solid var(--admin-border);
}
.modal-header {
border-bottom: 1px solid var(--admin-border);
}
.modal-footer {
border-top: 1px solid var(--admin-border);
}
.btn-close {
filter: invert(1) grayscale(100%) brightness(200%);
.chart-container h5 {
font-weight: 700;
margin-bottom: 1.5rem;
font-size: 1.2rem;
}
/* --- Form styles --- */
.form-group {
margin-bottom: 1.5rem;
}
.form-label {
display: block;
margin-bottom: 0.5rem;
font-weight: 600;
color: var(--admin-text-muted);
}
.form-control {
width: 100%;
padding: 0.8rem 1rem;
background-color: var(--admin-bg);
border: 1px solid var(--admin-border);
color: var(--admin-text);
border-radius: 8px;
box-sizing: border-box;
}
.form-control:focus {
outline: none;
border-color: var(--admin-primary);
box-shadow: 0 0 0 2px rgba(192, 159, 128, 0.2);
}
textarea.form-control {
min-height: 120px;
resize: vertical;
}
/* Responsive */
@media (max-width: 992px) {
.admin-main-content {
margin-right: 0;
.admin-sidebar {
width: 70px;
}
.sidebar-header h2 {
display: none;
}
.admin-nav-link {
justify-content: center;
}
.admin-nav-link span {
display: none;
}
}
@media (max-width: 768px) {
.admin-wrapper {
flex-direction: column;
}
.admin-sidebar {
transform: translateX(280px); /* For RTL */
transition: transform 0.3s ease;
width: 100%;
height: auto;
border-right: none;
border-bottom: 1px solid var(--admin-border);
flex-direction: row;
align-items: center;
padding: 0;
}
.admin-sidebar.is-open {
transform: translateX(0);
.sidebar-header {
display: none;
}
/* Add a hamburger toggle button */
.sidebar-toggle {
display: block;
position: fixed;
top: 15px;
right: 15px;
z-index: 1200;
background: var(--admin-surface);
border: 1px solid var(--admin-border);
color: var(--admin-text-primary);
padding: 5px 10px;
border-radius: 5px;
.admin-nav {
display: flex;
justify-content: space-around;
flex-grow: 1;
padding: 0;
}
}
.admin-nav-link {
border-left: none;
border-bottom: 4px solid transparent;
}
.admin-nav-link:hover, .admin-nav-link.active {
border-left-color: transparent;
border-bottom-color: var(--admin-primary);
}
.sidebar-footer {
display: none;
}
}

View File

@ -0,0 +1,69 @@
:root {
--dark-bg: #1a1a2e;
--dark-surface: #16213e;
--dark-primary: #0f3460;
--dark-secondary: #e94560;
--dark-text-primary: #ffffff;
--dark-text-secondary: #c5c5c5;
}
body.dark-theme {
background-color: var(--dark-bg);
color: var(--dark-text-primary);
}
.dashboard-container {
padding: 2rem;
}
.stat-card-v2 {
background-color: var(--dark-surface);
border-radius: 12px;
padding: 1.5rem;
display: flex;
align-items: center;
gap: 1.5rem;
border: 1px solid var(--dark-primary);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.stat-card-v2:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
}
.stat-card-v2 .icon-container {
font-size: 2.5rem;
padding: 1rem;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
background-color: var(--dark-primary);
}
.stat-card-v2 .stat-info p {
margin: 0;
font-size: 0.9rem;
color: var(--dark-text-secondary);
}
.stat-card-v2 .stat-info h3 {
margin: 0;
font-size: 2rem;
font-weight: bold;
}
.chart-container {
background-color: var(--dark-surface);
padding: 2rem;
border-radius: 12px;
border: 1px solid var(--dark-primary);
margin-top: 2rem;
}
.chart-container h5 {
font-weight: bold;
margin-bottom: 1.5rem;
}

View File

@ -1,10 +1,8 @@
<?php
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
require_once __DIR__ . '/auth_handler.php';
// Check if the user is logged in. If not, redirect to the login page.
if (!isset($_SESSION['is_admin']) || $_SESSION['is_admin'] !== true) {
if (!is_admin()) {
header('Location: login.php');
exit;
}

8
admin/auth_handler.php Normal file
View File

@ -0,0 +1,8 @@
<?php
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
function is_admin() {
return isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true;
}

1
admin/cache/sales_chart.json vendored Normal file
View File

@ -0,0 +1 @@
{"labels":["1404-09"],"data":[2940000]}

187
admin/dashboard.php Normal file
View File

@ -0,0 +1,187 @@
<?php
require_once __DIR__ . '/header.php';
?>
<div class="admin-header">
<h1>گزارش‌ها</h1>
</div>
<div id="dashboard-error" class="card d-none" style="color: var(--admin-danger);"><div class="card-body"></div></div>
<style>
.stat-cards-grid-reports {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1.5rem;
margin-bottom: 2rem;
}
</style>
<div class="stat-cards-grid-reports">
<div class="stat-card">
<div class="icon bg-success"><i class="fas fa-dollar-sign"></i></div>
<div class="stat-info">
<p>مجموع فروش (تحویل شده)</p>
<h3 id="total-sales">...</h3>
</div>
</div>
<div class="stat-card">
<div class="icon bg-info"><i class="fas fa-users"></i></div>
<div class="stat-info">
<p>مجموع کاربران</p>
<h3 id="total-users">...</h3>
</div>
</div>
<div class="stat-card">
<div class="icon bg-primary"><i class="fas fa-truck"></i></div>
<div class="stat-info">
<p>سفارشات در حال ارسال</p>
<h3 id="shipped-orders">...</h3>
</div>
</div>
<div class="stat-card">
<div class="icon bg-danger"><i class="fas fa-times-circle"></i></div>
<div class="stat-info">
<p>سفارشات لغو شده</p>
<h3 id="cancelled-orders">...</h3>
</div>
</div>
<div class="stat-card">
<div class="icon bg-warning"><i class="fas fa-spinner"></i></div>
<div class="stat-info">
<p>سفارشات در حال پردازش</p>
<h3 id="processing-orders">...</h3>
</div>
</div>
<div class="stat-card">
<div class="icon" style="background-color: #6f42c1;"><i class="fas fa-eye"></i></div>
<div class="stat-info">
<p>کل بازدید صفحات</p>
<h3 id="total-page-views">...</h3>
</div>
</div>
</div>
<div class="chart-container" style="position: relative; height:40vh; max-height: 450px;">
<h5>نمودار فروش ماهانه (سفارشات تحویل شده)</h5>
<canvas id="salesChart"></canvas>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const errorDiv = document.getElementById('dashboard-error');
const errorBody = errorDiv.querySelector('.card-body');
function showError(message) {
errorBody.textContent = message;
errorDiv.classList.remove('d-none');
}
// Fetch all data in parallel for faster loading
Promise.all([
fetch('api.php?action=get_stats').then(res => res.json()),
fetch('api.php?action=get_sales_data').then(res => res.json())
]).then(([statsData, salesData]) => {
// Handle stats data
if (statsData.error) throw new Error(`آمار: ${statsData.error}`);
document.getElementById('total-sales').textContent = new Intl.NumberFormat('fa-IR').format(statsData.total_sales) + ' تومان';
document.getElementById('total-users').textContent = statsData.total_users;
document.getElementById('shipped-orders').textContent = statsData.shipped_orders;
document.getElementById('cancelled-orders').textContent = statsData.cancelled_orders;
document.getElementById('processing-orders').textContent = statsData.processing_orders;
document.getElementById('total-page-views').textContent = statsData.total_page_views.count;
// Handle sales chart data
if (salesData.error) throw new Error(`نمودار فروش: ${salesData.error}`);
renderSalesChart(salesData.labels, salesData.data);
}).catch(error => {
console.error(`خطا:`, error);
showError(`خطا در بارگذاری گزارشات: ${error.message}`);
});
function renderSalesChart(labels, data) {
const ctx = document.getElementById('salesChart').getContext('2d');
const style = getComputedStyle(document.body);
// Use a more vibrant and visible color for the chart
const chartColor = '#FFD700'; // A nice gold color
const gradient = ctx.createLinearGradient(0, 0, 0, 400);
gradient.addColorStop(0, `${chartColor}60`); // 40% opacity
gradient.addColorStop(1, `${chartColor}00`); // 0% opacity
new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [{
label: 'میزان فروش',
data: data,
backgroundColor: gradient,
borderColor: chartColor,
borderWidth: 3,
fill: 'start',
tension: 0.4, // Makes the line curvy
pointBackgroundColor: chartColor,
pointRadius: 4,
pointHoverRadius: 6,
pointBorderColor: style.getPropertyValue('--admin-bg').trim(),
pointBorderWidth: 2,
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
},
tooltip: {
backgroundColor: style.getPropertyValue('--admin-surface').trim(),
titleColor: style.getPropertyValue('--admin-text').trim(),
bodyColor: style.getPropertyValue('--admin-text-muted').trim(),
titleFont: { family: 'Vazirmatn', size: 14, weight: 'bold' },
bodyFont: { family: 'Vazirmatn', size: 12 },
padding: 12,
cornerRadius: 8,
displayColors: false,
callbacks: {
label: (context) => `مجموع فروش: ${new Intl.NumberFormat('fa-IR').format(context.parsed.y)} تومان`
}
}
},
scales: {
x: {
grid: {
display: false
},
ticks: {
color: style.getPropertyValue('--admin-text-muted').trim(),
font: { family: 'Vazirmatn', size: 12 }
}
},
y: {
grid: {
color: style.getPropertyValue('--admin-border').trim(),
drawBorder: false,
},
ticks: {
color: style.getPropertyValue('--admin-text-muted').trim(),
font: { family: 'Vazirmatn', size: 12 },
padding: 10,
callback: (value) => new Intl.NumberFormat('fa-IR', { notation: 'compact' }).format(value)
},
beginAtZero: true
}
}
}
});
}
});
</script>
<?php
require_once __DIR__ . '/footer.php';
?>

View File

@ -3,9 +3,7 @@ session_start();
require_once __DIR__ . '/auth_check.php';
require_once __DIR__ . '/../db/config.php';
// Sanitize and validate product ID
$product_id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
if (!$product_id) {
$_SESSION['flash_message'] = ['type' => 'danger', 'message' => 'شناسه محصول نامعتبر است.'];
header('Location: products.php');
@ -17,116 +15,113 @@ try {
$stmt = $pdo->prepare("SELECT * FROM products WHERE id = ?");
$stmt->execute([$product_id]);
$product = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$product) {
$_SESSION['flash_message'] = ['type' => 'danger', 'message' => 'محصول مورد نظر یافت نشد.'];
header('Location: products.php');
exit;
}
} catch (PDOException $e) {
error_log("Database Error: " . $e->getMessage());
$_SESSION['flash_message'] = ['type' => 'danger', 'message' => 'خطا در اتصال به پایگاه داده. لطفاً بعداً تلاش کنید.'];
$_SESSION['flash_message'] = ['type' => 'danger', 'message' => 'خطا در اتصال به پایگاه داده.'];
header('Location: products.php');
exit;
}
$page_title = "ویرایش محصول: " . htmlspecialchars($product['name']);
require_once 'header.php';
require_once __DIR__ . '/header.php';
?>
<div class="page-header">
<h1 class="page-title">ویرایش محصول</h1>
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="index.php">داشبورد</a></li>
<li class="breadcrumb-item"><a href="products.php">مدیریت محصولات</a></li>
<li class="breadcrumb-item active" aria-current="page"><?php echo htmlspecialchars($product['name']); ?></li>
</ol>
</nav>
<style>
.form-grid {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 2rem;
}
.image-preview-container {
background-color: var(--admin-bg);
border: 1px dashed var(--admin-border);
padding: 1rem;
border-radius: 8px;
text-align: center;
}
.image-preview {
max-width: 100%;
height: auto;
max-height: 200px;
border-radius: 8px;
margin-bottom: 1rem;
}
</style>
<div class="admin-header">
<h1>ویرایش محصول: <?php echo htmlspecialchars($product['name']); ?></h1>
<a href="products.php" class="btn" style="background: var(--admin-border); color: var(--admin-text);">بازگشت</a>
</div>
<div class="card-container">
<div class="card-header">
<h5 class="card-title">فرم ویرایش محصول</h5>
<a href="products.php" class="btn btn-sm btn-outline-secondary">
<i class="fas fa-arrow-left me-2"></i>بازگشت
</a>
</div>
<div class="card-body">
<form action="handler.php?action=edit" method="POST" enctype="multipart/form-data">
<input type="hidden" name="id" value="<?php echo htmlspecialchars($product['id']); ?>">
<input type="hidden" name="current_image" value="<?php echo htmlspecialchars($product['image_url']); ?>">
<form action="handler.php?action=edit" method="post" enctype="multipart/form-data">
<input type="hidden" name="id" value="<?php echo htmlspecialchars($product['id']); ?>">
<input type="hidden" name="current_image" value="<?php echo htmlspecialchars($product['image_url']); ?>">
<div class="row">
<!-- Main Product Info -->
<div class="col-lg-8">
<div class="form-group mb-4">
<label for="name">نام محصول</label>
<input type="text" class="form-control" id="name" name="name" value="<?php echo htmlspecialchars($product['name']); ?>" required>
</div>
<div class="form-group mb-4">
<label for="description">توضیحات</label>
<textarea class="form-control" id="description" name="description" rows="6" required><?php echo htmlspecialchars($product['description']); ?></textarea>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group mb-4">
<label for="price">قیمت (تومان)</label>
<input type="number" class="form-control" id="price" name="price" min="0" value="<?php echo htmlspecialchars($product['price']); ?>" required>
</div>
</div>
<div class="col-md-6">
<div class="form-group mb-4">
<label for="colors">کدهای رنگ هگز</label>
<input type="text" class="form-control" id="colors" name="colors" value="<?php echo htmlspecialchars($product['colors'] ?? ''); ?>">
<div class="form-text">رنگ‌ها را با کاما جدا کنید (مثال: #FFFFFF, #000000).</div>
</div>
</div>
</div>
<div class="form-check form-switch custom-switch mb-4">
<input type="checkbox" class="form-check-input" id="is_featured" name="is_featured" value="1" <?php echo ($product['is_featured'] ?? 0) ? 'checked' : ''; ?>>
<label class="form-check-label" for="is_featured">محصول ویژه (نمایش در صفحه اصلی)</label>
</div>
<div class="form-grid">
<div class="card">
<div class="card-body">
<div class="form-group">
<label for="name" class="form-label">نام محصول</label>
<input type="text" class="form-control" id="name" name="name" value="<?php echo htmlspecialchars($product['name']); ?>" required>
</div>
<!-- Image Upload -->
<div class="col-lg-4">
<div class="form-group mb-4">
<label for="image">تصویر محصول</label>
<div class="image-upload-wrapper text-center">
<img src="../<?php echo htmlspecialchars($product['image_url']); ?>" alt="Current Image" class="img-thumbnail mb-3" id="image-preview" style="max-width: 180px; height: auto;">
<input type="file" class="form-control" id="image" name="image" accept="image/*" onchange="previewImage(event)">
<small class="form-text text-muted mt-2">برای تغییر، تصویر جدید را انتخاب کنید.</small>
</div>
<div class="form-group">
<label for="description" class="form-label">توضیحات</label>
<textarea class="form-control" id="description" name="description" rows="5" required><?php echo htmlspecialchars($product['description']); ?></textarea>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem;">
<div class="form-group">
<label for="price" class="form-label">قیمت (تومان)</label>
<input type="number" class="form-control" id="price" name="price" value="<?php echo htmlspecialchars($product['price']); ?>" required>
</div>
<div class="form-group">
<label for="stock" class="form-label">موجودی</label>
<input type="number" class="form-control" id="stock" name="stock" value="<?php echo htmlspecialchars($product['stock']); ?>" required>
</div>
</div>
</div>
<div class="form-group">
<label for="colors" class="form-label">کدهای رنگ (اختیاری)</label>
<input type="text" class="form-control" id="colors" name="colors" value="<?php echo htmlspecialchars($product['colors'] ?? ''); ?>" placeholder="مثال: #8B4513, #2C2C2C">
<small style="color: var(--admin-text-muted);">کدهای رنگ هگزادسیمال را با کاما جدا کنید.</small>
</div>
<div class="form-actions">
<a href="products.php" class="btn btn-outline-secondary">انصراف</a>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save me-2"></i>ذخیره تغییرات
</button>
<div class="form-group">
<label style="display: flex; align-items: center; gap: 0.5rem;">
<input type="checkbox" name="is_featured" value="1" <?php echo ($product['is_featured'] ?? 0) ? 'checked' : ''; ?> style="width: 20px; height: 20px;">
<span>این یک محصول ویژه است</span>
</label>
</div>
</div>
</form>
</div>
<div class="card">
<div class="card-header">تصویر محصول</div>
<div class="card-body">
<div class="image-preview-container">
<img src="../<?php echo htmlspecialchars($product['image_url']); ?>" alt="Current Image" id="image-preview" class="image-preview">
<input type="file" class="form-control" id="image" name="image" accept="image/*" onchange="previewImage(event)">
<small style="color: var(--admin-text-muted); margin-top: 0.5rem; display: block;">برای تغییر، تصویر جدیدی انتخاب کنید.</small>
</div>
</div>
</div>
</div>
</div>
<?php
require_once 'footer.php';
?>
<div style="text-align: left; margin-top: 2rem;">
<button type="submit" class="btn btn-primary"><i class="fas fa-save"></i> ذخیره تغییرات</button>
</div>
</form>
<script>
// Preview image before upload
function previewImage(event) {
const reader = new FileReader();
reader.onload = function(){
const output = document.getElementById('image-preview');
output.src = reader.result;
};
if (event.target.files[0]) {
reader.readAsDataURL(event.target.files[0]);
}
}
function previewImage(event) {
const reader = new FileReader();
reader.onload = () => document.getElementById('image-preview').src = reader.result;
if (event.target.files[0]) reader.readAsDataURL(event.target.files[0]);
}
</script>
<?php require_once __DIR__ . '/footer.php'; ?>

View File

@ -1,7 +1,5 @@
</div><!-- /.container-fluid -->
</main><!-- /.admin-main-content -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</div><!-- /.admin-main-content -->
</div><!-- /.admin-wrapper -->
</body>
</html>

View File

@ -1,169 +1,37 @@
<?php
session_start();
require_once __DIR__ . '/../db/config.php';
require_once __DIR__ . '/auth_check.php';
require_once __DIR__ . '/../db/config.php';
$action = $_REQUEST['action'] ?? '';
$pdo = db();
// Default redirect location
$redirect_to = 'index.php';
switch ($action) {
case 'add':
$redirect_to = 'add_product.php'; // Redirect back to form on error
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = trim($_POST['name'] ?? '');
$description = trim($_POST['description'] ?? '');
$price = filter_var($_POST['price'], FILTER_VALIDATE_FLOAT);
$colors = trim($_POST['colors'] ?? '');
$is_featured = isset($_POST['is_featured']) ? 1 : 0;
$errors = [];
// Validation
if (empty($name)) $errors[] = "Product name is required.";
if (empty($description)) $errors[] = "Description is required.";
if ($price === false) $errors[] = "Price is invalid or missing.";
$image_path = '';
if (isset($_FILES['image']) && $_FILES['image']['error'] === UPLOAD_ERR_OK) {
$upload_dir = __DIR__ . '/../assets/images/products/';
if (!is_dir($upload_dir)) {
if (!mkdir($upload_dir, 0777, true)) {
$errors[] = "Image directory does not exist and could not be created.";
}
}
if (!is_writable($upload_dir)) {
$errors[] = "Image directory is not writable. Please check server permissions.";
} else {
$filename = uniqid('product_', true) . '_' . basename($_FILES['image']['name']);
$target_file = $upload_dir . $filename;
if (move_uploaded_file($_FILES['image']['tmp_name'], $target_file)) {
$image_path = 'assets/images/products/' . $filename;
} else {
$errors[] = "Failed to move uploaded file.";
}
}
} else {
$file_error = $_FILES['image']['error'] ?? UPLOAD_ERR_NO_FILE;
$upload_errors = [
UPLOAD_ERR_INI_SIZE => "The uploaded file exceeds the server's maximum upload size (upload_max_filesize).",
UPLOAD_ERR_FORM_SIZE => "The uploaded file exceeds the maximum size specified in the form.",
UPLOAD_ERR_PARTIAL => "The file was only partially uploaded.",
UPLOAD_ERR_NO_FILE => "No file was selected for upload.",
UPLOAD_ERR_NO_TMP_DIR => "Server configuration is missing a temporary folder for uploads.",
UPLOAD_ERR_CANT_WRITE => "Failed to write file to disk. Check permissions.",
UPLOAD_ERR_EXTENSION => "A PHP extension stopped the file upload.",
];
if ($file_error !== UPLOAD_ERR_NO_FILE) {
$errors[] = $upload_errors[$file_error] ?? "An unknown error occurred during file upload.";
}
}
if (empty($errors)) {
$sql = "INSERT INTO products (name, description, price, image_url, colors, is_featured) VALUES (?, ?, ?, ?, ?, ?)";
$stmt = $pdo->prepare($sql);
$stmt->execute([$name, $description, $price, $image_path, $colors, $is_featured]);
$_SESSION['flash_message'] = ['type' => 'success', 'message' => 'محصول با موفقیت اضافه شد!'];
$redirect_to = 'products.php';
} else {
$_SESSION['flash_message'] = ['type' => 'error', 'message' => implode("<br>", $errors)];
}
}
break;
case 'edit':
$redirect_to = 'products.php'; // Default redirect on success or if ID is missing
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$id = filter_input(INPUT_POST, 'id', FILTER_VALIDATE_INT);
$name = trim($_POST['name'] ?? '');
$description = trim($_POST['description'] ?? '');
$price = filter_var($_POST['price'], FILTER_VALIDATE_FLOAT);
$colors = trim($_POST['colors'] ?? '');
$is_featured = isset($_POST['is_featured']) ? 1 : 0;
$errors = [];
if (!$id) {
$errors[] = "Invalid product ID.";
} else {
$redirect_to = "edit_product.php?id=$id"; // Redirect back to the edit form on error
}
if (empty($name)) $errors[] = "Product name is required.";
if (empty($description)) $errors[] = "Description is required.";
if ($price === false) $errors[] = "Price is invalid or missing.";
$current_image_path = $_POST['current_image'] ?? '';
$image_path = $current_image_path;
if (isset($_FILES['image']) && $_FILES['image']['error'] === UPLOAD_ERR_OK) {
$upload_dir = __DIR__ . '/../assets/images/products/';
if (!is_dir($upload_dir)) mkdir($upload_dir, 0777, true);
if (is_writable($upload_dir)) {
$filename = uniqid('product_', true) . '_' . basename($_FILES['image']['name']);
$target_file = $upload_dir . $filename;
if (move_uploaded_file($_FILES['image']['tmp_name'], $target_file)) {
$image_path = 'assets/images/products/' . $filename;
// Optionally, delete the old image if it's different
if ($current_image_path && file_exists(__DIR__ . '/../' . $current_image_path)) {
// unlink(__DIR__ . '/../' . $current_image_path);
}
} else {
$errors[] = "Failed to move uploaded file.";
}
} else {
$errors[] = "Image directory is not writable.";
}
}
if (empty($errors)) {
$sql = "UPDATE products SET name = ?, description = ?, price = ?, image_url = ?, colors = ?, is_featured = ? WHERE id = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$name, $description, $price, $image_path, $colors, $is_featured, $id]);
$_SESSION['flash_message'] = ['type' => 'success', 'message' => 'محصول با موفقیت به‌روزرسانی شد!'];
$redirect_to = 'products.php';
} else {
$_SESSION['flash_message'] = ['type' => 'error', 'message' => implode("<br>", $errors)];
}
}
break;
case 'delete':
$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
if ($id) {
// First, get the image path to delete the file
$stmt = $pdo->prepare("SELECT image_url FROM products WHERE id = ?");
$stmt->execute([$id]);
$product = $stmt->fetch(PDO::FETCH_ASSOC);
if ($product && !empty($product['image_url'])) {
$image_file = __DIR__ . '/../' . $product['image_url'];
if (file_exists($image_file)) {
// unlink($image_file);
}
}
// Then, delete the record from the database
$sql = "DELETE FROM products WHERE id = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$id]);
$_SESSION['flash_message'] = ['type' => 'success', 'message' => 'محصول با موفقیت حذف شد!'];
} else {
$_SESSION['flash_message'] = ['type' => 'error', 'message' => 'شناسه محصول برای حذف نامعتبر است.'];
}
$redirect_to = 'products.php';
break;
default:
$_SESSION['flash_message'] = ['type' => 'error', 'message' => 'عملیات نامعتبر است.'];
break;
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: index.php');
exit;
}
header("Location: " . $redirect_to);
$action = $_POST['action'] ?? '';
if ($action === 'update_order_status') {
$order_id = filter_input(INPUT_POST, 'order_id', FILTER_VALIDATE_INT);
$status = filter_input(INPUT_POST, 'status', FILTER_SANITIZE_STRING);
$allowed_statuses = ['Processing', 'Shipped', 'Delivered', 'Cancelled'];
if ($order_id && $status && in_array($status, $allowed_statuses)) {
try {
$pdo = db();
$stmt = $pdo->prepare("UPDATE orders SET status = ? WHERE id = ?");
$stmt->execute([$status, $order_id]);
$_SESSION['success_message'] = "وضعیت سفارش #{$order_id} با موفقیت به '{$status}' تغییر یافت.";
} catch (PDOException $e) {
error_log("Order status update failed: " . $e->getMessage());
$_SESSION['error_message'] = "خطایی در به‌روزرسانی وضعیت سفارش رخ داد.";
}
} else {
$_SESSION['error_message'] = "اطلاعات نامعتبر برای به‌روزرسانی وضعیت.";
}
}
header('Location: orders.php');
exit;
?>

View File

@ -5,23 +5,18 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>پنل مدیریت</title>
<meta name="robots" content="noindex, nofollow">
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<!-- Styles -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<!-- Unified Stylesheet -->
<link rel="stylesheet" href="assets/css/admin_style.css?v=<?php echo time(); ?>">
<!-- SweetAlert2 -->
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<!-- Chart.js -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body class="admin-page">
<body class="admin-dark-theme">
<?php require_once 'nav.php'; ?>
<main class="admin-main-content">
<div class="container-fluid">
<div class="admin-wrapper">
<?php require_once 'nav.php'; ?>
<div class="admin-main-content">

View File

@ -2,8 +2,6 @@
session_start();
require_once __DIR__ . '/auth_check.php';
require_once __DIR__ . '/../db/config.php';
// New header - includes nav, head, and opening body/main tags
require_once __DIR__ . '/header.php';
$dashboard_error = null;
@ -13,14 +11,25 @@ $recent_orders = [];
try {
$pdo = db();
$total_products = $pdo->query("SELECT COUNT(*) FROM products")->fetchColumn();
$total_orders = $pdo->query("SELECT COUNT(*) FROM orders")->fetchColumn();
$recent_orders = $pdo->query("SELECT id, customer_name, total_amount, `status`, created_at FROM orders ORDER BY created_at DESC LIMIT 5")->fetchAll(PDO::FETCH_ASSOC);
$recent_orders_query = "
SELECT
o.id,
COALESCE(CONCAT(u.first_name, ' ', u.last_name), o.billing_name) AS customer_name,
o.total_amount,
o.status,
o.created_at
FROM orders AS o
LEFT JOIN users AS u ON o.user_id = u.id
ORDER BY o.created_at DESC
LIMIT 5
";
$recent_orders = $pdo->query($recent_orders_query)->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
$dashboard_error = "<strong>خطا در بارگذاری اطلاعات داشبورد:</strong> " . $e->getMessage();
$dashboard_error .= "<br><br>این خطا معمولاً به دلیل قدیمی بودن ساختار دیتابیس رخ می‌دهد. لطفاً برای به‌روزرسانی به <a href='../migrate.php' class='alert-link'>صفحه مایگریشن</a> بروید.";
$dashboard_error = "<strong>خطا در بارگذاری اطلاعات:</strong> " . $e->getMessage();
}
$flash_message = $_SESSION['flash_message'] ?? null;
@ -28,115 +37,110 @@ if ($flash_message) {
unset($_SESSION['flash_message']);
}
// Function to map status to a badge class
function get_status_badge($status) {
function get_status_badge_class($status) {
switch (strtolower($status)) {
case 'processing':
return 'bg-processing';
case 'shipped':
return 'bg-shipped';
case 'cancelled':
return 'bg-cancelled';
default:
return 'bg-pending';
case 'processing': return 'status-processing';
case 'shipped': return 'status-shipped';
case 'delivered': return 'status-delivered';
case 'cancelled': return 'status-cancelled';
default: return 'status-pending';
}
}
?>
<h1 class="h2 mb-4">داشبورد</h1>
<style>
.status-badge {
padding: 0.3em 0.6em;
border-radius: 6px;
font-size: 0.8rem;
font-weight: 600;
color: #fff;
}
.status-processing { background-color: var(--admin-info); }
.status-shipped { background-color: var(--admin-warning); }
.status-delivered { background-color: var(--admin-success); }
.status-cancelled { background-color: var(--admin-danger); }
.status-pending { background-color: var(--admin-text-muted); }
.stat-cards-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1.5rem;
margin-bottom: 2rem;
}
</style>
<?php if ($dashboard_error): ?>
<div class="alert alert-danger"><?php echo $dashboard_error; ?></div>
<?php else: ?>
<div class="admin-header">
<h1>داشبورد اصلی</h1>
</div>
<!-- Stat Cards -->
<div class="row g-4 mb-4">
<div class="col-lg-6">
<div class="stat-card">
<div class="icon-container">
<i class="bi bi-box-seam"></i>
</div>
<div class="stat-info">
<p>کل محصولات</p>
<h3><?php echo htmlspecialchars($total_products); ?></h3>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="stat-card">
<div class="icon-container">
<i class="bi bi-receipt"></i>
</div>
<div class="stat-info">
<p>کل سفارشات</p>
<h3><?php echo htmlspecialchars($total_orders); ?></h3>
</div>
</div>
</div>
</div>
<!-- Recent Orders -->
<div class="card shadow-sm card-table" style="background-color: var(--admin-surface); border-color: var(--admin-border);">
<div class="card-header">
<h5 class="mb-0">آخرین سفارشات</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-dark table-hover align-middle">
<thead>
<tr>
<th>شماره سفارش</th>
<th>نام مشتری</th>
<th>مبلغ کل</th>
<th>وضعیت</th>
<th>تاریخ</th>
<th class="text-end"></th>
</tr>
</thead>
<tbody>
<?php if (empty($recent_orders)): ?>
<tr>
<td colspan="6" class="text-center py-4">هیچ سفارشی یافت نشد.</td>
</tr>
<?php else: ?>
<?php foreach ($recent_orders as $order): ?>
<tr>
<td>#<?php echo htmlspecialchars($order['id']); ?></td>
<td><?php echo htmlspecialchars($order['customer_name']); ?></td>
<td><?php echo number_format($order['total_amount']); ?> تومان</td>
<td><span class="badge <?php echo get_status_badge($order['status']); ?>"><?php echo htmlspecialchars($order['status']); ?></span></td>
<td><?php echo date('Y-m-d', strtotime($order['created_at'])); ?></td>
<td class="text-end">
<a href="orders.php?id=<?php echo $order['id']; ?>" class="btn btn-sm btn-outline-info">مشاهده</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<?php if ($flash_message): ?>
<script>
document.addEventListener('DOMContentLoaded', function() {
Swal.fire({
title: '<?php echo $flash_message["type"] === "success" ? "موفق" : "خطا"; ?>',
html: '<?php echo addslashes($flash_message["message"]); ?>',
icon: '<?php echo $flash_message["type"]; ?>',
confirmButtonText: 'باشه',
background: 'var(--admin-surface)',
color: 'var(--admin-text)'
});
});
</script>
<?php endif; ?>
<!-- Page-specific scripts -->
<script>
document.addEventListener('DOMContentLoaded', function () {
// Flash message handling (if any)
<?php if ($flash_message): ?>
Swal.fire({
title: '<?php echo $flash_message["type"] === "success" ? "موفق" : "خطا"; ?>',
html: '<?php echo addslashes($flash_message["message"]); ?>',
icon: '<?php echo $flash_message["type"]; ?>',
confirmButtonText: 'باشه',
background: 'var(--admin-surface)',
color: 'var(--admin-text-primary)'
});
<?php endif; ?>
});
</script>
<?php if ($dashboard_error): ?>
<div class="card"><div class="card-body" style="color: var(--admin-danger);"><?php echo $dashboard_error; ?></div></div>
<?php else: ?>
<?php
// New footer
require_once __DIR__ . '/footer.php';
?>
<div class="stat-cards-grid">
<div class="stat-card">
<div class="icon bg-primary"><i class="fas fa-box"></i></div>
<div class="stat-info">
<p>کل محصولات</p>
<h3><?php echo htmlspecialchars($total_products); ?></h3>
</div>
</div>
<div class="stat-card">
<div class="icon bg-warning"><i class="fas fa-receipt"></i></div>
<div class="stat-info">
<p>کل سفارشات</p>
<h3><?php echo htmlspecialchars($total_orders); ?></h3>
</div>
</div>
</div>
<div class="card">
<div class="card-header">آخرین سفارشات</div>
<div class="card-body">
<table class="table">
<thead>
<tr>
<th>شماره سفارش</th>
<th>نام مشتری</th>
<th>مبلغ کل</th>
<th>وضعیت</th>
<th>تاریخ</th>
</tr>
</thead>
<tbody>
<?php if (empty($recent_orders)): ?>
<tr><td colspan="5" style="text-align: center; padding: 2rem;">هیچ سفارشی یافت نشد.</td></tr>
<?php else: ?>
<?php foreach ($recent_orders as $order): ?>
<tr>
<td>#<?php echo htmlspecialchars($order['id']); ?></td>
<td><?php echo htmlspecialchars($order['customer_name']); ?></td>
<td><?php echo number_format($order['total_amount']); ?> تومان</td>
<td><span class="status-badge <?php echo get_status_badge_class($order['status']); ?>"><?php echo htmlspecialchars($order['status']); ?></span></td>
<td><?php echo date('Y-m-d', strtotime($order['created_at'])); ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<?php endif; ?>
<?php require_once __DIR__ . '/footer.php'; ?>

View File

@ -1,42 +1,37 @@
<?php $current_page = basename($_SERVER['PHP_SELF']); ?>
<div class="admin-sidebar">
<div class="admin-sidebar-header">
<a href="index.php" class="logo">آتیمه<span>.</span></a>
<aside class="admin-sidebar">
<div class="sidebar-header">
<h2><a href="index.php">آتیمه<span>.</span></a></h2>
</div>
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link <?php echo ($current_page == 'index.php') ? 'active' : ''; ?>" href="index.php">
<i class="bi bi-grid-1x2-fill"></i>
<span>داشبورد</span>
<ul class="admin-nav">
<li class="admin-nav-item">
<a class="admin-nav-link <?php echo ($current_page == 'index.php') ? 'active' : ''; ?>" href="index.php">
<i class="fas fa-tachometer-alt"></i>
<span>داشبورد اصلی</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link <?php echo ($current_page == 'products.php' || $current_page == 'add_product.php' || $current_page == 'edit_product.php') ? 'active' : ''; ?>" href="products.php">
<i class="bi bi-box-seam"></i>
<li class="admin-nav-item">
<a class="admin-nav-link <?php echo ($current_page == 'dashboard.php') ? 'active' : ''; ?>" href="dashboard.php">
<i class="fas fa-chart-line"></i>
<span>گزارش‌ها</span>
</a>
</li>
<li class="admin-nav-item">
<a class="admin-nav-link <?php echo in_array($current_page, ['products.php', 'add_product.php', 'edit_product.php']) ? 'active' : ''; ?>" href="products.php">
<i class="fas fa-box"></i>
<span>محصولات</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link <?php echo ($current_page == 'orders.php') ? 'active' : ''; ?>" href="orders.php">
<i class="bi bi-card-checklist"></i>
<li class="admin-nav-item">
<a class="admin-nav-link <?php echo ($current_page == 'orders.php') ? 'active' : ''; ?>" href="orders.php">
<i class="fas fa-receipt"></i>
<span>سفارشات</span>
</a>
</li>
</ul>
<div class="admin-sidebar-footer">
<a href="../index.php" target="_blank" class="btn btn-outline-secondary w-100 mb-2">
<i class="bi bi-box-arrow-up-right"></i> مشاهده سایت
</a>
<a href="logout.php" class="btn btn-outline-danger w-100">
<i class="bi bi-box-arrow-right"></i> خروج
</a>
<div class="sidebar-footer">
<a href="../index.php" target="_blank"><i class="fas fa-external-link-alt"></i> مشاهده سایت</a>
<hr style="border-color: var(--admin-border); margin: 1rem 0;">
<a href="logout.php"><i class="fas fa-sign-out-alt"></i> خروج</a>
</div>
</div>
<button class="btn sidebar-toggle d-lg-none" type="button" onclick="toggleSidebar()">
<i class="bi bi-list"></i>
</button>
<script>
function toggleSidebar() {
document.querySelector('.admin-sidebar').classList.toggle('is-open');
}
</script>
</aside>

View File

@ -2,130 +2,185 @@
session_start();
require_once __DIR__ . '/auth_check.php';
require_once __DIR__ . '/../db/config.php';
// New header
require_once __DIR__ . '/header.php';
// Fetch orders from the database
try {
$pdo = db();
$stmt = $pdo->query("SELECT * FROM orders ORDER BY created_at DESC");
$query = "SELECT o.*, COALESCE(CONCAT(u.first_name, ' ', u.last_name), o.billing_name) AS customer_display_name FROM orders o LEFT JOIN users u ON o.user_id = u.id ORDER BY o.created_at DESC";
$stmt = $pdo->query($query);
$orders = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
$error_message = "خطا در دریافت اطلاعات سفارشات: " . $e->getMessage();
$orders = [];
}
// Function to map status to a badge class
function get_status_badge($status) {
function get_status_badge_class($status) {
switch (strtolower($status)) {
case 'processing':
return 'bg-processing';
case 'shipped':
return 'bg-shipped';
case 'cancelled':
return 'bg-cancelled';
default:
return 'bg-pending';
case 'processing': return 'status-processing';
case 'shipped': return 'status-shipped';
case 'delivered': return 'status-delivered';
case 'cancelled': return 'status-cancelled';
default: return 'status-pending';
}
}
$statuses = ['Processing', 'Shipped', 'Delivered', 'Cancelled'];
?>
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h2">مدیریت سفارشات</h1>
<style>
/* Same status badges from index.php */
.status-badge { padding: 0.3em 0.6em; border-radius: 6px; font-size: 0.8rem; font-weight: 600; color: #fff; }
.status-processing { background-color: var(--admin-info); }
.status-shipped { background-color: var(--admin-warning); }
.status-delivered { background-color: var(--admin-success); }
.status-cancelled { background-color: var(--admin-danger); }
.status-pending { background-color: var(--admin-text-muted); }
/* Custom Modal Styles */
.modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); z-index: 1000; display: none; align-items: center; justify-content: center; }
.modal-container { background: var(--admin-surface); border: 1px solid var(--admin-border); border-radius: 12px; width: 90%; max-width: 800px; max-height: 90vh; display: flex; flex-direction: column; }
.modal-header { padding: 1rem 1.5rem; border-bottom: 1px solid var(--admin-border); display: flex; justify-content: space-between; align-items: center; }
.modal-body { padding: 1.5rem; overflow-y: auto; }
.modal-footer { padding: 1rem 1.5rem; border-top: 1px solid var(--admin-border); text-align: left; }
.modal-close { background: none; border: none; font-size: 1.5rem; color: var(--admin-text-muted); cursor: pointer; }
.modal-overlay.active { display: flex; }
.items-list img { width: 50px; height: 50px; object-fit: cover; border-radius: 6px; }
</style>
<div class="admin-header">
<h1>مدیریت سفارشات</h1>
</div>
<?php if (isset($error_message)): ?>
<div class="alert alert-danger"><?php echo $error_message; ?></div>
<?php endif; ?>
<?php if (isset($_SESSION['flash_message'])): ?>
<script>
document.addEventListener('DOMContentLoaded', () => {
const style = getComputedStyle(document.body);
Swal.fire({
title: '<?php echo $_SESSION["flash_message"]["type"] === "success" ? "موفق" : "خطا"; ?>',
html: '<?php echo addslashes($_SESSION["flash_message"]["message"]); ?>',
icon: '<?php echo $_SESSION["flash_message"]["type"]; ?>',
confirmButtonText: 'باشه',
background: style.getPropertyValue('--admin-surface'),
color: style.getPropertyValue('--admin-text')
});
});
</script>
<?php unset($_SESSION['flash_message']); endif; ?>
<div class="card shadow-sm card-table" style="background-color: var(--admin-surface); border-color: var(--admin-border);">
<div class="card">
<div class="card-body">
<div class="table-responsive">
<table class="table table-dark table-hover align-middle">
<thead>
<tr>
<th>شماره</th>
<th>نام مشتری</th>
<th>مبلغ کل</th>
<th>وضعیت</th>
<th>تاریخ</th>
<th class="text-end">عملیات</th>
</tr>
</thead>
<tbody>
<?php if (empty($orders)): ?>
<table class="table">
<thead>
<tr><th>شماره</th><th>نام مشتری</th><th>مبلغ کل</th><th>وضعیت</th><th>تاریخ</th><th style="text-align: left;">عملیات</th></tr>
</thead>
<tbody>
<?php if (empty($orders)): ?>
<tr><td colspan="6" style="text-align: center; padding: 2rem;">هیچ سفارشی یافت نشد.</td></tr>
<?php else: ?>
<?php foreach ($orders as $order): ?>
<tr>
<td colspan="6" class="text-center py-4">هیچ سفارشی یافت نشد.</td>
<td>#<?php echo $order['id']; ?></td>
<td><?php echo htmlspecialchars($order['customer_display_name']); ?></td>
<td><?php echo number_format($order['total_amount']); ?> تومان</td>
<td><span class="status-badge <?php echo get_status_badge_class($order['status']); ?>"><?php echo htmlspecialchars($order['status']); ?></span></td>
<td><?php echo date("Y-m-d", strtotime($order['created_at'])); ?></td>
<td style="text-align: left;">
<button class="btn btn-sm view-order-btn" data-order-id="<?php echo $order['id']; ?>" style="background-color: var(--admin-info); color: white;"><i class="fas fa-eye"></i></button>
</td>
</tr>
<?php else: ?>
<?php foreach ($orders as $order): ?>
<tr>
<td>#<?php echo $order['id']; ?></td>
<td><?php echo htmlspecialchars($order['customer_name']); ?></td>
<td><?php echo number_format($order['total_amount']); ?> تومان</td>
<td><span class="badge <?php echo get_status_badge($order['status']); ?>"><?php echo htmlspecialchars($order['status']); ?></span></td>
<td><?php echo date("Y-m-d", strtotime($order['created_at'])); ?></td>
<td class="text-end">
<button type="button" class="btn btn-sm btn-outline-info" data-bs-toggle="modal" data-bs-target="#orderModal<?php echo $order['id']; ?>">
<i class="bi bi-eye-fill"></i> مشاهده
</button>
</td>
</tr>
<!-- Modal for order details -->
<div class="modal fade" id="orderModal<?php echo $order['id']; ?>" tabindex="-1" aria-labelledby="orderModalLabel<?php echo $order['id']; ?>" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="orderModalLabel<?php echo $order['id']; ?>">جزئیات سفارش #<?php echo $order['id']; ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-6">
<p><strong>نام:</strong> <?php echo htmlspecialchars($order['customer_name']); ?></p>
<p><strong>ایمیل:</strong> <?php echo htmlspecialchars($order['customer_email']); ?></p>
<p><strong>تلفن:</strong> <?php echo htmlspecialchars($order['customer_phone'] ?? '-'); ?></p>
</div>
<div class="col-md-6">
<p><strong>آدرس:</strong> <?php echo htmlspecialchars($order['customer_address']); ?></p>
</div>
</div>
<hr>
<h6>محصولات خریداری شده:</h6>
<?php
$items = json_decode($order['items_json'], true);
if ($items):
?>
<ul class="list-group list-group-flush">
<?php foreach($items as $item): ?>
<li class="list-group-item d-flex justify-content-between align-items-center" style="background: none; color: var(--admin-text-primary);">
<?php echo htmlspecialchars($item['name']); ?>
<span class="badge bg-secondary rounded-pill">
<?php echo $item['quantity']; ?> عدد - <?php echo number_format($item['price']); ?> ت
</span>
</li>
<?php endforeach; ?>
</ul>
<?php else: ?>
<p>جزئیات محصولات موجود نیست.</p>
<?php endif; ?>
</div>
<div class="modal-footer">
<p class="w-100 text-start"><strong>مبلغ نهایی: <?php echo number_format($order['total_amount']); ?> تومان</strong></p>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<?php
// New footer
require_once __DIR__ . '/footer.php';
?>
<?php foreach ($orders as $order): ?>
<div id="modal-<?php echo $order['id']; ?>" class="modal-overlay">
<div class="modal-container">
<div class="modal-header">
<h5>جزئیات سفارش #<?php echo $order['id']; ?></h5>
<button class="modal-close">&times;</button>
</div>
<div class="modal-body">
<h6>اطلاعات مشتری</h6>
<p><strong>نام:</strong> <?php echo htmlspecialchars($order['customer_display_name']); ?><br>
<strong>آدرس:</strong> <?php echo htmlspecialchars($order['billing_address'] . ", " . $order['billing_city'] . ", " . $order['billing_province']); ?><br>
<strong>تلفن:</strong> <?php echo htmlspecialchars($order['billing_phone']); ?></p>
<hr style="border-color: var(--admin-border);">
<h6>محصولات</h6>
<table class="table items-list">
<thead>
<tr>
<th colspan="2">محصول</th>
<th>رنگ</th>
<th>تعداد</th>
<th class="text-start">قیمت واحد</th>
</tr>
</thead>
<tbody>
<?php $items = json_decode($order['items_json'], true); ?>
<?php foreach($items as $item): ?>
<tr style="vertical-align: middle;">
<td style="width: 60px;"><img src="../<?php echo htmlspecialchars($item['image_url']); ?>" style="width: 50px; height: 50px; object-fit: cover; border-radius: 6px;"></td>
<td><?php echo htmlspecialchars($item['name']); ?></td>
<td style="width: 60px;">
<?php if (!empty($item['color'])): ?>
<span style="display: inline-block; width: 22px; height: 22px; border-radius: 50%; background-color: <?php echo htmlspecialchars($item['color']); ?>; border: 1px solid var(--admin-border); box-shadow: 0 1px 3px rgba(0,0,0,0.1);" title="<?php echo htmlspecialchars($item['color']); ?>"></span>
<?php endif; ?>
</td>
<td style="width: 80px;"><?php echo $item['quantity']; ?> عدد</td>
<td style="width: 120px;" class="text-start"><?php echo number_format($item['price']); ?> تومان</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<hr style="border-color: var(--admin-border);">
<h5 style="text-align: left;">مبلغ نهایی: <?php echo number_format($order['total_amount']); ?> تومان</h5>
</div>
<div class="modal-footer">
<form action="handler.php" method="POST" style="display: flex; width: 100%; justify-content: space-between; align-items: center;">
<input type="hidden" name="order_id" value="<?php echo $order['id']; ?>">
<input type="hidden" name="action" value="update_order_status">
<div class="form-group" style="display: flex; align-items: center; gap: 1rem;">
<label for="status_<?php echo $order['id']; ?>" class="form-label">تغییر وضعیت:</label>
<select class="form-control" name="status" id="status_<?php echo $order['id']; ?>">
<?php foreach ($statuses as $status): ?>
<option value="<?php echo $status; ?>" <?php echo ($order['status'] === $status) ? 'selected' : ''; ?>><?php echo $status; ?></option>
<?php endforeach; ?>
</select>
</div>
<button type="submit" class="btn btn-primary">به‌روزرسانی</button>
</form>
</div>
</div>
</div>
<?php endforeach; ?>
<script>
document.addEventListener('DOMContentLoaded', function() {
const viewButtons = document.querySelectorAll('.view-order-btn');
viewButtons.forEach(button => {
button.addEventListener('click', function() {
const orderId = this.getAttribute('data-order-id');
document.getElementById('modal-' + orderId).classList.add('active');
});
});
const closeButtons = document.querySelectorAll('.modal-close');
closeButtons.forEach(button => {
button.addEventListener('click', function() {
this.closest('.modal-overlay').classList.remove('active');
});
});
document.querySelectorAll('.modal-overlay').forEach(overlay => {
overlay.addEventListener('click', function(e) {
if (e.target === this) {
this.classList.remove('active');
}
});
});
});
</script>
<?php require_once __DIR__ . '/footer.php'; ?>

View File

@ -2,8 +2,6 @@
session_start();
require_once __DIR__ . '/auth_check.php';
require_once __DIR__ . '/../db/config.php';
// New header - includes nav, head, and opening body/main tags
require_once __DIR__ . '/header.php';
try {
@ -11,7 +9,6 @@ try {
$stmt = $pdo->query("SELECT id, name, price FROM products ORDER BY created_at DESC");
$products = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
// In a real app, log this error instead of just dying
die("Error fetching products: " . $e->getMessage());
}
@ -19,101 +16,83 @@ $flash_message = $_SESSION['flash_message'] ?? null;
if ($flash_message) {
unset($_SESSION['flash_message']);
}
?>
<!-- Page Title and Action Button -->
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h2">مدیریت محصولات</h1>
<a href="add_product.php" class="btn btn-primary">
<i class="bi bi-plus-circle me-1"></i> افزودن محصول
</a>
<div class="admin-header">
<h1>مدیریت محصولات</h1>
<a href="add_product.php" class="btn btn-primary"><i class="fas fa-plus"></i> افزودن محصول</a>
</div>
<!-- Products Table -->
<div class="card shadow-sm" style="background-color: var(--admin-surface); border-color: var(--admin-border);">
<div class="card">
<div class="card-body">
<div class="table-responsive">
<table class="table table-dark table-striped table-hover align-middle">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">نام محصول</th>
<th scope="col">قیمت</th>
<th scope="col" class="text-end">عملیات</th>
</tr>
</thead>
<tbody>
<?php if (empty($products)): ?>
<table class="table">
<thead>
<tr>
<th>#</th>
<th>نام محصول</th>
<th>قیمت</th>
<th style="text-align: left;">عملیات</th>
</tr>
</thead>
<tbody>
<?php if (empty($products)): ?>
<tr><td colspan="4" style="text-align: center; padding: 2rem;">هیچ محصولی یافت نشد.</td></tr>
<?php else: ?>
<?php foreach ($products as $product): ?>
<tr>
<td colspan="4" class="text-center py-4">هیچ محصولی یافت نشد.</td>
<td><?php echo htmlspecialchars($product['id']); ?></td>
<td><?php echo htmlspecialchars($product['name']); ?></td>
<td><?php echo number_format($product['price']); ?> تومان</td>
<td style="text-align: left;">
<a href="edit_product.php?id=<?php echo $product['id']; ?>" class="btn" style="background-color: var(--admin-info); color: white;"><i class="fas fa-edit"></i></a>
<a href="handler.php?action=delete&id=<?php echo $product['id']; ?>" class="btn btn-danger delete-btn"><i class="fas fa-trash"></i></a>
</td>
</tr>
<?php else: ?>
<?php foreach ($products as $product): ?>
<tr>
<th scope="row"><?php echo htmlspecialchars($product['id']); ?></th>
<td><?php echo htmlspecialchars($product['name']); ?></td>
<td><?php echo number_format($product['price']); ?> تومان</td>
<td class="text-end">
<a href="edit_product.php?id=<?php echo $product['id']; ?>" class="btn btn-sm btn-info">
<i class="bi bi-pencil-fill"></i> ویرایش
</a>
<a href="handler.php?action=delete&id=<?php echo $product['id']; ?>" class="btn btn-sm btn-danger delete-btn">
<i class="bi bi-trash-fill"></i> حذف
</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<!-- Page-specific scripts -->
<script>
document.addEventListener('DOMContentLoaded', function () {
// Flash message handling
<?php if ($flash_message): ?>
Swal.fire({
title: '<?php echo $flash_message["type"] === "success" ? "موفق" : "خطا"; ?>',
html: '<?php echo addslashes($flash_message["message"]); ?>',
icon: '<?php echo $flash_message["type"]; ?>',
confirmButtonText: 'باشه',
background: 'var(--admin-surface)',
color: 'var(--admin-text-primary)'
});
<?php endif; ?>
document.addEventListener('DOMContentLoaded', function () {
const style = getComputedStyle(document.body);
// Delete confirmation
const deleteButtons = document.querySelectorAll('.delete-btn');
deleteButtons.forEach(button => {
button.addEventListener('click', function (e) {
e.preventDefault();
const href = this.getAttribute('href');
Swal.fire({
title: 'آیا مطمئن هستید؟',
text: "این عمل غیرقابل بازگشت است!",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: 'var(--admin-danger)',
cancelButtonColor: 'var(--admin-info)',
confirmButtonText: 'بله، حذف کن!',
cancelButtonText: 'انصراف',
background: 'var(--admin-surface)',
color: 'var(--admin-text-primary)'
}).then((result) => {
if (result.isConfirmed) {
window.location.href = href;
}
});
<?php if ($flash_message): ?>
Swal.fire({
title: '<?php echo $flash_message["type"] === "success" ? "موفق" : "خطا"; ?>',
html: '<?php echo addslashes($flash_message["message"]); ?>',
icon: '<?php echo $flash_message["type"]; ?>',
confirmButtonText: 'باشه',
background: style.getPropertyValue('--admin-surface'),
color: style.getPropertyValue('--admin-text')
});
<?php endif; ?>
document.querySelectorAll('.delete-btn').forEach(button => {
button.addEventListener('click', function (e) {
e.preventDefault();
const href = this.getAttribute('href');
Swal.fire({
title: 'آیا مطمئن هستید؟',
text: "این عمل غیرقابل بازگشت است!",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: style.getPropertyValue('--admin-danger'),
cancelButtonColor: style.getPropertyValue('--admin-info'),
confirmButtonText: 'بله، حذف کن!',
cancelButtonText: 'انصراف',
background: style.getPropertyValue('--admin-surface'),
color: style.getPropertyValue('--admin-text')
}).then((result) => {
if (result.isConfirmed) {
window.location.href = href;
}
});
});
});
});
</script>
<?php
// New footer - includes closing tags
require_once __DIR__ . '/footer.php';
?>
<?php require_once __DIR__ . '/footer.php'; ?>

63
api/get_order_details.php Normal file
View File

@ -0,0 +1,63 @@
<?php
header('Content-Type: application/json');
require_once '../db/config.php';
require_once '../includes/jdf.php';
$response = ['success' => false, 'message' => 'Invalid request'];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$data = json_decode(file_get_contents('php://input'), true);
$tracking_id = $data['tracking_id'] ?? '';
$phone = $data['phone'] ?? '';
if (empty($tracking_id) || empty($phone)) {
$response['message'] = 'کد رهگیری و شماره تلفن الزامی است.';
echo json_encode($response);
exit;
}
try {
$db = db();
$stmt = $db->prepare(
"SELECT o.*, CONCAT(u.first_name, ' ', u.last_name) AS full_name, u.email
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.tracking_id = :tracking_id AND o.billing_phone = :phone"
);
$stmt->bindParam(':tracking_id', $tracking_id);
$stmt->bindParam(':phone', $phone);
$stmt->execute();
$order = $stmt->fetch(PDO::FETCH_ASSOC);
if ($order) {
$order_id = $order['id'];
$products_stmt = $db->prepare(
"SELECT p.name, p.price, p.image, oi.quantity, oi.color
FROM order_items oi
JOIN products p ON oi.product_id = p.id
WHERE oi.order_id = :order_id"
);
$products_stmt->bindParam(':order_id', $order_id);
$products_stmt->execute();
$products = $products_stmt->fetchAll(PDO::FETCH_ASSOC);
// Format creation date
$order['created_at_jalali'] = jdate('Y/m/d H:i', strtotime($order['created_at']));
$response['success'] = true;
$response['message'] = 'سفارش یافت شد.';
$response['order'] = $order;
$response['products'] = $products;
} else {
$response['message'] = 'سفارشی با این مشخصات یافت نشد.';
}
} catch (PDOException $e) {
error_log("Order tracking PDO error: " . $e->getMessage());
$response['message'] = 'خطا در برقراری ارتباط با سرور.';
}
echo json_encode($response);
} else {
echo json_encode($response);
}
?>

File diff suppressed because it is too large Load Diff

198
assets/css/dark_luxury.css Normal file
View File

@ -0,0 +1,198 @@
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;600;700&display=swap');
:root {
--luxury-bg: #111214;
--luxury-surface: #1a1b1e;
--luxury-text: #eceff1;
--luxury-text-muted: #90a4ae;
--luxury-primary: #c09f80; /* A soft gold for a touch of luxury */
--luxury-border: #37474f;
}
body.dark-luxury {
background-color: var(--luxury-bg);
color: var(--luxury-text);
font-family: 'Vazirmatn', sans-serif;
line-height: 1.8;
}
.dark-luxury h1, .dark-luxury h2, .dark-luxury h3, .dark-luxury h4, .dark-luxury h5, .dark-luxury h6 {
color: var(--luxury-text);
font-weight: 600;
}
.dark-luxury .text-muted {
color: var(--luxury-text-muted) !important;
}
.dark-luxury a {
color: var(--luxury-text);
text-decoration: none;
transition: color 0.3s ease;
}
.dark-luxury a:hover {
color: var(--luxury-primary);
}
.dark-luxury .section-title h1 {
font-size: 3rem;
font-weight: 700;
position: relative;
display: inline-block;
padding-bottom: 0.5rem;
}
.dark-luxury .section-title h1::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 60px;
height: 3px;
background-color: var(--luxury-primary);
}
.dark-luxury .contact-card, .dark-luxury .about-card {
background-color: var(--luxury-surface);
border: 1px solid var(--luxury-border);
border-radius: 15px;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.dark-luxury .contact-card:hover, .dark-luxury .about-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
}
.dark-luxury .form-control {
background-color: var(--luxury-bg);
border: 1px solid var(--luxury-border);
color: var(--luxury-text);
border-radius: 8px;
padding: 0.8rem 1rem;
}
.dark-luxury .form-control:focus {
background-color: var(--luxury-bg);
color: var(--luxury-text);
border-color: var(--luxury-primary);
box-shadow: 0 0 0 0.2rem rgba(192, 159, 128, 0.25);
}
.dark-luxury .form-label {
font-weight: 600;
color: var(--luxury-text-muted);
}
.dark-luxury .btn-primary {
background-color: var(--luxury-primary);
border-color: var(--luxury-primary);
color: #111214;
font-weight: 700;
padding: 0.8rem 2rem;
border-radius: 50px;
transition: all 0.3s ease;
}
.dark-luxury .btn-primary:hover {
background-color: #d4b090;
border-color: #d4b090;
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(192, 159, 128, 0.2);
}
.dark-luxury .contact-info i {
color: var(--luxury-primary);
font-size: 1.5rem;
}
.dark-luxury .contact-info a {
color: var(--luxury-text);
text-decoration: none;
transition: color 0.3s ease;
}
.dark-luxury .contact-info a:hover {
color: var(--luxury-primary);
}
.dark-luxury .about-image {
border-radius: 15px;
object-fit: cover;
}
.dark-luxury .values-card {
background-color: var(--luxury-surface);
border: 1px solid var(--luxury-border);
border-radius: 10px;
padding: 2rem;
}
.dark-luxury .values-card i {
font-size: 2.5rem;
color: var(--luxury-primary);
}
/* Product Grid Styles */
.dark-luxury .product-card {
background-color: var(--luxury-surface);
border: 1px solid var(--luxury-border);
border-radius: 15px;
overflow: hidden;
display: flex;
flex-direction: column;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.dark-luxury .product-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
}
.dark-luxury .product-image {
width: 100%;
aspect-ratio: 3 / 4; /* Enforce 3:4 aspect ratio */
overflow: hidden;
}
.dark-luxury .product-image img {
width: 100%;
height: 100%;
object-fit: cover; /* Crop image to fit, don't distort */
transition: transform 0.4s ease;
}
.dark-luxury .product-card:hover .product-image img {
transform: scale(1.05);
}
.dark-luxury .product-info {
padding: 1.25rem;
flex-grow: 1;
display: flex;
flex-direction: column;
justify-content: center;
}
.dark-luxury .product-title {
font-size: 1.1rem;
font-weight: 600;
margin-bottom: 0.5rem;
}
.dark-luxury .product-title a {
color: var(--luxury-text);
}
.dark-luxury .product-title a:hover {
color: var(--luxury-primary);
}
.dark-luxury .product-price {
font-size: 1.2rem;
font-weight: 700;
color: var(--luxury-primary);
margin-bottom: 0;
}

View File

@ -0,0 +1,115 @@
document.addEventListener('DOMContentLoaded', function () {
const form = document.querySelector('form[action="checkout_handler.php"]');
if (!form) return;
const requiredFields = form.querySelectorAll('[required]');
const validateField = (field) => {
const errorContainer = field.parentElement.querySelector('.invalid-feedback');
if (!errorContainer) return;
let isValid = true;
let errorMessage = '';
if (field.value.trim() === '') {
isValid = false;
errorMessage = 'این فیلد نمی‌تواند خالی باشد.';
} else if (field.type === 'email' && field.value.trim() !== '' && !/^[\S]+@[\S]+\.[\S]+$/.test(field.value)) {
isValid = false;
errorMessage = 'لطفاً یک ایمیل معتبر وارد کنید.';
}
if (!isValid) {
field.classList.add('is-invalid');
errorContainer.textContent = errorMessage;
errorContainer.style.display = 'block';
} else {
field.classList.remove('is-invalid');
errorContainer.style.display = 'none';
}
return isValid;
};
requiredFields.forEach(field => {
// Create a container for the error message if it doesn't exist
let errorContainer = field.parentElement.querySelector('.invalid-feedback');
if (!errorContainer) {
errorContainer = document.createElement('div');
errorContainer.className = 'invalid-feedback';
// Insert after the input field
field.parentNode.insertBefore(errorContainer, field.nextSibling);
}
field.addEventListener('blur', () => {
validateField(field);
});
// Also validate on input to give immediate feedback
field.addEventListener('input', () => {
// Only remove error, don't show it while typing
if (field.classList.contains('is-invalid')) {
validateField(field);
}
});
});
form.addEventListener('submit', function (event) {
let isFormValid = true;
requiredFields.forEach(field => {
if (!validateField(field)) {
isFormValid = false;
}
});
if (!isFormValid) {
event.preventDefault(); // Stop form submission
// Find the first invalid field and focus it for better UX
const firstInvalidField = form.querySelector('.is-invalid');
if(firstInvalidField) {
firstInvalidField.focus();
}
}
});
// Handle address selection logic from the original file
const savedAddressSelect = document.getElementById('saved_address');
if (savedAddressSelect) {
savedAddressSelect.addEventListener('change', function() {
// Clear all fields first
document.getElementById('first_name').value = '';
document.getElementById('last_name').value = '';
document.getElementById('phone_number').value = '';
document.getElementById('province').value = '';
document.getElementById('city').value = '';
document.getElementById('address_line').value = '';
document.getElementById('postal_code').value = '';
// Clear validation states
requiredFields.forEach(field => {
field.classList.remove('is-invalid');
const errorContainer = field.parentElement.querySelector('.invalid-feedback');
if(errorContainer) errorContainer.style.display = 'none';
});
if (this.value) {
try {
const address = JSON.parse(this.value);
document.getElementById('first_name').value = address.first_name || '';
document.getElementById('last_name').value = address.last_name || '';
document.getElementById('phone_number').value = address.phone_number || '';
document.getElementById('province').value = address.province || '';
document.getElementById('city').value = address.city || '';
document.getElementById('address_line').value = address.address_line || '';
document.getElementById('postal_code').value = address.postal_code || '';
// Re-validate all fields after filling them
requiredFields.forEach(field => validateField(field));
} catch (e) {
console.error("Failed to parse address JSON:", e);
}
}
});
}
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View File

@ -12,6 +12,9 @@ switch ($action) {
case 'verify_otp':
handle_verify_otp();
break;
case 'google_login':
handle_google_login();
break;
case 'logout':
handle_logout();
break;
@ -144,4 +147,122 @@ function flash_message($type, $message, $location) {
$_SESSION['flash_message'] = ['type' => $type, 'message' => $message];
header("Location: $location");
exit;
}
function handle_google_login() {
// Load Google credentials from .env
$google_client_id = getenv('GOOGLE_CLIENT_ID');
$google_client_secret = getenv('GOOGLE_CLIENT_SECRET');
// The redirect URI must be the exact same one configured in your Google Cloud project
$redirect_uri = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . strtok($_SERVER["REQUEST_URI"], '?') . '?action=google_login';
if (empty($google_client_id) || empty($google_client_secret)) {
flash_message('danger', 'قابلیت ورود با گوگل هنوز پیکربندی نشده است.', 'login.php');
}
// If 'code' is not in the query string, this is the initial request. Redirect to Google.
if (!isset($_GET['code'])) {
$auth_url = 'https://accounts.google.com/o/oauth2/v2/auth?' . http_build_query([
'client_id' => $google_client_id,
'redirect_uri' => $redirect_uri,
'response_type' => 'code',
'scope' => 'https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile',
'access_type' => 'online',
'prompt' => 'select_account'
]);
header('Location: ' . $auth_url);
exit;
}
// If 'code' is present, this is the callback from Google.
else {
try {
// Step 1: Exchange authorization code for an access token
$token_url = 'https://oauth2.googleapis.com/token';
$token_data = [
'code' => $_GET['code'],
'client_id' => $google_client_id,
'client_secret' => $google_client_secret,
'redirect_uri' => $redirect_uri,
'grant_type' => 'authorization_code'
];
$token_response = curl_request($token_url, 'POST', $token_data);
if (!isset($token_response['access_token'])) {
throw new Exception("Failed to get access token from Google. Response: " . json_encode($token_response));
}
// Step 2: Use access token to get user's profile information
$userinfo_url = 'https://www.googleapis.com/oauth2/v1/userinfo?access_token=' . $token_response['access_token'];
$userinfo = curl_request($userinfo_url);
if (!isset($userinfo['email'])) {
throw new Exception("Failed to get user info from Google. Response: " . json_encode($userinfo));
}
// Step 3: Log in or create user
$email = filter_var($userinfo['email'], FILTER_VALIDATE_EMAIL);
$first_name = $userinfo['given_name'] ?? '';
$last_name = $userinfo['family_name'] ?? '';
$pdo = db();
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = ?");
$stmt->execute([$email]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
$user_id = null;
if ($user) {
// User exists, log them in
$user_id = $user['id'];
// Update name if it's missing
if (empty($user['first_name']) && !empty($first_name)) {
$stmt_update = $pdo->prepare("UPDATE users SET first_name = ?, last_name = ? WHERE id = ?");
$stmt_update->execute([$first_name, $last_name, $user_id]);
}
} else {
// User does not exist, create a new one
$stmt_create = $pdo->prepare("INSERT INTO users (email, first_name, last_name, is_admin) VALUES (?, ?, ?, 0)");
$stmt_create->execute([$email, $first_name, $last_name]);
$user_id = $pdo->lastInsertId();
}
// Set session variables for login
$_SESSION['user_id'] = $user_id;
$_SESSION['user_name'] = $first_name;
unset($_SESSION['otp_email']); // Clean up OTP session if it exists
flash_message('success', 'شما با موفقیت با حساب گوگل وارد شدید!', 'index.php');
} catch (Exception $e) {
error_log('Google Login Error: ' . $e->getMessage());
flash_message('danger', 'خطایی در فرآیند ورود با گوگل رخ داد. لطفاً دوباره تلاش کنید.', 'login.php');
}
}
}
function curl_request($url, $method = 'GET', $data = []) {
$ch = curl_init();
if ($method === 'POST') {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
}
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
// Set a common user agent
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36');
$response = curl_exec($ch);
if (curl_errno($ch)) {
$error_msg = curl_error($ch);
curl_close($ch);
throw new Exception("cURL Error: " . $error_msg);
}
curl_close($ch);
return json_decode($response, true);
}

View File

@ -24,19 +24,40 @@ $cart_item_id = $product_id . ($color ? '_' . str_replace('#', '', $color) : '')
switch ($action) {
case 'add':
if ($quantity > 0) {
// Check if product exists and get details
try {
$pdo = db();
$stmt = $pdo->prepare("SELECT name, price, image_url FROM products WHERE id = ?");
// Fetch product details including colors
$stmt = $pdo->prepare("SELECT name, price, image_url, colors FROM products WHERE id = ?");
$stmt->execute([$product_id]);
$product = $stmt->fetch(PDO::FETCH_ASSOC);
if ($product) {
// If item already in cart, update quantity
// --- START COLOR VALIDATION ---
$available_colors = [];
if (!empty($product['colors'])) {
$colors_raw = explode(',', $product['colors']);
foreach ($colors_raw as $c) {
$trimmed_c = trim($c);
if ($trimmed_c) $available_colors[] = $trimmed_c;
}
}
if (count($available_colors) > 1 && empty($color)) {
// For multi-color products, a color must be selected.
$_SESSION['flash_message'] = [
'type' => 'warning',
'message' => 'برای افزودن این محصول، انتخاب یکی از رنگ‌ها الزامی است.'
];
header('Location: product.php?id=' . $product_id);
exit;
}
// --- END COLOR VALIDATION ---
// If item is already in the cart (same product ID and color), just update the quantity.
if (isset($_SESSION['cart'][$cart_item_id])) {
$_SESSION['cart'][$cart_item_id]['quantity'] += $quantity;
} else {
// Otherwise, add new item
// Otherwise, add the new item to the cart.
$_SESSION['cart'][$cart_item_id] = [
'product_id' => $product_id,
'name' => $product['name'],
@ -46,13 +67,24 @@ switch ($action) {
'color' => $color
];
}
$_SESSION['flash_message'] = [
'type' => 'success',
'message' => 'محصول با موفقیت به سبد خرید اضافه شد!'
];
} else {
$_SESSION['flash_message'] = ['type' => 'error', 'message' => 'محصول یافت نشد.'];
}
} catch (PDOException $e) {
// Log error, maybe set a session error message to display in cart
error_log("Cart Add Error: " . $e->getMessage());
$_SESSION['flash_message'] = [
'type' => 'error',
'message' => 'مشکلی در افزودن محصول به سبد خرید رخ داد.'
];
}
}
break;
// Redirect back to the previous page (likely the product page) to show the flash message.
header('Location: ' . ($_SERVER['HTTP_REFERER'] ?? 'shop.php'));
exit;
case 'update':
if ($quantity > 0) {
@ -60,7 +92,7 @@ switch ($action) {
$_SESSION['cart'][$cart_item_id]['quantity'] = $quantity;
}
} else {
// If quantity is 0 or less, remove the item
// If quantity is 0 or less, remove the item.
unset($_SESSION['cart'][$cart_item_id]);
}
break;
@ -72,6 +104,6 @@ switch ($action) {
break;
}
// Redirect back to the cart page to show changes
// For 'update' and 'remove' actions, redirect to the cart page to show changes.
header('Location: cart.php');
exit;

View File

@ -76,14 +76,17 @@ require_once 'includes/header.php';
<div class="col-md-6">
<label for="first_name" class="form-label">نام</label>
<input type="text" class="form-control bg-dark text-light" id="first_name" name="first_name" value="<?php echo htmlspecialchars($logged_in_user['first_name'] ?? ''); ?>" required>
<div class="invalid-feedback"></div>
</div>
<div class="col-md-6">
<label for="last_name" class="form-label">نام خانوادگی</label>
<input type="text" class="form-control bg-dark text-light" id="last_name" name="last_name" value="<?php echo htmlspecialchars($logged_in_user['last_name'] ?? ''); ?>" required>
<div class="invalid-feedback"></div>
</div>
<div class="col-md-6">
<label for="phone_number" class="form-label">تلفن همراه</label>
<input type="tel" class="form-control bg-dark text-light" id="phone_number" name="phone_number" value="<?php echo htmlspecialchars($logged_in_user['phone_number'] ?? ''); ?>" required>
<div class="invalid-feedback"></div>
<?php if (!$is_logged_in): ?>
<div class="form-text text-info fw-bold">توجه: فقط با شماره تلفن همراه میتوان سفارش را رهگیری کرد.</div>
<?php endif; ?>
@ -91,22 +94,27 @@ require_once 'includes/header.php';
<div class="col-md-6">
<label for="province" class="form-label">استان</label>
<input type="text" class="form-control bg-dark text-light" id="province" name="province" required>
<div class="invalid-feedback"></div>
</div>
<div class="col-md-6">
<label for="city" class="form-label">شهر</label>
<input type="text" class="form-control bg-dark text-light" id="city" name="city" required>
<div class="invalid-feedback"></div>
</div>
<div class="col-md-6">
<label for="address_line" class="form-label">آدرس دقیق</label>
<textarea class="form-control bg-dark text-light" id="address_line" name="address_line" rows="2" required></textarea>
<div class="invalid-feedback"></div>
</div>
<div class="col-md-5">
<label for="postal_code" class="form-label">کد پستی</label>
<input type="text" class="form-control bg-dark text-light" id="postal_code" name="postal_code" required>
<div class="invalid-feedback"></div>
</div>
<div class="col-md-7">
<label for="email" class="form-label">ایمیل (اختیاری)</label>
<input type="email" class="form-control bg-dark text-light" id="email" name="email" value="<?php echo htmlspecialchars($logged_in_user['email'] ?? ''); ?>">
<div class="invalid-feedback"></div>
</div>
</div>
<div class="form-check mt-4">
@ -114,6 +122,7 @@ require_once 'includes/header.php';
<label class="form-check-label" for="terms">
با <a href="#">قوانین و مقررات</a> سایت موافقم.
</label>
<div class="invalid-feedback">لطفاً قوانین و مقررات را بپذیرید.</div>
</div>
</div>
</div>
@ -132,6 +141,12 @@ require_once 'includes/header.php';
<div>
<?php echo htmlspecialchars($item['name']); ?>
<small class="d-block text-muted">تعداد: <?php echo $item['quantity']; ?></small>
<?php if (!empty($item['color'])): ?>
<div class="d-flex align-items-center gx-2 mt-1">
<small class="text-muted">رنگ:</small>
<span class="d-inline-block rounded-circle border ms-2" style="width: 15px; height: 15px; background-color: <?php echo htmlspecialchars($item['color']); ?>;"></span>
</div>
<?php endif; ?>
</div>
</div>
<span class="fw-bold"><?php echo number_format($item['price'] * $item['quantity']); ?></span>
@ -165,31 +180,6 @@ require_once 'includes/header.php';
</form>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const savedAddressSelect = document.getElementById('saved_address');
if (savedAddressSelect) {
savedAddressSelect.addEventListener('change', function() {
if (this.value) {
try {
const address = JSON.parse(this.value);
document.getElementById('province').value = address.province || '';
document.getElementById('city').value = address.city || '';
document.getElementById('address_line').value = address.address_line || '';
document.getElementById('postal_code').value = address.postal_code || '';
} catch (e) {
console.error("Failed to parse address JSON:", e);
}
} else {
// Clear fields if no address is selected
document.getElementById('province').value = '';
document.getElementById('city').value = '';
document.getElementById('address_line').value = '';
document.getElementById('postal_code').value = '';
}
});
}
});
</script>
<script src="assets/js/checkout_validation.js?v=<?php echo time(); ?>"></script>
<?php require_once 'includes/footer.php'; ?>

View File

@ -40,16 +40,16 @@ try {
$total_amount = array_reduce($cart_items, function ($sum, $item) {
return $sum + ($item['price'] * $item['quantity']);
}, 0);
$items_json = json_encode($cart_items, JSON_UNESCAPED_UNICODE);
$products_data_json = json_encode($cart_items, JSON_UNESCAPED_UNICODE);
// 5. Insert the order into the database using the correct, updated column names
$stmt = $pdo->prepare(
"INSERT INTO orders (user_id, billing_name, billing_email, customer_phone, shipping_province, shipping_city, shipping_address_line, shipping_postal_code, total_amount, items_json, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
"INSERT INTO orders (user_id, billing_name, billing_email, billing_phone, billing_province, billing_city, billing_address, billing_postal_code, total_amount, items_json, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
);
$user_id = isset($_SESSION['user_id']) ? $_SESSION['user_id'] : null;
$status = 'Pending'; // Default status
$final_email = ($email !== false && $email !== '') ? $email : null; // Ensure email is null if empty/invalid, not false
$status = 'Processing'; // Default status
$final_email = ($email !== false && $email !== '') ? $email : null;
$stmt->execute([
$user_id,
@ -61,10 +61,16 @@ try {
$address_line,
$postal_code,
$total_amount,
$items_json,
$products_data_json,
$status
]);
$order_id = $pdo->lastInsertId();
$tracking_id = uniqid('ATMH-');
$update_stmt = $pdo->prepare("UPDATE orders SET tracking_id = ? WHERE id = ?");
$update_stmt->execute([$tracking_id, $order_id]);
// 6. If user is logged in, save the new address for future use
if ($user_id) {
$stmt_check_addr = $pdo->prepare("SELECT COUNT(*) FROM user_addresses WHERE user_id = ? AND address_line = ? AND postal_code = ?");
@ -84,9 +90,12 @@ try {
// 7. Commit transaction
$pdo->commit();
// 8. Clear the cart and redirect with a success message
// 8. Clear the cart and redirect with a success message including tracking ID
unset($_SESSION['cart']);
$_SESSION['success_message'] = 'سفارش شما با موفقیت ثبت شد! از خرید شما متشکریم.';
$_SESSION['success_message'] = "سفارش شما با موفقیت ثبت شد! کد پیگیری شما: <strong>" . htmlspecialchars($tracking_id) . "</strong>";
// As I don't have SMS capability, I am displaying the tracking code here.
// You can later integrate an SMS service and send the tracking ID to $phone_number.
header('Location: index.php');
exit;

View File

@ -15,10 +15,8 @@ if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST['contact_form'])) {
} elseif (!$email) {
$_SESSION['flash_message'] = ['type' => 'error', 'message' => 'آدرس ایمیل وارد شده معتبر نیست.'];
} else {
// Send email using MailService
$to_email = getenv('MAIL_TO') ?: 'your-default-email@example.com'; // Fallback email
$to_email = getenv('MAIL_TO') ?: 'support@atimeh.com';
$subject = "پیام جدید از فرم تماس وب‌سایت";
$email_result = MailService::sendContactMessage($name, $email, $message, $to_email, $subject);
if (!empty($email_result['success'])) {
@ -29,64 +27,101 @@ if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST['contact_form'])) {
}
}
// Redirect to the same page to prevent form resubmission
header("Location: contact.php");
exit();
}
// Check for flash messages
$flash_message = $_SESSION['flash_message'] ?? null;
if ($flash_message) {
unset($_SESSION['flash_message']);
}
?>
<main class="container py-5">
<div class="row justify-content-center">
<div class="col-lg-8">
<div class="text-center mb-5" data-aos="fade-down">
<h1 class="display-4 fw-bold">ارتباط با ما</h1>
<p class="fs-5 text-muted">نظرات، پیشنهادات و سوالات شما برای ما ارزشمند است.</p>
</div>
<div class="container py-5 my-5">
<div class="section-title text-center mb-5" data-aos="fade-down">
<h1>ارتباط با ما</h1>
<p class="fs-5 text-muted">نظرات، پیشنهادات و سوالات شما برای ما ارزشمند است.</p>
</div>
<div class="card border-0 shadow-lg" data-aos="fade-up">
<div class="card-body p-4 p-md-5">
<form action="contact.php" method="POST">
<input type="hidden" name="contact_form">
<div class="mb-4">
<label for="name" class="form-label fs-5">نام شما</label>
<input type="text" class="form-control form-control-lg bg-dark" id="name" name="name" required>
<div class="contact-card p-4 p-lg-5" data-aos="fade-up">
<div class="row g-5">
<div class="col-lg-5">
<div class="contact-info h-100 d-flex flex-column justify-content-center">
<h3 class="mb-4">راه‌های ارتباطی</h3>
<div class="d-flex align-items-start mb-4">
<i class="fas fa-map-marker-alt mt-1 me-3"></i>
<div>
<strong>آدرس:</strong>
<p class="text-muted mb-0">تهران، خیابان هنر، کوچه خلاقیت، پلاک ۱۲</p>
</div>
<div class="mb-4">
<label for="email" class="form-label fs-5">ایمیل</label>
<input type="email" class="form-control form-control-lg bg-dark" id="email" name="email" required>
</div>
<div class="d-flex align-items-start mb-4">
<i class="fas fa-envelope mt-1 me-3"></i>
<div>
<strong>ایمیل:</strong>
<p class="mb-0"><a href="mailto:info@atimeh.com">info@atimeh.com</a></p>
</div>
<div class="mb-4">
<label for="message" class="form-label fs-5">پیام شما</label>
<textarea class="form-control form-control-lg bg-dark" id="message" name="message" rows="6" required></textarea>
</div>
<div class="d-flex align-items-start mb-4">
<i class="fas fa-phone-alt mt-1 me-3"></i>
<div>
<strong>تلفن:</strong>
<p class="mb-0"><a href="tel:+982112345678">۰۲۱-۱۲۳۴۵۶۷۸</a></p>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary btn-lg">ارسال پیام</button>
</div>
</form>
</div>
<hr class="my-4" style="border-color: var(--luxury-border);">
<h4 class="h5 mb-3">ما را دنبال کنید</h4>
<div class="d-flex">
<a href="#" class="btn btn-outline-primary rounded-circle me-2" style="width: 40px; height: 40px; line-height: 25px; text-align: center; padding: 5px;"><i class="fab fa-instagram"></i></a>
<a href="#" class="btn btn-outline-primary rounded-circle me-2" style="width: 40px; height: 40px; line-height: 25px; text-align: center; padding: 5px;"><i class="fab fa-telegram"></i></a>
<a href="#" class="btn btn-outline-primary rounded-circle" style="width: 40px; height: 40px; line-height: 25px; text-align: center; padding: 5px;"><i class="fab fa-whatsapp"></i></a>
</div>
</div>
</div>
<div class="col-lg-7">
<h3 class="mb-4">فرم تماس</h3>
<form action="contact.php" method="POST">
<input type="hidden" name="contact_form">
<div class="mb-4">
<label for="name" class="form-label">نام شما</label>
<input type="text" class="form-control" id="name" name="name" required>
</div>
<div class="mb-4">
<label for="email" class="form-label">ایمیل</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<div class="mb-4">
<label for="message" class="form-label">پیام شما</label>
<textarea class="form-control" id="message" name="message" rows="7" required></textarea>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary">ارسال پیام</button>
</div>
</form>
</div>
</div>
</div>
</main>
</div>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
<?php if ($flash_message): ?>
Swal.fire({
const swalConfig = {
title: '<?php echo $flash_message["type"] === "success" ? "موفق" : "خطا"; ?>',
text: '<?php echo addslashes($flash_message["message"]); ?>',
icon: '<?php echo $flash_message["type"]; ?>',
confirmButtonText: 'باشه',
background: '#2C2C2C',
color: '#D5D5D5'
});
};
// Apply dark theme to SweetAlert2
if (document.body.classList.contains('dark-luxury')) {
swalConfig.background = 'var(--luxury-surface)';
swalConfig.color = 'var(--luxury-text)';
swalConfig.confirmButtonColor = 'var(--luxury-primary)';
}
Swal.fire(swalConfig);
<?php endif; ?>
});
</script>

View File

@ -0,0 +1 @@
ALTER TABLE `orders` ADD `tracking_id` VARCHAR(255) UNIQUE NULL DEFAULT NULL AFTER `status`;

View File

@ -0,0 +1 @@
ALTER TABLE `orders` CHANGE `customer_phone` `billing_phone` VARCHAR(50) DEFAULT NULL;

View File

@ -0,0 +1,6 @@
-- Rename shipping_* columns to billing_* to match the application logic
ALTER TABLE `orders`
CHANGE `shipping_province` `billing_province` VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
CHANGE `shipping_city` `billing_city` VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
CHANGE `shipping_address_line` `billing_address` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
CHANGE `shipping_postal_code` `billing_postal_code` VARCHAR(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL;

View File

@ -0,0 +1,16 @@
-- Convert the orders table and its text-based columns to utf8mb4 to support Persian characters
ALTER TABLE `orders` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- Additionally, ensure individual text columns are correctly set
ALTER TABLE `orders`
MODIFY `billing_name` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
MODIFY `billing_email` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
MODIFY `legacy_customer_address` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
MODIFY `billing_phone` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
MODIFY `billing_province` VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
MODIFY `billing_city` VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
MODIFY `billing_address` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
MODIFY `billing_postal_code` VARCHAR(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
MODIFY `items_json` LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
MODIFY `status` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
MODIFY `tracking_id` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

View File

@ -0,0 +1,6 @@
CREATE TABLE IF NOT EXISTS `page_views` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`view_timestamp` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`page_url` VARCHAR(2048) NOT NULL,
`ip_address` VARCHAR(45) NOT NULL
);

View File

@ -0,0 +1,7 @@
-- Add indexes to the orders table to improve query performance for the dashboard
-- Index for the status column, as it's frequently used in WHERE clauses
ALTER TABLE `orders` ADD INDEX `idx_status` (`status`);
-- Index for the order date column, as it's used for grouping and filtering by date
ALTER TABLE `orders` ADD INDEX `idx_created_at` (`created_at`);

View File

@ -2,8 +2,29 @@
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Log page views for non-admin pages
if (strpos($_SERVER['REQUEST_URI'], '/admin/') === false) {
require_once __DIR__ . '/../db/config.php';
try {
$pdo = db();
// Check if the table exists to avoid errors before migration
$table_check = $pdo->query("SHOW TABLES LIKE 'page_views'");
if ($table_check->rowCount() > 0) {
$ip_address = $_SERVER['REMOTE_ADDR'];
$page_url = $_SERVER['REQUEST_URI'];
$stmt = $pdo->prepare("INSERT INTO page_views (page_url, ip_address) VALUES (?, ?)");
$stmt->execute([$page_url, $ip_address]);
}
} catch (PDOException $e) {
// Silently fail or log to a file to not break the page for users
error_log("Could not log page view: " . $e->getMessage());
}
}
$cart_item_count = isset($_SESSION['cart']) ? array_sum(array_column($_SESSION['cart'], 'quantity')) : 0;
$page_title = $page_title ?? 'فروشگاه آتیمه'; // Default title
?>
<!DOCTYPE html>
<html lang="fa" dir="rtl">
@ -18,6 +39,28 @@ $page_title = $page_title ?? 'فروشگاه آتیمه'; // Default title
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700&display=swap" rel="stylesheet">
<!-- Bootstrap Variable Overrides for Dark Luxury Theme -->
<style>
:root {
--luxury-bg: #111214;
--luxury-surface: #1a1b1e;
--luxury-text: #eceff1;
--luxury-text-muted: #90a4ae;
--luxury-primary: #c09f80;
--luxury-border: #37474f;
/* Override Bootstrap's root variables */
--bs-body-bg: var(--luxury-bg);
--bs-body-color: var(--luxury-text);
--bs-primary: var(--luxury-primary);
--bs-primary-rgb: 192, 159, 128;
--bs-link-color: var(--luxury-primary);
--bs-link-hover-color: #d4b090; /* A lighter gold for hover */
--bs-border-color: var(--luxury-border);
--bs-tertiary-bg: var(--luxury-surface);
}
</style>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.rtl.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
@ -29,6 +72,8 @@ $page_title = $page_title ?? 'فروشگاه آتیمه'; // Default title
<!-- Custom CSS -->
<link rel="stylesheet" href="/assets/css/custom.css?v=<?php echo time(); ?>">
<link rel="stylesheet" href="/assets/css/dark_luxury.css?v=<?php echo time(); ?>">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<script>
// Apply theme from local storage before page load to prevent flashing
@ -43,10 +88,9 @@ $page_title = $page_title ?? 'فروشگاه آتیمه'; // Default title
</script>
</head>
<body>
<body class="dark-luxury">
<div class="overflow-hidden">
<?php
$current_page = basename($_SERVER['PHP_SELF']);
$is_admin_page = strpos($_SERVER['REQUEST_URI'], '/admin/') !== false;
?>

24
includes/jdf.php Normal file
View File

@ -0,0 +1,24 @@
<?php
// Jalali (Shamsi) to Gregorian and vice-versa converter in PHP
// Original source: https://github.com/moradin/Jalali/blob/master/jdf.php
// Simplified for this project.
function gregorian_to_jalali($gy, $gm, $gd, $mod='') {
$g_d_m = array(0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334);
$jy = ($gy <= 1600) ? 0 : 979;
$gy -= ($gy <= 1600) ? 621 : 1600;
$gy2 = ($gm > 2) ? ($gy + 1) : $gy;
$days = (365 * $gy) + ((int)(($gy2 + 3) / 4)) - ((int)(($gy2 + 99) / 100)) + ((int)(($gy2 + 399) / 400)) - 80 + $gd + $g_d_m[$gm - 1];
$jy += 33 * ((int)($days / 12053));
$days %= 12053;
$jy += 4 * ((int)($days / 1461));
$days %= 1461;
$jy += (int)(($days - 1) / 365);
if ($days > 365) $days = ($days - 1) % 365;
$jm = ($days < 186) ? 1 + (int)($days / 31) : 7 + (int)(($days - 186) / 30);
$jd = 1 + (($days < 186) ? ($days % 31) : (($days - 186) % 30));
return ($mod == '') ? array($jy, $jm, $jd) : $jy . $mod . $jm . $mod . $jd;
}
?>

View File

@ -65,10 +65,10 @@ $page_title = "ورود یا ثبت‌نام";
<div class="separator my-4"><span>یا</span></div>
<div class="d-grid">
<button class="btn btn-google" disabled>
<a href="auth_handler.php?action=google_login" class="btn btn-google">
<svg class="me-2" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 48 48"><path fill="#EA4335" d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z"/><path fill="#4285F4" d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z"/><path fill="#FBBC05" d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z"/><path fill="#34A853" d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z"/><path fill="none" d="M0 0h48v48H0z"/></svg>
ورود با گوگل (به زودی)
</button>
ورود با گوگل
</a>
</div>
<div class="auth-footer">

View File

@ -32,10 +32,16 @@ if (!$product) {
// Set page title after fetching product name
$page_title = htmlspecialchars($product['name']);
// Safely decode colors JSON
$available_colors = json_decode($product['colors'] ?? '[]', true);
if (json_last_error() !== JSON_ERROR_NONE) {
$available_colors = []; // Assign empty array if JSON is invalid
// Parse comma-separated colors string
$available_colors = [];
if (!empty($product['colors'])) {
$colors_raw = explode(',', $product['colors']);
foreach ($colors_raw as $color) {
$trimmed_color = trim($color);
if (!empty($trimmed_color)) {
$available_colors[] = $trimmed_color;
}
}
}
?>
@ -66,7 +72,7 @@ if (json_last_error() !== JSON_ERROR_NONE) {
<h5 class="mb-3">انتخاب رنگ:</h5>
<div class="color-swatches">
<?php foreach ($available_colors as $index => $color_hex): ?>
<input type="radio" class="btn-check" name="product_color" id="color_<?php echo $index; ?>" value="<?php echo htmlspecialchars($color_hex); ?>" <?php echo ($index === 0) ? 'checked' : ''; ?>>
<input type="radio" class="btn-check" name="product_color" id="color_<?php echo $index; ?>" value="<?php echo htmlspecialchars($color_hex); ?>" <?php echo (count($available_colors) === 1) ? 'checked' : ''; ?>>
<label class="btn" for="color_<?php echo $index; ?>" style="background-color: <?php echo htmlspecialchars($color_hex); ?>;" title="<?php echo htmlspecialchars($color_hex); ?>"></label>
<?php endforeach; ?>
</div>
@ -89,4 +95,103 @@ if (json_last_error() !== JSON_ERROR_NONE) {
</div>
</main>
<!-- SweetAlert for color validation -->
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
// --- 1. Flash Message Handling (from server-side) ---
<?php
if (isset($_SESSION['flash_message'])) {
$flash_message = $_SESSION['flash_message'];
unset($_SESSION['flash_message']);
echo "Swal.fire({
title: '" . addslashes($flash_message['message']) . "',
icon: '" . $flash_message['type'] . "',
toast: true,
position: 'top-start',
showConfirmButton: false,
timer: 4000,
timerProgressBar: true,
didOpen: (toast) => {
toast.onmouseenter = Swal.stopTimer;
toast.onmouseleave = Swal.resumeTimer;
},
customClass: {
popup: 'dark-theme-toast'
}
});";
}
?>
// --- 2. Client-side Color Selection Validation ---
const form = document.querySelector('form[action="cart_handler.php"]');
if (form) {
form.addEventListener('submit', function(event) {
const availableColors = <?php echo json_encode($available_colors); ?>;
const hasMultipleColors = Array.isArray(availableColors) && availableColors.length > 1;
if (hasMultipleColors) {
const selectedColor = document.querySelector('input[name="product_color"]:checked');
if (!selectedColor) {
event.preventDefault(); // Stop form submission
Swal.fire({
title: 'لطفاً یک رنگ انتخاب کنید',
text: 'برای افزودن این محصول به سبد خرید، انتخاب رنگ الزامی است.',
icon: 'warning',
confirmButtonText: 'متوجه شدم',
customClass: {
popup: 'dark-theme-popup',
title: 'dark-theme-title',
htmlContainer: 'dark-theme-content',
confirmButton: 'dark-theme-button'
}
});
}
}
});
}
});
</script>
<style>
body.swal2-shown > [aria-hidden="true"] {
filter: blur(5px);
transition: filter 0.3s ease-out;
}
.swal2-popup.dark-theme-popup {
background-color: #2a2a2e !important;
border-radius: 20px;
}
.swal2-title.dark-theme-title {
color: #e8e6e3 !important;
}
.swal2-html-container.dark-theme-content {
color: #b0b0b0 !important;
}
.swal2-confirm.dark-theme-button {
background-color: var(--primary-color) !important;
border-radius: 10px;
padding: .6em 2em;
box-shadow: none !important;
transition: background-color 0.2s;
}
.swal2-confirm.dark-theme-button:hover {
background-color: #c89c6c !important; /* A slightly lighter shade of primary for hover */
}
/* Toast Styles */
.swal2-toast.dark-theme-toast {
background-color: #2a2a2e !important;
color: #e8e6e3 !important;
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}
.swal2-toast.dark-theme-toast .swal2-title {
color: #e8e6e3 !important;
font-size: 1em;
}
.swal2-toast.dark-theme-toast .swal2-timer-progress-bar {
background-color: var(--primary-color);
}
</style>
<?php require_once 'includes/footer.php'; ?>

View File

@ -1,114 +1,299 @@
<?php
session_start();
require_once 'db/config.php';
require_once 'includes/jdf.php'; // For Jalali date conversion
// Require user to be logged in
if (!isset($_SESSION['user_id'])) {
header('Location: login.php');
exit;
}
require_once 'db/config.php';
$user_id = $_SESSION['user_id'];
$pdo = db();
// Fetch user data
try {
$pdo = db();
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$user_id]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$user_id]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
// Fetch user addresses
$stmt = $pdo->prepare("SELECT * FROM user_addresses WHERE user_id = ? ORDER BY is_default DESC, id DESC");
$stmt->execute([$user_id]);
$addresses = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Fetch user addresses
$stmt_addresses = $pdo->prepare("SELECT * FROM user_addresses WHERE user_id = ? ORDER BY is_default DESC, id DESC");
$stmt_addresses->execute([$user_id]);
$addresses = $stmt_addresses->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
die("Error fetching user data: " . $e->getMessage());
}
// Fetch user orders with items
$stmt_orders = $pdo->prepare("SELECT * FROM orders WHERE user_id = ? ORDER BY created_at DESC");
$stmt_orders->execute([$user_id]);
$orders = $stmt_orders->fetchAll(PDO::FETCH_ASSOC);
$page_title = 'حساب کاربری من';
$page_title = 'حساب کاربری';
require_once 'includes/header.php';
?>
<style>
body {
background-color: #f4f7f6;
}
.profile-container {
display: flex;
gap: 30px;
}
.profile-sidebar {
flex: 0 0 280px;
background-color: #fff;
border-radius: 15px;
box-shadow: 0 4px 25px rgba(0,0,0,0.05);
padding: 20px;
align-self: flex-start;
}
.profile-content {
flex: 1;
}
.user-card {
text-align: center;
padding: 20px 10px;
border-bottom: 1px solid #eee;
margin-bottom: 20px;
}
.user-card .user-avatar {
width: 90px;
height: 90px;
border-radius: 50%;
background-color: var(--bs-primary);
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 2.5rem;
margin: 0 auto 15px auto;
font-weight: bold;
}
.user-card h5 {
font-weight: 600;
margin-bottom: 5px;
}
.user-card p {
color: #888;
font-size: 0.9rem;
}
.profile-nav .nav-link {
color: #6c757d;
border-bottom: 2px solid transparent;
display: flex;
align-items: center;
gap: 12px;
padding: 12px 15px;
border-radius: 10px;
color: #555;
font-weight: 500;
transition: all 0.3s ease;
margin-bottom: 5px;
}
.profile-nav .nav-link.active {
.profile-nav .nav-link i {
font-size: 1.3rem;
color: #888;
transition: all 0.3s ease;
}
.profile-nav .nav-link.active,
.profile-nav .nav-link:hover {
background-color: var(--bs-primary-light);
color: var(--bs-primary);
border-bottom-color: var(--bs-primary);
}
.profile-nav .nav-link.active i,
.profile-nav .nav-link:hover i {
color: var(--bs-primary);
}
.tab-pane h3 {
font-weight: 700;
margin-bottom: 25px;
color: #333;
}
/* Order Accordion Styles */
.order-accordion .accordion-item {
border: none;
border-radius: 15px;
margin-bottom: 20px;
box-shadow: 0 4px 25px rgba(0,0,0,0.05);
background-color: #fff;
}
.order-accordion .accordion-button {
border-radius: 15px !important;
background-color: #fff;
box-shadow: none;
padding: 20px;
}
.order-accordion .accordion-button:not(.collapsed) {
border-bottom: 1px solid #eee;
}
.order-header {
display: flex;
justify-content: space-between;
width: 100%;
align-items: center;
}
.order-header-item {
flex: 1;
text-align: right;
font-size: 0.9rem;
}
.order-header-item:first-child { text-align: right; }
.order-header-item span {
display: block;
font-size: 0.8rem;
color: #888;
}
.order-header-item strong {
font-weight: 600;
color: #333;
font-size: 1rem;
}
.order-status {
padding: 5px 12px;
border-radius: 20px;
font-weight: 500;
color: #fff;
font-size: 0.8rem;
}
.order-details-table {
margin-top: 15px;
}
.order-details-table img {
width: 60px;
height: 60px;
object-fit: cover;
border-radius: 10px;
}
.product-color-swatch {
display: inline-block;
width: 20px;
height: 20px;
border-radius: 50%;
border: 1px solid #eee;
vertical-align: middle;
}
</style>
<div class="container my-5">
<div class="row">
<div class="col-md-4">
<div class="card text-center p-3">
<i class="ri-user-smile-line fs-1 text-primary mb-3"></i>
<h4 class="card-title"><?php echo htmlspecialchars($user['first_name'] . ' ' . $user['last_name']); ?></h4>
<p class="text-muted"><?php echo htmlspecialchars($user['email']); ?></p>
</div>
<div class="card mt-4">
<div class="card-body">
<h5 class="card-title mb-3">افزودن آدرس جدید</h5>
<form action="#" method="POST"> <!-- Will be handled by a future handler -->
<div class="mb-3">
<label for="province" class="form-label">استان</label>
<input type="text" class="form-control" id="province" name="province" required>
</div>
<div class="mb-3">
<label for="city" class="form-label">شهر</label>
<input type="text" class="form-control" id="city" name="city" required>
</div>
<div class="mb-3">
<label for="address_line" class="form-label">آدرس دقیق</label>
<textarea class="form-control" id="address_line" name="address_line" rows="3" required></textarea>
</div>
<div class="mb-3">
<label for="postal_code" class="form-label">کد پستی</label>
<input type="text" class="form-control" id="postal_code" name="postal_code" required>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-secondary">ثبت آدرس</button>
</div>
</form>
<div class="profile-container">
<!-- Profile Sidebar -->
<div class="profile-sidebar">
<div class="user-card">
<div class="user-avatar">
<?php echo strtoupper(substr($user['first_name'], 0, 1)); ?>
</div>
<h5><?php echo htmlspecialchars($user['first_name'] . ' ' . $user['last_name']); ?></h5>
<p><?php echo htmlspecialchars($user['email']); ?></p>
</div>
<ul class="nav flex-column profile-nav" id="profileTab" role="tablist">
<li class="nav-item">
<a class="nav-link active" id="orders-tab" data-bs-toggle="tab" href="#orders" role="tab" aria-controls="orders" aria-selected="true">
<i class="ri-shopping-bag-3-line"></i>
سفارشات من
</a>
</li>
<li class="nav-item">
<a class="nav-link" id="addresses-tab" data-bs-toggle="tab" href="#addresses" role="tab" aria-controls="addresses" aria-selected="false">
<i class="ri-map-pin-line"></i>
آدرس‌های من
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="logout.php">
<i class="ri-logout-box-r-line"></i>
خروج از حساب
</a>
</li>
</ul>
</div>
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h5 class="mb-0">آدرس‌های من</h5>
</div>
<div class="card-body">
<?php if (empty($addresses)): ?>
<p class="text-center text-muted">شما هنوز هیچ آدرسی ثبت نکرده‌اید.</p>
<!-- Profile Content -->
<div class="profile-content">
<div class="tab-content" id="profileTabContent">
<!-- Orders Tab -->
<div class="tab-pane fade show active" id="orders" role="tabpanel" aria-labelledby="orders-tab">
<h3>تاریخچه سفارشات</h3>
<?php if (empty($orders)): ?>
<div class="alert alert-light text-center">شما هنوز هیچ سفارشی ثبت نکرده‌اید.</div>
<?php else: ?>
<div class="list-group">
<?php foreach ($addresses as $address): ?>
<div class="list-group-item list-group-item-action flex-column align-items-start">
<div class="d-flex w-100 justify-content-between">
<h6 class="mb-1">
<?php echo htmlspecialchars($address['province'] . '، ' . $address['city']); ?>
<?php if($address['is_default']): ?>
<span class="badge bg-primary">پیش‌فرض</span>
<div class="accordion order-accordion" id="ordersAccordion">
<?php foreach ($orders as $index => $order): ?>
<?php
$items = json_decode($order['items_json'], true);
$status_map = [
'pending' => ['label' => 'در انتظار پرداخت', 'color' => '#ffc107'],
'processing' => ['label' => 'در حال پردازش', 'color' => '#0dcaf0'],
'shipped' => ['label' => 'ارسال شده', 'color' => '#0d6efd'],
'completed' => ['label' => 'تکمیل شده', 'color' => '#198754'],
'cancelled' => ['label' => 'لغو شده', 'color' => '#dc3545'],
];
$status_info = $status_map[$order['status']] ?? ['label' => htmlspecialchars($order['status']), 'color' => '#6c757d'];
?>
<div class="accordion-item">
<h2 class="accordion-header" id="heading<?php echo $order['id']; ?>">
<button class="accordion-button <?php echo $index > 0 ? 'collapsed' : ''; ?>" type="button" data-bs-toggle="collapse" data-bs-target="#collapse<?php echo $order['id']; ?>" aria-expanded="<?php echo $index === 0 ? 'true' : 'false'; ?>" aria-controls="collapse<?php echo $order['id']; ?>">
<div class="order-header">
<div class="order-header-item">
<span>شماره سفارش</span>
<strong>#<?php echo $order['id']; ?></strong>
</div>
<div class="order-header-item">
<span>تاریخ ثبت</span>
<strong><?php echo jdate('d F Y', strtotime($order['created_at'])); ?></strong>
</div>
<div class="order-header-item">
<span>مبلغ کل</span>
<strong><?php echo number_format($order['total_amount']); ?> تومان</strong>
</div>
<div class="order-header-item text-start">
<span class="order-status" style="background-color: <?php echo $status_info['color']; ?>;">
<?php echo $status_info['label']; ?>
</span>
</div>
</div>
</button>
</h2>
<div id="collapse<?php echo $order['id']; ?>" class="accordion-collapse collapse <?php echo $index === 0 ? 'show' : ''; ?>" aria-labelledby="heading<?php echo $order['id']; ?>" data-bs-parent="#ordersAccordion">
<div class="accordion-body">
<h6>جزئیات سفارش</h6>
<?php if (!empty($order['tracking_id'])): ?>
<p><strong>کد رهگیری:</strong> <?php echo htmlspecialchars($order['tracking_id']); ?></p>
<?php endif; ?>
</h6>
<small class="text-muted">#<?php echo $address['id']; ?></small>
</div>
<p class="mb-1"><?php echo htmlspecialchars($address['address_line']); ?></p>
<small class="text-muted">کدپستی: <?php echo htmlspecialchars($address['postal_code']); ?></small>
<div class="mt-2">
<a href="#" class="btn btn-sm btn-outline-danger">حذف</a>
<a href="#" class="btn btn-sm btn-outline-primary">ویرایش</a>
<table class="table order-details-table">
<tbody>
<?php foreach ($items as $item): ?>
<tr>
<td><img src="<?php echo htmlspecialchars($item['image_url']); ?>" alt="<?php echo htmlspecialchars($item['name']); ?>"></td>
<td>
<?php echo htmlspecialchars($item['name']); ?>
<?php if (!empty($item['color'])): ?>
<br>
<small>
رنگ:
<span class="product-color-swatch" style="background-color: <?php echo htmlspecialchars($item['color']); ?>" title="<?php echo htmlspecialchars($item['color']); ?>"></span>
</small>
<?php endif; ?>
</td>
<td><?php echo $item['quantity']; ?> عدد</td>
<td class="text-start"><?php echo number_format($item['price']); ?> تومان</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
<!-- Addresses Tab -->
<div class="tab-pane fade" id="addresses" role="tabpanel" aria-labelledby="addresses-tab">
<h3>آدرس‌های من</h3>
<!-- Address management will be implemented here -->
</div>
</div>
</div>
</div>
@ -117,3 +302,4 @@ require_once 'includes/header.php';
<?php
require_once 'includes/footer.php';
?>

156
track_order.php Normal file
View File

@ -0,0 +1,156 @@
<?php
$page_title = "پیگیری سفارش";
include 'includes/header.php';
?>
<div class="track-container">
<h1>پیگیری سفارش</h1>
<p>کد رهگیری و شماره تلفن خود را برای مشاهده جزئیات سفارش وارد کنید.</p>
<form id="track-order-form">
<div class="form-group">
<label for="tracking_id">کد رهگیری</label>
<input type="text" id="tracking_id" name="tracking_id" required>
</div>
<div class="form-group">
<label for="phone">شماره تلفن</label>
<input type="text" id="phone" name="phone" required>
</div>
<button type="submit" class="btn-track">جستجو</button>
</form>
<div id="result-message" style="margin-top: 20px; font-weight: bold;"></div>
</div>
<!-- The Modal -->
<div id="order-modal" class="modal">
<div class="modal-content">
<span class="close-btn">&times;</span>
<div id="modal-body">
<!-- Order details will be injected here by JavaScript -->
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
const form = document.getElementById('track-order-form');
const modal = document.getElementById('order-modal');
const modalBody = document.getElementById('modal-body');
const closeBtn = document.querySelector('.close-btn');
const resultMessage = document.getElementById('result-message');
form.addEventListener('submit', function (e) {
e.preventDefault();
const trackingId = document.getElementById('tracking_id').value;
const phone = document.getElementById('phone').value;
resultMessage.textContent = 'در حال جستجو...';
resultMessage.style.color = 'var(--luxury-text-muted)';
fetch('api/get_order_details.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
tracking_id: trackingId,
phone: phone
}),
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
if (data.success) {
resultMessage.textContent = '';
displayOrderDetails(data.order, data.products);
modal.style.display = 'block';
} else {
resultMessage.textContent = data.message;
resultMessage.style.color = '#ff6b6b'; // A clearer error color
}
})
.catch(error => {
console.error('Error:', error);
resultMessage.textContent = 'خطا در برقراری ارتباط با سرور.';
resultMessage.style.color = '#ff6b6b';
});
});
function displayOrderDetails(order, products) {
let productsHtml = `
<div class="detail-box" style="grid-column: 1 / -1;">
<h3>محصولات سفارش</h3>
<table class="products-table">
<thead>
<tr>
<th>محصول</th>
<th>تعداد</th>
<th>رنگ</th>
<th>قیمت واحد</th>
<th>قیمت کل</th>
</tr>
</thead>
<tbody>
`;
products.forEach(p => {
productsHtml += `
<tr>
<td><img src="assets/images/products/${p.image}" alt="${p.name}">${p.name}</td>
<td>${p.quantity}</td>
<td>${p.color || '-'}</td>
<td>${parseInt(p.price).toLocaleString()} تومان</td>
<td>${(p.quantity * p.price).toLocaleString()} تومان</td>
</tr>
`;
});
productsHtml += `
</tbody>
</table>
</div>
`;
modalBody.innerHTML = `
<div class="modal-header">
<h2>جزئیات سفارش</h2>
<p>کد رهگیری: ${order.tracking_id}</p>
</div>
<div class="order-details-grid">
<div class="detail-box">
<h3>اطلاعات خریدار</h3>
<p><strong>نام و نام خانوادگی:</strong> ${order.full_name}</p>
<p><strong>ایمیل:</strong> ${order.email}</p>
<p><strong>تلفن:</strong> ${order.billing_phone}</p>
</div>
<div class="detail-box">
<h3>اطلاعات سفارش</h3>
<p><strong>وضعیت:</strong> <span style="font-weight: bold; color: #81c784;">${order.status}</span></p>
<p><strong>تاریخ ثبت:</strong> ${order.created_at_jalali}</p>
<p><strong>آدرس کامل:</strong> ${order.address}</p>
</div>
${productsHtml}
</div>
<div class="total-price-container">
<p>جمع کل: ${parseInt(order.total_price).toLocaleString()} تومان</p>
</div>
`;
}
closeBtn.onclick = function () {
modal.style.display = 'none';
}
window.onclick = function (event) {
if (event.target == modal) {
modal.style.display = 'none';
}
}
});
</script>
<?php include 'includes/footer.php'; ?>